How can I access the dependencies of an application from within the build file of a dependency embedded in the application? - gradle

I have a Gradle-based library that is imported as a dependency into consuming applications. In other words, an application that consumes my library will have a build.gradle file with a list of dependencies that includes both my library as well as any other dependencies they wish to import.
From within my library's build.gradle file, I need to write a Gradle task that can access the full set of dependencies declared by the consuming application. In theory, this should be pretty straightforward, but hours of searching has not yielded a working solution yet.
The closest I've come is to follow this example and define an additional task in the library's build.gradle file that runs after the library is built:
build {
doLast {
project.getConfigurations().getByName('runtime')
.resolvedConfiguration
.firstLevelModuleDependencies
.each { println(it.name) }
}
}
I keep getting an error message that the 'runtime' configuration (passed into getByName and referenced in the Gradle forum post I linked) cannot be found. I have tried other common Gradle configurations that I can think of, but I never get any dependencies back from this code.
So: what is the best way to access the full set of dependencies declared by a consuming application from within the build file of one of those dependencies?

Okay, I mostly figured it out. The code snippet is essentially correct, but the configuration I should have been accessing was 'compileClasspath' or 'runtimeClasspath', not 'runtime'. This page helped me understand the configuration I was looking for.
The final build task in the library looks roughly like this:
build {
doLast {
// ...
def deps = project.getConfigurations().getByName('compileClasspath')
.resolvedConfiguration
.firstLevelModuleDependencies
.each {
// it.name will give you the dependency in the standard Gradle format (e.g."org.springframework.boot:spring-boot:1.5.22.RELEASE")
}
}
}

Related

How to make Kotlin `internal` objects accessible to tests?

