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

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.

Related

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

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")
}
}
}

Where is documentation for Gradle's `test` block from Java plugin located?

As can be seen from the top of documentation of class org.gradle.api.tasks.testing.Test, test tasks can be configured using the following piece of code:
test {
// configuration here. For example:
useJUnitPlatform()
}
From usage of method useJUnitPlatform we can assume that method test is called with a Closure which has an instance of aforementioned class Test as delegate.
In Gradle, there are other similar methods which take a Closure. For example, afterEvaluate. Documentation of method afterEvaluate is readily available in documentation of class Project. This is also mentioned in the user guide:
This example uses method Project.afterEvaluate() to add a closure which is executed after the project is evaluated.
Where is the documentation of method test? I could not find it. Or maybe this isn't a method in a class, but inserted via reflection into class Project at runtime or some similar DSL magic?
test in this context is not a method per se, but rather a task named test. To figure out what exactly is going on here requires diving into the internals of Gradle itself which is not part of any public documentation because well, it's not part of the public API.
The only way to figure exactly out what is going on is to debug Gradle during its execution. The easiest way to do that is to generate a plugin project via gradle init. Write a simple Gradle build file such as (build.gradle; I am assuming you are using the Groovy DSL):
plugins {
id("java")
}
test {
useJUnitPlatform()
}
Then write a basic functional test and start debugging. I was curious myself what is going and did just that.
In the following screenshot, you can see the stack trace in the bottom left corner. As you can see, there is a lot of methods called.
There is a mixture of Groovy specific methods and Gradle specific methods. Digging further in, you will come to:
You can see here bottom right that the list of objects is:
Project (root project)
Extra properties
Extensions
Tasks
This aligns with what I mentioned earlier: Gradle will go out of its way to match to what is being asked for. This is also explained in the "A Groovy Build Script Primer" in official documentation (starting from "If the name is unqualified [...]").
Eventually, you will land in some of the public API methods:
getByName is part of NamedDomainObjectContainer which is documented. However, what actually implements that interface is not as you can see from the Javadoc here. The implementation, from debugging, is DefaultTaskContainer.
The rest I will leave to you as an exercise. Hopefully this gives you an idea as to what is going on behind the scenes.
Indeed, test { ... } in this case is not calling a method with name test. This block is a feature of the Gradle API called "Groovy dynamic task configuration block". Per Gradle documentation version 6.1:
// Configure task using Groovy dynamic task configuration block
myCopy {
from 'resources'
into 'target'
}
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')
This works for any task. Task access is just a shortcut for the tasks.named() (Kotlin) or tasks.getByName() (Groovy) method. It is important to note that blocks used here are for configuring the task and are not evaluated when the task executes.
As such, per this shortcut convention, test { ... } block is used for configuring a task registered in the project – task with name test in this case.
Although nowadays I'd recommend using Gradle's Configuration Avoidance API to configure a task lazily instead of eagerly:
project.tasks.named('test', Test).configure {
it.useJunitPlatform()
}
See getByName replacement in the table "Existing vs New API overview".

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)
}

How to tell tasks and other code blocks apart in Gradle?

I have a build.gradle file cobbled together from examples online:
apply plugin: "java"
sourceSets {
java {
srcDirs = ['src']
}
}
repositories {
flatDir {
name "fileRepo"
dirs "repo"
}
}
uploadArchives {
repositories {
add project.repositories.fileRepo
}
}
When I run gradle tasks --all, I can see that "uploadArchives" is a task. How can I tell what is a task by looking at the build.gradle file? If "repositories" and "sourceSets" aren't considered tasks, what are they?
You simply can't.
But, the pure knowledge whether a closure configures a task or something else, won't give you anything. To understand a build script, you will need to understand the basic concept of Gradle and the used plugins, either built-in or third-party.
Each build.gradle script is executed against a Project instance. Everything you can access from the build script belongs to one of the following scopes:
The Project object itself. This scope includes any property getters and setters declared by the Project implementation class. For example, getRootProject() is accessible as the rootProject property. The properties of this scope are readable or writable depending on the presence of the corresponding getter or setter method.
The extra properties of the project. Each project maintains a map of extra properties, which can contain any arbitrary name -> value pair. Once defined, the properties of this scope are readable and writable. See extra properties for more details.
The extensions added to the project by the plugins. Each extension is available as a read-only property with the same name as the extension.
The convention properties added to the project by the plugins. A plugin can add properties and methods to a project through the project's Convention object. The properties of this scope may be readable or writable, depending on the convention objects.
The tasks of the project. A task is accessible by using its name as a property name. The properties of this scope are read-only. For example, a task called compile is accessible as the compile property.
The extra properties and convention properties are inherited from the project's parent, recursively up to the root project. The properties of this scope are read-only.
For your specific example, uploadArchives is a task, repositories belongs to the original Project object (it is available in each build script) and sourceSets is an extension of the java plugin.
Please note, that many plugins do not require or plan direct task configuration. They provide a DSL extension for configuration and then generate the tasks based on this configuration.

The actual use of custom configurations in gradle

I am new to gradle and struggling with a basic problem. I have a set of compile time dependencies declared in my project. My problem statement is, I want to make a subset of dependencies non transitive and the remaining transitive.
I have tried to make a custom configuration which extends from compile and set its transitive property to false.
Customcompile.extendsFrom(compile)
Customcompile.transitive = false
By this, I assume that whatever I declare with
Customcompile 'xxx:xxx:1.0' will have transitive=false applied and that it will act as compile time dependency.
But this is not able to compile my project with these dependencies
Am I wrong anywhere in this assumption?
You need to change customCompile.extendsFrom(compile) to compile.extendsFrom(customCompile).
configurations {
customCompile
customCompile.transitive = false
compile.extendsFrom(customCompile)
}
This is because the compilation classpath is derived from the dependencies for the compile configuration.
By making compile configuration extend from customCompile configuration, you are now including all dependencies from customCompile configuration into compile configuration.

Resources