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

Gradle Plugin Portal Maven Central License Kotlin

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


Current features:

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

Summary

  1. Dependency
    1. Gradle plugins DSL
    2. Gradle apply method
    3. Maven
  2. Plugin Configuration
    1. Available options
    2. Gradle
    3. Maven
  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

Both compiler plugin and annotations are added to your project's dependencies in the same unique way, as shown below :

Using the Gradle plugin DSL (Gradle 2.1+)

Click to expand

Using Kotlin DSL:

plugins {
   id("com.zwendo.restrikt") version "4.0.0"
}

Using Groovy DSL:

plugins {
   id 'com.zwendo.restrikt' version '4.0.0'
}

Using apply method (Gradle prior to 2.1)

Click to expand

Using Kotlin DSL:

buildscript {
   repositories {
      maven {
         url = uri("https://plugins.gradle.org/m2/")
      }
   }

   dependencies {
      classpath("gradle.plugin.com.restrikt:restrikt:4.0.0")
   }
}

apply(plugin = "com.zwendo.restrikt")

Using Groovy DSL:

buildscript {
   repositories {
      maven {
         url 'https://plugins.gradle.org/m2/'
      }
   }

   dependencies {
      classpath 'gradle.plugin.com.restrikt:restrikt:4.0.0'
   }
}


apply plugin: 'com.zwendo.restrikt'

Using Maven

Click to expand

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

<dependency>
    <groupId>com.zwendo</groupId>
    <artifactId>restrikt-compiler-plugin</artifactId>
    <version>[latest-version]</version>
</dependency>

Then, you need to add the annotations to your project's dependencies:

<dependency>
    <groupId>com.zwendo</groupId>
    <artifactId>restrikt-annotation</artifactId>
    <version>[latest-version]</version>
</dependency>

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>
                <!-- ... -->
                <dependencies>
                    <dependency>
                        <groupId>com.zwendo</groupId>
                        <artifactId>restrikt-compiler-plugin</artifactId>
                        <version>[latest-version]</version>
                    </dependency>
                    <!-- ... -->
                </dependencies>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <!-- ... -->
        <dependency>
            <groupId>com.zwendo</groupId>
            <artifactId>restrikt-annotation</artifactId>
            <version>[latest-version]</version>
        </dependency>
    </dependencies>
</project>

Plugin Configuration

Available options

Click to expand

Here are the currently supported default configuration options:

name type default description
enabled boolean true Whether the plugin is enabled
automaticInternalHiding boolean true Whether the internal symbols should be automatically hidden.
annotationProcessing boolean true Whether the plugin annotations should be parsed to manually hide symbols.
toplevelPrivateConstructor boolean true Whether to generate private constructor for top-level classes.
defaultRetentionPolicy string binary The default retention for the plugin's annotation

Moreover, all annotations of the plugin can be individually configured using their own DSL (hideFromKotlin, hideFromJava or packagePrivate), with the following configuration options:

name type default description
enabled boolean true Whether the annotation should be processed to hide symbols. (works only if annotationProcessing is true).
retention boolean binary The retention policy of the annotation.
defaultReason string none The default reason written on the annotation if no specific reason is provided.

In addition to the options above, HideFromKotlin annotation can be configured using the following options:

name type default description
deprecatedMessage string none The message written on the Deprecatedannotation.

Gradle

Click to expand

You can configure the plugin using the configuration DSL.

restrikt {
   option = value
   // ...
}

To configure the annotations, a DSL function is available for each annotation:

restrikt {
    hideFromKotlin {
        option = value
        // ...
    }
}

Maven

Currently, the plugin is not configurable using Maven. The default configuration will be used.

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.

Private constructors for Top-level classes

Restrikt plugin also features the generation of private constructors for top-level classes (classic top-level classes as well as facade classes generated by @JvmMultifileClass annotation). This is done to prevent instantiation of top-level classes from Java sources.

'Hide' 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

Both annotations also accepts a string parameter to indicate the reason of the restriction. If no message is provided, the default message defined in the plugin configuration will be used instead.

@HideFromKotlin(reason = "This class is designed for Java")
class Bar { // will be hidden from kotlin sources
   // ...
}

Moreover, it is possible to configure the annotation retention directly on the annotation, using the retention parameter. This parameter accepts the following enum values: source, binary, runtime and default. If no value is provided, the default retention policy defined in the plugin configuration will be used instead.

@HideFromKotlin(retention = RestriktRetention.SOURCE) // will not be added to the class file
class Foo { // 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 ;
  • Symbols hidden from Kotlin will still be accessible at compile-time from Kotlin sources in the same class (there is some very specific exceptions but the only access that always work is from the same class).
  • Most IDEs won't warn you on the usage of a symbol made package-private by the @PackagePrivate annotation. However, at runtime, you will get an IllegalAccessError if you try to access it from outside its package.

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.


All known issues have been resolved.

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, this annotation induce the generation of the JVM ACC_SYNTHETIC, 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 generates on the marked elements the @Deprecated annotation. This annotation used with the DeprecationLevel.HIDDEN level, makes the element invisible to Kotlin sources, but still visible to Java sources.

// Foo.kt
@HideFromKotlin(reason = "java only")
class Foo {
    // ...
}

// will be compiled to ...

// Foo.class
@HideFromKotlin(reason = "java only")
@Deprecated("java only", DeprecationLevel.HIDDEN)
class Foo {
    // ...
}

Generating the Deprecated annotation or simply using it directly have slightly different outcomes. Indeed, the Deprecated annotation (with HIDDEN level) acts as a flag for the Kotlin compiler. The latter will add the JVM ACC_SYNTHETIC flag for the element in the produced classfile, making it also invisible for Java sources. The hack is that the Kotlin compiler runs before calling the compiler plugin, so when it writes the classfile, the Deprecated annotation is not present meaning that the ACC_SYNTHETIC flag is not set.

Future Plans

  • Add plugin support for maven projects ;
  • Create a Restrikt IDEA plugin to prevent restricted symbols misuse ;
  • 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

4.0.0 - 2023-03-31

Breaking Changes :

  • Annotations now accepts a retention parameter to specify the annotation retention.

Features :

  • Performance improvements by using a more efficient way to process symbols. Plugin is now lighter (from 900 to less than 300 lines for the plugin's backend).

Bugfixes :

  • Plugin now properly works with inline functions.

3.0.1 - 2023-01-11

Bugfixes :

  • Fixed bug were Restrikt caused errors when compiling tests, solved by disabling the plugin for test sources.

3.0.0 - 2023-01-09

Breaking Changes :

  • Option keepAnnotation has been replaced by the new retention option.

Features :

  • Annotation retentions are now individually configurable
  • Slightly improved performance by using a more efficient way to retrieve information from symbols.
  • Plugin now works with maven projects.

Bugfix :

  • Plugin now works with inline functions
  • Plugin now correctly hides multi file classes functions

2.1.0 - 2022.09.05

Features :

  • Added the PackagePrivate annotation to force compiler to use the package-private visibility ;
  • Plugin can now generate private constructors for top-level classes ;
  • HideFromJava annotation now supports the File and Property targets ;
  • HideFromKotlin annotation now supports the Property target.

Bugfixes :

  • 'Hide' annotations now works correctly on annotation classes declarations ;
  • Automatic internal hiding now works properly on constructors.

2.0.0 - 2022.08.27

Features :

  • Automatic detection and hiding from internal symbols ;
  • Added the HideFromJava annotation to hide symbols from Java sources ;
  • New gradle plugin configuration options for each annotation and internal hiding.

About

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

Topics

Resources

License

Stars

Watchers

Forks

Languages