This is a helper plugin for transforming existing libraries into TurnKey bundles. For a detailed discussion of TurnKey bundles, see the TurnKey Support Library documentation. The plugin provides two core features: binary rewriting and source file rewriting.
The plugin is published on Maven Central, not the Gradle Plugin Portal. Add to your
settings.gradle.kts
:
pluginManagement {
repositories {
gradlePluginPortal()
mavenLocal()
}
}
then import the plugin in the build.gradle.kts
via
plugins {
id("tools.aqua.turnkey") version "<version>"
}
When applied, the plugin does nothing. You must add tasks from the plugin to your project to handle your specific build scenario.
Binary rewriting operates on bundles of libraries for a specific platform. Each bundle is analyzed
as a whole and transformed into TurnKey-linked artifacts. The plugin automatically generates a
turnkey.xml
descriptor file for the bundle. The task is applied like this:
val turnkeyLinuxAMD64 by
tasks.registering(ELFTurnKeyTask::class) {
libraries.from(myBinaries).filter { it.isFile }
rootLibraryNames.add("libexamplejava.so")
targetDirectory = layout.buildDirectory.dir("turnkey/linux-amd64")
targetSubPath = "com/acme/example/linux/amd64"
}
Note that the task class is specific to the binary format (in this example, ELF). TurnKey tasks offer the following properties:
libraries
(required): must be set to the library files to be analyzed. Do not include duplicates, non-library files, or directories.rootLibraryNames
(required): the libraries that are required by your Java code. This is often a specific JNI library. All dependencies of this library are automatically included by the plugin.targetDirectory
(required): the base output directory for the transformed files and theturnkey.xml
.targetSubPath
(optional): when set, this suffix is added to the target directory for placement. This is a convenience that allows introducing a package structure while allowing a subsequent task to add thetargetDirectory
to its inputs while maintaining Gradle dependency tracking.
At the moment, tasks for COFF (Windows), ELF (Linux), and Mach-O (macOS) libraries exist. These
tasks require external, native programs to be installed. Native program lookup is designed to handle
most installation naming schemes by searching the following naming schemes for the program example
in each directory on the $PATH
:
- try
example
- for each defined file name extension
.
e in$PATHEXT
, tryexample.
e (required for.exe
on Windows) - try
example-
n, where n is a numeric version (required for multiple LLVM versions on Debian); in case of conflicts, the highest version is chosen - try
llvm-example
(required for LLVM reimplementations of tools) - try
llvm-example-
n (required for multiple LLVM reimplementaion version on Debian); in case of conflicts, the highest version is chosen
COFF libraries can be handled by the COFFTurnKeyTask
. This task requires a readobj
binary to be
installed. Both the GNU project and the LLVM project provide implementations. Since on Windows, no
relative linkage can be specified, this task only analyzes the dependency graph and generates
explicit load instructions for all dependents. No rewriting is performed.
ELF libraries can be handled by the ELFTurnKeyTask
. This task requires a patchelf
binary to be
installed, which is maintained by the NixOS project. This sets each library's RPath to origin
linkage (i.e., same-directory lookup). Only the root libraries need to be loaded.
Mach-O libraries can be handled by the MachOTurnKeyTask
. This task requires both a
install_name_tool
(alternatively: install-name-tool
) and a otool
binary to be installed. Both
Apple and the LLVM project provide implementations. This sets each bundled dependency's linkage to
loader-relative paths (i.e., same-directory lookup). Only the root libraries need to be loaded.
Java rewriting operates on single files via JavaParser. It is designed to insert calls to the TurnKey support library into an existing native wrapper that does not use the TurnKey abstraction. A transformation can be defined like this:
val rewriteNativeJava by
tasks.registering(JavaRewriteTask::class) {
inputDirectory = myJava.flatMap { it.outputDir }
inputFile = "com/acme/example/Native.java"
rewrite { compilationUnit ->
val nativeClass = compilationUnit.types.single { it.name.id == "Native" }
val staticInitializer =
nativeClass.members
.filterIsInstance<InitializerDeclaration>()
.first(InitializerDeclaration::isStatic)
staticInitializer.body =
BlockStmt(
NodeList(
ExpressionStmt(
MethodCallExpr(
"tools.aqua.turnkey.support.TurnKey.load",
StringLiteralExpr("com/acme/example"),
MethodReferenceExpr(
ClassExpr(parseClassOrInterfaceType("com.acme.example.Native")),
NodeList(),
"getResourceAsStream")),
)))
}
outputDirectory = layout.buildDirectory.dir("generated/rewritten")
}
The task offers the following properties:
inputDirectory
(required): must be set to a directory containing Java source files.inputFile
(required): is resolved relative to theinputDirectory
to locate the file to rewrite. This is a convenience that allows depending on a previous task's outputs via theinputDirectory
while maintaining Gradle dependency tracking.rewrite
(required): a function that accepts a JavaParserCompilationUnit
and applies the desired transformation. For convenience, this can be set via a function, as shown above. The example above replaces a static initializer with a call to the TurnKey support library. Important: the lambda assigned to this variable is subjected to Java serialization by Gradle for rebuild elision purposes. If the lambda captures any non-serializable object (e.g., the containing task object), runtime errors will result!outputDirectory
(required): the base output directory for the transformed Java file.outputFile
(optional): is resolved relative to theoutputDirectory
to locate the rewritten file (defaults to theinputFile
). This is a convenience that allows maintaining a package structure while allowing a subsequent task to add theoutputDirectory
to its inputs while maintaining Gradle dependency tracking.
The plugin requires Java 17 (minimum for Gradle 9+) and is linked against the Gradle 8.10.1 stack.
The plugin and other non-runtime code are licensed under the Apache Licence, Version 2.0. Standalone documentation is licensed under the Creative Commons Attribution 4.0 International License. Dependencies use other open-source licenses.