Skip to content

Lightweight compiler plugin intended for Kotlin/JVM library development and symbol visibility control.

License

Notifications You must be signed in to change notification settings

ZwenDo/Restrikt

Repository files navigation

Restrikt 2.0

Gradle Plugin

Gradle Plugin Portal

Compiler Plugin

Maven Central

Annotations

Maven Central

Others

Kotlin Java License

A Kotlin/JVM compiler plugin to restrict symbols access, from external project sources. Compatible with K2.


Current features:

  • Automatic way to hide symbols, with the automatic hiding of internal symbols.
  • Manual way to hide symbols, by using annotations to hide symbols from either Kotlin or Java sources.
  • Possibility to use the package-private visibility thanks to annotations.
  • Generation of private constructors for top-level classes.

Summary

  1. Dependency
    1. Gradle
    2. Maven
    3. Kotlin Compiler
  2. Plugin Configuration
    1. Available options
    2. Gradle
    3. Maven
    4. Kotlin Compiler
  3. Usage
    1. Internal symbols hiding
    2. Private constructors for Top-level classes
    3. 'Hide' Annotations
    4. PackagePrivate annotation
    5. Important notes
  4. Known issues
  5. How it works
  6. Future plans
  7. Changelog

Dependency

This compiler plugin offers some features working by detecting annotations on symbols. It allows you to define your own annotations, but also provides some default annotations recognized by the plugin. You can add them to your dependencies as regular dependencies.

Note

See the Plugin Configuration section for more information about the default annotations.

Using Gradle

Click to expand

Using Kotlin DSL:

plugins {
    id("com.zwendo.restrikt2") version "[latest-version]"
}

Using Groovy DSL:

plugins {
    id 'com.zwendo.restrikt2' version '[latest-version]'
}

To add the default annotations to your project, you can add the following dependencies:

Using Kotlin DSL:

dependencies {
    implementation("com.zwendo:restrikt2-annotations:[latest-version]")
}

Using Groovy DSL:

dependencies {
    implementation 'com.zwendo:restrikt2-annotations:[latest-version]'
}

Using Maven

Warning

Maven support does not work at the moment. However, to use the plugin maven if the support is added, it should be as follows:

Click to expand

First of all, you need to add the compiler plugin to the kotlin's maven plugin dependencies:

<!-- ... -->
<plugins>
    <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <version>[kotlin-version]</version>
        <!-- rest of the plugin configuration... -->

        <dependencies>
            <dependency>
                <groupId>com.zwendo</groupId>
                <artifactId>restrikt2-compiler-plugin</artifactId>
                <version>[latest-version]</version>
            </dependency>
        </dependencies>
    </plugin>
    <!-- other plugins... -->
</plugins><!-- ... -->

The second step is to add the plugin id to the list of compiler plugins:

<!-- ... -->
<plugins>
    <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <version>[kotlin-version]</version>

        <configuration>
            <compilerPlugins>
                <plugin>com.zwendo.restrikt2</plugin>
            </compilerPlugins>
        </configuration>

        <!-- rest of the plugin configuration... -->
    </plugin>
    <!-- other plugins... -->
</plugins><!-- ... -->

Your pom.xml should look like this:

<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>[kotlin-version]</version>
                <!-- ... -->

                <configuration>
                    <compilerPlugins>
                        <plugin>com.zwendo.restrikt2</plugin>
                    </compilerPlugins>
                </configuration>

                <dependencies>
                    <dependency>
                        <groupId>com.zwendo</groupId>
                        <artifactId>restrikt2-compiler-plugin</artifactId>
                        <version>[latest-version]</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

To add the default annotations to your project, you can add the following dependencies:

<dependencies>
    <dependency>
        <groupId>com.zwendo</groupId>
        <artifactId>restrikt2-annotations</artifactId>
        <version>[latest-version]</version>
    </dependency>
</dependencies>

Using Kotlinc in the command line

Click to expand

