Restrikt
Restrikt copied to clipboard
Lightweight compiler plugin intended for Kotlin/JVM library development and symbol visibility control.
Current features:
- Automatic way to hide symbols, with the automatic hiding of
internalsymbols ; - Manual way to hide symbols, by using two different annotations to hide symbols from either Kotlin or Java sources ;
- Possibility to use the
package-privatevisibility thanks to ann annotation ; - Generation of private constructors for top-level classes .
Summary
- Dependency
- Gradle plugins DSL
- Gradle apply method
- Maven
- Plugin Configuration
- Available options
- Gradle
- Maven
- Usage
- Internal symbols hiding
- Private constructors for Top-level classes
- 'Hide' Annotations
- PackagePrivate annotation
- Important notes
- Known issues
- How it works
- Future plans
- 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
@PackagePrivateannotation. However, at runtime, you will get anIllegalAccessErrorif 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 differentiateinternalandpublic) symbols of a project to simplify Kotlin project obfuscation with ProGuard.
Changelog
4.0.0 - 2023-03-31
Breaking Changes :
- Annotations now accepts a
retentionparameter 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
keepAnnotationhas been replaced by the newretentionoption.
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
PackagePrivateannotation to force compiler to use thepackage-privatevisibility ; - Plugin can now generate private constructors for top-level classes ;
HideFromJavaannotation now supports the File and Property targets ;HideFromKotlinannotation 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
HideFromJavaannotation to hide symbols from Java sources ; - New gradle plugin configuration options for each annotation and internal hiding.