Background
I am using a multi-project gradle build to develop a java application. The build contains both library and application sub-projects. The applications depend on the libraries and these can in turn depend on other libraries. Each sub-project is a java module that requires other modules in a module-info.java
This must must be kept in sync with the dependencies in the gradle build script.
Problem
Some of the libraries depend on LWJGL which contains natives. The natives are separate dependencies that need to be handled. The way that I initially solved this was to make the module depend on the .native
version of the LWJGL modules. This also requires the build script to put the natives on the implementation
configuration. This is less ideal as the java module should not care about natives.
The build script generated by the LWJGL customize tool on their website recommends putting the natives on the runtimeOnly
configuration. The java module should then require the non-native modules. This does however result in the problem I am trying to solve. Due to the native dependencies (which are included in the runtimeOnly
configuration) ending up on the module path, they are only loaded if a module requires them. This means that I have to include them in the classpath instead. The natives would then need to be extracted into a folder which can be added to the classpath.
Only the applications need the natives to run and should therefore be the one to collect the natives. It is important that all natives from all libraries that the application depends on are collected. This includes libraries that depend on other libraries.
Partial Solution
After a bit of searching and looking at other projects, I came up with the solution of adding the native dependencies to a custom dependency configuration. This has the benefit of clear seperation between natives and non-natives. It would also allow the application sub-projects to collect all the natives through gradles existing dependency system.
Looking at the documentation for dependency configurations and the java library plugin, this would mean adding the configurations natives
, nativesClasspath
and nativeElements
. This would mimic the standard configurations created by the java plugins. The following is the build script for creating the configurations.
configurations.register("natives") {
canBeConsumed = false
canBeResolved = false
transitive = true
}
configurations.register("nativesPath") {
canBeConsumed = false
canBeResolved = true
transitive = true
extendsFrom(configurations.natives)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'java-natives'))
}
}
configurations.register("nativesElements") {
canBeConsumed = true
canBeResolved = false
transitive = false
extendsFrom(configurations.natives)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'java-natives'))
}
}
From my understanding, I should then be able to add native dependencies like this.
natives 'org.lwjgl:lwjgl-glfw::natives-windows'
natives project(':lib')
The nativesClasspath
configuration on the application sub-projects should then contain all the natives. These could then be extracted into a folder using a copy task such as this.
tasks.register('extractNatives', Copy) {
group = "natives"
description = "Extracts native libraries from all subprojects"
inputs.files configurations.nativesClasspath
outputs.dir "$buildDir/natives"
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from {
configurations.nativesClasspath.files
}
into "$buildDir/natives"
}
Finally, the folder can be added to the classpath of the run task and the eclipse project files. It can also be included in releases using the distribution plugin.
The problem with this is that the LWJGL platform/BOM that supplies the versions for all libraries and natives seem to require that the configuration uses the usage attribute java-runtime
instead of a custom one. I do not know what effect it would have to use java-runtime
for natives as well. Also, the three configurations seem to need some artifact setup as the natives are not collected properly. Only the projects end up in the dependency graph and not their natives.
Alternatives
I do not know if using the three configurations is overkill and fewer would be better. The java configurations seem to suggest it would be best to separate the configuration into these three. I could also just list the natives in the application sub-projects but that would defeat the purpose of having a separate library. Also, if using custom configurations is not the right way to do things, then please point me in a better direction. I am not necessarily looking for a specific solution, just a good one.