To use the plugin with the Kotlin compiler, you simply need to add the plugin to the list of compiler plugins used in your compilation process. It can be done by adding the following option to the kotlinc command:

-Xplugin=path/to/the/restrikt2-compiler-plugin.jar

Note that you might need to download the plugin jar. You can find it on the Maven Central Repository.

To the add default annotations to your project, you simply need to add the jars to the classpath of the compiler.

Plugin Configuration

Available options

Here are the currently supported default configuration options:

name type default allow multiple occurrences description
enabled boolean true false Whether the plugin is enabled.
automatic-internal-hiding boolean true false Whether the internal symbols should be automatically hidden.
toplevel-private-constructor boolean true false Whether to generate private constructor for top-level classes.
annotation-processing boolean true false Whether the plugin annotations should be parsed to manually hide symbols.
hide-from-java-annotation string (1) none(2) true Adds an annotation to the annotations marking elements as hidden from Java.
hide-from-kotlin-annotation string (1) none(2) true Adds an annotation to the annotations marking elements as hidden from Kotlin.
package-private-annotation string (1) none(2) true Adds an annotation to the annotations marking elements as package-private.
ignore-default-annotations boolean false false Whether to ignore default marking annotations when processing annotations. (3)

Important

Note 1: The syntax to add annotation with any option accepting an annotation is its fully qualified name where all packages are separated by a slash (/) and all inner classes are separated by a dot (.).

Here are few examples:

  • A Foo annotation declared in the a.b.c package could be added with a/b/c/Foo.
  • A Bar annotation declared in the Base class itself in the bar package could be added with bar/Base.Bar.

Note 2: none means that no annotation is added by default, except for the annotations controlled by the ignore-default-annotations option.

Note 3: Default annotations are:

  • com/zwendo/restrikt2/annotation/HideFromKotlin
  • com/zwendo/restrikt2/annotation/HideFromJava
  • com/zwendo/restrikt2/annotation/PackagePrivate

Important

Any custom annotation added to the plugin must have the BINARY or RUNTIME retention policy, for the plugin to be able to see it during the compilation process.

Gradle

Click to expand

[!NOTE]
To follow the kotlin/groovy camelCase convention, options listed above will have their names in camelCase in the DSL.

Moreover, the hide-from-java-annotation, hide-from-kotlin-annotation and package-private-annotation options will be renamed to hideFromJavaAnnotations, hideFromKotlinAnnotations and packagePrivateAnnotations respectively, and will be sets of strings instead of single strings.

You can configure the plugin using the configuration DSL.

restrikt2 {
    enabled = true
    hideFromJavaAnnotations = setOf("com/example/MyAnnotation", "com/example/MyOtherAnnotation")
    // ...
}

Maven

Click to expand

To configure the plugin with maven, you need to pass the configuration options as properties in the plugin configuration.

Here is an example of options configuration:

<pluginOptions>
    <option>com.zwendo.restrikt2:enabled=true</option>
    <option>com.zwendo.restrikt2:annotation-processing=false</option>
</pluginOptions>

Using the example above and the configuration presented in the Dependency section, your pom.xml should look like this:

<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>[kotlin-version]</version>

                <configuration>
                    <compilerPlugins>
                        <plugin>com.zwendo.restrikt2</plugin>
                    </compilerPlugins>

                    <pluginOptions>
                        <option>com.zwendo.restrikt2:enabled=true</option>
                        <option>com.zwendo.restrikt2:annotation-processing=false</option>
                    </pluginOptions>
                </configuration>

                <dependencies>
                    <dependency>
                        <groupId>com.zwendo</groupId>
                        <artifactId>restrikt2-compiler-plugin</artifactId>
                        <version>[latest-version]</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

Kotlinc in the command line

Click to expand

To configure the plugin with the Kotlin compiler, you need to pass the configuration options as arguments -P plugin:com.zwendo.restrikt2:[option-name]=[option-value].

Here is a concrete example:

-P plugin:com.zwendo.restrikt2:enabled=true

Usage

Internal symbols hiding