My project uses several Gradle source sets for its production code base instead of just main:
domain
dal
rest
test
dbUnitTest
This has proven very useful for limiting dependencies and enforcing separation of concern.
It comes with one downside however: we cannot access classes or methods with visibility internal from within test classes. The reason for this is that the Kotlin compiler places every source set in its own "module":
$ find . -name '*.kotlin_module'
./classes/kotlin/domain/META-INF/contact-management_domain.kotlin_module
./classes/kotlin/dal/META-INF/contact-management_dal.kotlin_module
./classes/kotlin/rest/META-INF/contact-management_dal.kotlin_module
./classes/kotlin/test/META-INF/contact-management.kotlin_module
./classes/kotlin/dbUnitTest/META-INF/contact-management_dbUnitTest.kotlin_module
I would like all sourceset to use the same module name "contact-management", as the main sourceset would by default.
I tried to override the name with the compiler option -module-name:
tasks.withType<KotlinCompile> {
kotlinOptions {
// place all source sets in the same kotlin module, making objects with 'internal' visibility available to every source sets of this project
freeCompilerArgs += listOf("-module-name \"contact-management\")
}
}
Upon running gradlew build, I get
> Task :contact-management:compileDomainKotlin FAILED
e: Invalid argument: -module-name "contact-management"
The reason being that -module-name "contact-management_domain" is set before by the Gradle code invoking the Kotlin compiler as well, but apparently this option is only accepted once.
In a Gradle build, how can I control what is being considered "one module" by the Kotlin compiler?
A related question where the test source set is to be split has no satisfactory answers so far.
You can do that using kotlin compilations. (As far as I understand, a compilation is simply a block of files that are compiled together. A good explanation can be found here)
When you create a sourceset in gradle, the kotlin plugin creates a compilation under the hood (with the same name as the sourceset).
What you can do now with compilations is create associations. If a compilation A is associated with another compilation B, source code in A gets access to internal code units of B.
So in your case, if the test sourceset should get access to the dal sourceset you can simply associate the test compilation with the dal compilation:
kotlin.target.compilations.getByName("test").associateWith(kotlin.target.compilations.getByName("dal"))
PS: It also works the other way around. If you create compilations explicitly, the corresponding sourcesets are created under the hood. So for custom sourcesets you can create compilations and associate them:
val domainCompilation = kotlin.target.compilations.create("domain")
val dalCompilation = kotlin.target.compilations.create("dal") {
associateWith(domainCompilation)
}
In above example, the sourceset domain will have access to internal code units of the sourceset dal.

Adding a `.klib` library to kotlin multiplatform

I'm wondering how I'd be able to import my cinterop-ted library to gradle build of the kotlin multiplatform build.
I've already created the library.def file and filled it, I also generated the library.klib and the folder that goes with it.
I just don't understand how to import it into gradle.
I've looked all over internet and I found a reference to Konan and I'm wondering if that's something I have to use, or if that's something being used for something similar to 'cinterop'.
I've looked over the following links, and I haven't found anything remotely connected to the .klib import part of my question.
Link #1 (kotlinlang.org)
Link #2 (github.com)
Link #3 (plugins.gradle.org)
In general, you'll want to use the multiplatform plugin. If you're building a klib separately, you're creating some extra steps (probably). In Link #2 it says that platform plugin is deprecated. Konan is the name of the native platform/compiler. There was a separate plugin for that last year, but you definitely don't want to be using that.
I just created an example but it's not public yet, so this is the best one I have off hand:
https://github.com/JetBrains/kotlin-native/blob/3329f74c27b683574ac181bc40e3836ceccce6c1/samples/tensorflow/build.gradle.kts#L12
I'm working on a Firestore library. The native and interop config live in the multiplatform config.
kotlin {
android {
publishAllLibraryVariants()
}
// iosArm64()
iosX64("ios"){
compilations["main"].cinterops {
firebasecore {
packageName 'cocoapods.FirebaseCore'
defFile = file("$projectDir/src/iosMain/c_interop/FirebaseCore.def")
includeDirs ("$projectDir/../iosApp/Pods/FirebaseCore/Firebase/Core/Public")
compilerOpts ("-F$projectDir/src/iosMain/c_interop/modules/FirebaseCore-${versions.firebaseCoreIos}")
}
firestore {
packageName 'cocoapods.FirebaseFirestore'
defFile = file("$projectDir/src/iosMain/c_interop/FirebaseFirestore.def")
includeDirs ("$projectDir/../iosApp/Pods/FirebaseFirestore/Firestore/Source/Public", "$projectDir/../iosApp/Pods/FirebaseCore/Firebase/Core/Public")
compilerOpts ("-F$projectDir/src/iosMain/c_interop/modules/FirebaseFirestore-${versions.firebaseFirestoreIos}")
}
}
}
}
The cinterops sets up where the def files are and params. I then publish that whole thing as a multiplatform library. The actual native artifact is a klib ultimately, but it's all managed with gradle and dependency metadata.

Gradle Kotlin DSL: get sourceSet of another project

Currently, we're trying to migrate our existing build.gradle scripts to the new Kotlin DSL. Right now, we are struggling with the jar task configuration.
Our project is a simple multi-project. Let's say we've Core and Plugin and Plugin uses classes from Core. Now, when building Plugin, the target jar should include any used classes from Core.
This is how it looked like before:
jar {
from sourceSets.main.output
from project(':Core').sourceSets.main.output
}
And this is the current solution we've with Kotlin DSL:
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").the<SourceSetContainer>()["main"].allSource)
}
However, the above example just gives me an Extension of type 'SourceSetContainer' does not exist. Currently registered extension types: [ExtraPropertiesExtension] error. I've also tried other code snippets I've found, but none of them have been working so far.
I have also tried this (like suggested in the first answer):
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").sourceSets.getByName("main").allSource)
}
But then the IDE (and also the jar task) argues that sourceSets is not available: Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public val KotlinJvmProjectExtension.sourceSets: NamedDomainObjectContainer<DefaultKotlinSourceSet> defined in org.gradle.kotlin.dsl.
I hope that someone can help us, because it is very frustrating to spend hours in configuration instead of writing any usefull code.
Thank you very much in advance.
You can access the SourceSetContainer by
project(":Core").extensions.getByType(SourceSetContainer::class)
it seems <T : Any> Project.the(extensionType: KClass<T>): T looks in the convention of the project, while val Project.sourceSets: SourceSetContainer get() looks in the extensions ExtensionContaier instead. This is somewhat odd, as the documentation for the says "Returns the plugin convention or extension of the specified type."
Note that you may need to do your sourceSet manipulation in gradle.projectsEvaluated, because otherwise the sourceSet in question may not be configured yet if the corresponding project is not yet evaluated.
If you get access to the project, then everything should looks like your actual groovy gradle script:
project(":Core").sourceSets.getByName("main").allSource
So regarding your actual code:
val jar: Jar by tasks
jar.apply {
from(java.sourceSets["main"].allSource)
from(project(":Core").sourceSets.getByName("main").allSource)
}

Setting up a multi-project Gradle build