Restrikt plugin features automatic hiding from internal symbols in Kotlin sources. At compile time, all symbols with the internal visibility automatically receives the JVM ACC_SYNTHETIC flag, making them invisible to Java sources.

Thus, the following code:

// Foo.kt
internal class Foo {
    fun bar() {
        // ...
    }
}

internal fun baz() {
    // ...
}

Will be compiled to:

class Foo {
   // $FF: synthetic method
    public void bar() {
        
    }
}

class FooKt {
    // $FF: synthetic method
    public static void baz() {
        // ...
    }
}

Private constructors for Top-level classes

Restrikt plugin also features the generation of private constructors for top-level classes. This is done to prevent instantiation of top-level classes from Java sources.

It will compile the following code:

// Foo.kt
fun bar() {
    // ...
}

To:

public final class Foo {
    private Foo() {
        throw new AssertionError();
    }

    public static void bar() {
        // ...
    }
}

'Hide' annotations

Note

This section will only talk and use the default annotations in the examples, but everything shown here works the same way with custom annotations.

This plugin provides two annotations intended for symbol access restrictions. These two annotations, namely HideFromJava and HideFromKotlin, are used to hide symbols from Java and Kotlin sources respectively. They can be used on several targets such as classes, functions, properties, getters, setters, field etc. They are designed to be used in the same way, just by placing the right annotation on the symbol to hide as follows:

@HideFromJava
fun someFunction() { // will be hidden from java sources
    // ...
}

@HideFromKotlin
class SomeClass // will be hidden from kotlin sources

PackagePrivate annotation

Because sometimes you just want to hide code to the outside of your package, and because Kotlin doesn't provide a package-private visibility modifier, Restrikt plugin provides a PackagePrivate annotation to hide symbols from outside their package.

Important notes

  • All elements hidden by a 'Hide' annotation will still be accessible at runtime, meaning that already compiled code will still be able to access it ;

Known issues

Problems listed below are in the process of being resolved. If you encounter an issue that doesn't seem to be in this list, feel free to open an issue for it.


Limitation on platform specific elements

Due to the way compiler plugins work (they are called before platform specific elements are generated), the plugin cannot perform certain operations on platform specific elements. Here are the current known limitations:

  • Apply visibility restrictions on value class members ;
  • Generate private constructor to MultiFileClass facades.

How it works

This section is intended for curious people and aims at describing the most specific parts of how this project works.

Java hiding

Like the Kotlin @JvmSynthetic, marked elements will have an ACC_SYNTHETIC accessor added to them, hiding class members from Java sources. As for classes, because the ACC_SYNTHETIC doesn't work on them, the flag is applied to all the class members instead.

Kotlin hiding

To effectively hide elements from Kotlin, the plugin changes the elements visibility to internal (this is done only if the element visibility is greater than internal, currently public or protected). The elements made internal in this way are not marked with the ACC_SYNTHETIC flag to keep them visible from Java sources.

// Foo.kt
@HideFromKotlin
class Foo {
    // ...
}

// will be compiled to ...

// Foo.class
@HideFromKotlin
internal class Foo {
    // ...
}

Future Plans

  • Add support for generating annotations on all public (to be able to differentiate internal and public) symbols of a project to simplify Kotlin project obfuscation with ProGuard.

Changelog

0.1.2 - 2024-12-24

Bugfixes :

  • Fixed issue where elements annotated with HideFromKotlin were also not accessible from Java Changed due to the generation of an ACC_SYNTHETIC flag ;
  • Changed the retention policy of the HideFromJava annotation to BINARY, to be consistent with other annotations.

0.1.1 - 2024-10-11

Bugfixes :

  • Fixed parent state transitivity for fields and property methods ;
  • Disabled plugin on Value classes as it did not work properly.

0.1.0 - 2024-10-11

  • Initial release of the plugin.

About

Lightweight compiler plugin intended for Kotlin/JVM library development and symbol visibility control.

Topics

Resources

License

Stars

Watchers

Forks

Languages