I'm setting up a multi-module Gradle build for a legacy system at work (replacing the current Ant build). However, I'm new to Gradle, and I'm not sure what's the best way to do it. And I want to do it right, because this build script will be around for a long time. I have found a way to do things that works, but when I google around and read answers on StackOverflow, I see people using a different way, which --in my case-- doesn't work. But maybe I'm doing something wrong. I've also been reading in the Gradle in Action book, but haven't found this particular issue there.
I have a build.gradle file in the root of my project, with a bunch of subdirectories that each contain a sub-project. Most of these are regular Java projects, but there are some Wars and Ears in there, too, which require some special packaging love. Each sub-project has its own build.gradle file which, at this point, only contains a list of dependencies, nothing more.
The build.gradle file in the root of my projects looks something like this (I left out the War stuff for brevity):
configure(javaProjects()) {
jar.doFirst {
manifest {
...
}
}
}
configure(earProjects()) {
apply plugin: 'ear'
ear.doFirst {
manifest {
...
}
}
}
Set<String> javaProjects() {
subprojects - earProjects()
}
Set<String> earProjects() {
subprojects.findAll { it.name.endsWith(".ear") }
}
The only reason why I'm doing things this way, is because it was the first solution I tried that I could get to work in my situation. Now that the script is growing, though, it starts to feel a little clunky. Also, the doFirst thing seems a little awkward.
But when I look on StackOverflow, I see recommendations of using constructs like this:
allprojects {
tasks.withType(Jar) {
manifest {
...
}
}
tasks.withType(Ear) {
manifest {
...
}
}
}
This seems much nicer, but I don't seem to be able to rewrite my script in that way. I get errors like this one:
Cannot change configuration ':some.subproject:compile' after it has been resolved.
I don't know what to do about this error, and I can't seem to google it either, for some reason.
So, my question is: have I indeed been doing things the wrong way? Or rather: in a way that is not idiomatic Gradle? For the sake of maintainability, I'd like to do things as idiomatically as possible. And if so: what can I do about the error message?
In general you should do things like described in your second snippet:
allprojects {
tasks.withType(Jar) {
manifest {
...
}
}
}
But there are some limitations where this isn't sufficient. The error message you get means that you modify the compile configuration AFTER the configuration is already resolved. That happens for example when you do something like
configurations.compile.files.each...
during the configuration phase (e.g. in your manifest block like seen above) and in another place (e.g. in one of your subprojects build.gradle files):
dependencies{
compile "org.acme:somelib:1.2.3"
}
The other problem with this is, that you resolve the compile dependencies every time you invoke your build script, even when no jar task is triggered.
The suggested workaround is to replace
tasks.withType(Jar) {
manifest {
...
}
}
with
tasks.withType(Jar) {
doFirst{
manifest {
...
}
}
}
That means that resolving the configuration is postponed to the execution phase of gradle and really just triggered when needed.
When you configure a project in a multiproject build you can think of that each snippet that is part of the whole configuration. you're not configuring the project 'twice' but you configure different aspects of the project at different places.
This is a known limitation of the current gradle configuration model.
You can still use
configure(earProjects()) {
}
that doesn't matter here. IMO it is just a matter of personal preference. The gradle project itself uses 'configure'.
Personally I prefer to apply the plugins like Ear or war on the projects build.gradle file to mark a project as a ear/war project.
To share common configurations among all ear projects, you could have something like this in your root build.gradle file:
allprojects{
plugins.withType(EarPlugin){
// only applied if it is a ear project
// put ear specific logic here
}
}

How do I execute a Gradle plugin task before another one?

I am using the native-artifacts plugin which defines a number of tasks that extract the Nar dependencies of dependencies that are built elsewhere (and stored in Nexus/Maven). I need to ensure these tasks are called before the build of a binary, otherwise the headers that these Nars include are not found.
My question is, how do I define a system/plugin-defined task as a dependency of one of my tasks?
I'd like something like:
binaries.all {binary ->
dependencies {
// this next line is now the same as the plugin-defined task I want to have called before
// before the build takes place
compile "extractNarDeps${binary.name.capitalize()}"
}
}
Sadly, this doesn't build. How can I achieve this please? I have a component called unitTests that is a C++ component and is used to create unitTestsExecutable. I want extractNarDepsxxx called before compileUnitTests is called.
First of all, do you have any list named binaries? If yes, then you can try something like below in your build.gradle file as dependencies will be executed before your defined task:
dependencies {
binaries.each {binary ->
// this next line is now the same as the plugin-defined task I want to have called before
// before the build takes place
compile "extractNarDeps${binary.name.capitalize()}"
}
}
// Your defined task here
The way I did it was to specify my tasks as dependencies of the plugin's relevant task. The missing syntax link was in using "tasks" as a means of referencing the tasks in the project.
The answer isn't mine, it was very well explained here: tasks._applied_plugin_task_name_here.dependsOn(myTask)

Resources