I'm trying to start using Kotlin DSL with gradle in the project with the following restrictions:
Project has different modules (moreover: sometimes these modules use different plugins, however if two projects uses the same plugin then version of the plugins are the same).
Project has internal corporate repositories only (e.g. we don't use jcenter directly, we use proxy for it).
What we have with Groovy:
Some common configurations items are excluded to the separate scripts. Please check the example below.
Gradle modules include these files.
As a result (just based on my example):
We don't need to add the same code lines into the each module.
The most of projects have difference just with dependency list.
I tried to reproduce the same with Gralde KTS and received the following difficulties:
I'm unable to apply plugin in the include file and use it in the module. In this case I receive compilation error (because plugin types are not added into the module script).
I'm unable to extract constants to the something common to use them in the each scripts (root build.gradle.kts inclusive). With Groovy I can just use variable like springBootVersion, however with Kotlin Script I have to create the same property in the each module file.
Precompiled script plugins does not work without public repositories access (e.g. I'm unable to configure common script file with idea "just use default embedded Kotlin Script version, download all dependencies from these urls: ...".
Include file sample:
apply plugin: 'kotlin'
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
Gradle module sample:
apply from: "${rootDir}/gradle/include/kotlin-common-include.gradle"
dependencies {
compile project(':my.project.libraries.common')
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion
}
Questions:
How can I put all common constants (such as dependency versions) to the separate file to include them just by using something like springBootVersion or Constants.springBootVersion with compile-time checks?
How can I extract plugin applying to the include scripts (to avoid Gradle module scripts overload)?
How can I use precompiled script plugins without public global repositories access?
Additional links:
Issue for Gradle KTS for plugin applying.
Issue for Gradle KTS for shared constants extracting.
There are limitations in Kotlin DSL currently (5.3) that prevents to have compile-time checks everywhere. In order for Kotlin DSL to work it has to add extensions on top of the Gradle API and it can't do it magically. First of all you need to go through Kotlin DSL Primer page to understand it.
How can I extract plugin applying to the include scripts (to avoid Gradle module scripts overload)?
The one way to do it is to use precompiled build scripts with Kotlin DSL Plugin. In order to do it you need to move your script into $rootDir/buildSrc project. Here how it might look like:
// $rootDir/buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
maven {
url = uri("http://host:8082/artifactory/...")
}
}
Then put your common script like that:
// $rootDir/buildSrc/src/main/kotlin/common.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.3.21"
}
tasks.compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
tasks.compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Then you can apply this script as to a plugin like that:
// $rootDir/build.gradle.kts
subprojects {
apply(id = "common")
}
How can I use precompiled script plugins without public global repositories access?
Configuring custom repositories for pre-compiled scripts plugin is no different that your usual build script. Do it like that:
// $rootDir/buildSrc/settings.gradle.kts
pluginManagement {
repositories.maven {
url = uri("http://host:8082/artifactory/...")
}
}
The other way around that if you don't want to use precompiled plugins is to configure extensions explicitly. You can do it like that:
// $rootDir/gradle/common.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
And in main script:
// $rootDir/build.gradle.kts
subprojects {
apply {
plugin(KotlinPlatformJvmPlugin::class)
from("common.gradle.kts")
}
}
How can I put all common constants (such as dependency versions) to the separate file to include them just by using something like springBootVersion or Constants.springBootVersion with compile-time checks?
There is no good way to do it currently. You can use extra properties, but it won't guarantee compile time checks. Something like that:
// $rootDir/dependencies.gradle.kts
// this will try to take configuration from existing ones
val compile by configurations
val api by configurations
dependencies {
compile("commons-io:commons-io:1.2.3")
api("some.dep")
}
// This will put your version into extra extension
extra["springBootVersion"] = "1.2.3"
And you can use it like this:
// $rootDir/build.gradle.kts
subprojects {
apply {
plugin<JavaLibraryPlugin>()
from("$rootDir/dependencies.gradle.kts")
}
And in your module:
// $rootDir/module/build.gradle.kts
// This will take existing dependency from extra
val springBootVersion: String by extra
dependencies {
compile("org.spring:boot:$springBootVersion")
}
Related
I have a Java Gradle project that uses an OpenAPI specified API. I've used the org.openapi.generator plugin which generates sources as well as a complete Gradle module.
I expect that there's a way to define the generate, compile, jar steps such that I can have other modules depend on the generated module.
I.e.
# api/build.gradle:
plugins {
id 'java'
id "org.openapi.generator" version "5.0.0"
}
repositories {
mavenCentral()
}
dependencies {
testImplementation group: 'junit', name: 'junit', version: '4.12'
}
compileJava.dependsOn "openApiGenerate"
openApiGenerate {
generatorName = "java"
inputSpec = "$projectDir/src/main/openapi/spec.yaml".toString()
outputDir = "$buildDir/generated"
apiPackage = "com.example.api"
invokerPackage = "com.example.api.invoker"
modelPackage = "com.example.api.model"
configOptions = [
dateLibrary: "java8",
library : "native"
]
groupId = "com.example"
id = "api"
}
gradlew api:openApiGenerate generates (extraneous files elided):
api/build/generated/
├── build.gradle
├── pom.xml
├── settings.gradle
└── src
├── main/java/...
└── test/java/...
Is there some way I can delegate-to, include, or depend on this generated module from other modules in the project? The generated module has a reliable group:artifact:version coordinate.
I.e. I'd like to be able to specify com.example:api:1.0 elsewhere in the project.
I've had a read through of https://docs.gradle.org/current/userguide/composite_builds.html as that seemed to be close to what I expect, but I am new to Gradle and it was a little to deep.
I've tried overriding the main and test source sets in api/build.gradle but I dislike having to copy and paste the dependencies from the api/build/generated/build.gradle.
I found https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:dependency-types which includes a tantalizing example but falls down as it is a source-only dependency.
dependencies {
implementation files("$buildDir/classes") {
builtBy 'compile'
}
}
I looked at this example but how do I depend on a project (api/build/generated/) that does not exist yet?
dependencies {
implementation project(':shared')
}
Great question! I don’t have a perfect answer but hopefully the following will still help.
Suggested Approach
I would keep the builds of the modules that depend on the generated API completely separate from the build that generates the API. The only connection between such builds should be a dependency declaration. That means, you’ll have to manually make sure to build the API generating project first and only build the dependent projects afterwards.
By default, this would mean to also publish the API module before the dependent projects can be built. An alternative to this default would indeed be composite builds – for example, to allow you to test a newly generated API locally first before publishing it. However, before creating/running the composite build, you would have to manually run the API generating build each time that the OpenAPI document changes.
Example
Let’s say you have project A depending on the generated API. Its Gradle build would contain something like this:
dependencies {
implementation 'com.example:api:1.0'
}
Before running A’s build, you’d first have to run
./gradlew openApiGenerate from your api project.
./gradlew publish from the api/build/generated/ directory.
Then A’s build could fetch the published dependency from the publishing repository.
Alternatively, you could drop step 2 locally and run A’s build with an additional Gradle CLI option:
./gradlew --include-build $path_to/api/build/generated/ …
Idea for Less Manual Work
I have thought quite a bit about this but didn’t come up with any complete solution – hence my imperfect suggestion above. Let me still summarize my idea for how this could work.
You would have a Gradle build which generates the API – similar to your api project. That build would also be committed to your VCS.
That build would publish the generated API, even if it wouldn’t produce it itself. Instead, it would somehow delegate to the Gradle build generated by the openApiGenerate task. The delegation would have to happen via a GradleBuild task.
Here lies the crux: all information on dependencies and published artifacts would effectively have to be retrieved via the Gradle CLI. I doubt that that’s currently possible.
Projects that dependend on the API could then include the api-like Gradle project in a composite build without requiring the manual hassle from the approach above.
To expand on #Chriki's answer with what I've actually used:
Define api/ as it's own project with an empty api/settings.gradle file.
This tells gradle that it is a self-contained project.
Define the api module with:
# api/build.gradle
plugins {
id 'java'
id "org.openapi.generator" version "5.0.0"
}
repositories {
mavenCentral()
}
openApiGenerate {
generatorName = "java"
inputSpec = "$projectDir/src/main/openapi/specification.yaml"
outputDir = "$buildDir/generated"
apiPackage = "com.example.api"
invokerPackage = "com.example.api.invoker"
modelPackage = "com.example.api.model"
configOptions = [
dateLibrary: "java8",
library : "native"
]
groupId = "com.example"
id = "api"
version = "1.0.0"
}
Note the group and id (and version) explicitly define its maven coordinate.
Include the build with a substitution so that the dependents can just use its maven coordinate:
# settings.gradle
includeBuild('api/build/generated') {
dependencySubstitution {
substitute module('com.example:api') with project(':')
}
}
... and in some other module:
# app/build.gradle
dependencies {
implementation group: 'com.example', name: 'api'
}
The main advantage of this over ./gradlew --include-build api/build/generated is that [my] IDE will 'link' it all up too.
Generate the API library:
./gradlew --project-dir api/ openApiGenerate
Build/run the main project:
./gradlew build
./gradlew run
I have a gradle project with a lot of subprojects and I want a BOM file to apply to all the subprojects.
I tried to put this in some subproject and it works fine:
dependencies{
implementation enforcedPlatform('group:bom-artifact:version')
}
But when I put it a parent gradle.build, or wrap it like:
allprojects {
dependencies {
implementation enforcedPlatform('group:bom-artifact:version')
}
}
It ends with error:
> Could not find method implementation() for arguments [DefaultExternalModuleDependency{group='group', name='bom-artifact', version='version', configuration='default'}] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Can't figure out what's wrong. How to fix this? Or is there a better way to apply one BOM to all subprojects and manage it from one place?
I don't think the issue is the platform itself here
The message you're getting usually appears if you have not (yet) applied the Java plugin.
Gradle's configuration scopes like implementation, api and compileOnly are initialized as part of the Java plugin's init phase.
So depending on the structure of your subproject you might have one or more subprojects that don't use the Java plugin that does not recorgnize the scope. I'm not quite sure about the execution order between subprojects, this might play also play a role.
A simple solution would be to apply the plugin also into the allprojects closure, like
allprojects {
apply plugin: 'java'
dependencies {
implementation enforcedPlatform('group:bom-artifact:version')
}
}
I am converting over to using IntelliJ (version 2019.1). The multi-project directory structure used has the standard src/main/java and src/test/java for each project, but additionally has some non-standard ones such as: src/testsupport/java.
Gradlew (using the internal/recommended gradlew packaged within IntelliJ) is used to import the projects. The Gradle build files include both:
apply plugin: 'idea'
apply plugin: 'java'
Edited to improve clarity
Every project imports fine. Interproject references work to the standard directories. However, when I am in Project B, but need access to src/generated/java or src/testsupport/java from Project A, those are not imported (import statements that compile fine from the gradle command line show up as unresolvable within IntelliJ). Is there a configuration change or something needed to make these take effect?
Currently, I have:
subprojects {
idea {
module {
testSourceDirs += project.sourceSets.generated.java.srcDirs
testSourceDirs += project.sourceSets.testsupport.java.srcDirs
}
}
}
You need help Gradle out by creating a source set for the custom sources your projects define. So from your question, something like:
(using Kotlin DSL)
allprojects {
apply {
plugin("idea")
plugin("java-library")
}
repositories {
mavenCentral()
}
configure<SourceSetContainer> {
create("generated") {
compileClasspath += project.the<SourceSetContainer>()["main"].output
runtimeClasspath += project.the<SourceSetContainer>()["main"].output
}
create("testsupport") {
compileClasspath += project.the<SourceSetContainer>()["main"].output
runtimeClasspath += project.the<SourceSetContainer>()["main"].output
}
}
val api by configurations
val testImplementation by configurations
val testRuntimeOnly by configurations
dependencies {
api(platform("org.junit:junit-bom:5.5.1"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
val test by tasks.getting(Test::class) {
useJUnitPlatform()
}
}
The above will give you:
So now you want to use projectA in projectB, so projectB's Gradle file would include a dependency on projectA:
dependencies {
implementation(":projectA")
}
This should hopefully get you started. Keep in mind, the examples given above use the Kotlin DSL which you should be able to convert back to Groovy.
References:
https://docs.gradle.org/current/userguide/java_plugin.html#source_sets
https://docs.gradle.org/current/userguide/java_testing.html#sec:configuring_java_integration_tests
I'm using Antlr in a simple Kotlin/Gradle project, and while my Gradle build is generating Antlr sources, they are not available for importing into the project.
As you can see (on the left), the classes (Lexer/Parser, etc.) are being generated. I have also configured this generated-src/antlr/main directory as a Source Root. Most questions I see list this as a solution, but I've already done it.
The issue persists after multiple rebuilds (both in IDEA and on the CLI), and following all the usual "Invalidate Cache and Restart" issues.
Further, the import issue is listed in the Gradle build on the CLI so it doesn't seem isolated to IDEA.
What am I missing here?
Here's the build.gradle file produced by IDEA when I was creating the project initially, and which IDEA is using for project/workspace synchronization.
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}
group 'com.craigotis'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
apply plugin: 'antlr'
dependencies {
antlr "org.antlr:antlr4:4.5"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Try adding this to your build.gradle:
sourceSets {
main.java.srcDirs += "${project.buildDir}/generated-src/antlr/main"
}
generateGrammarSource {
arguments += ["-visitor", "-package", "com.craigotis.sprint.core.antlr"]
outputDirectory = file("${project.buildDir}/generated-src/antlr/main/com/craigotis/sprint/core/antlr")
}
compileKotlin.dependsOn generateGrammarSource
Shouldn't it locate the compiled classes and not the sources? Do you see the antlr generated classes in the target directory?
Try this: first build the project without referencing or using any ANTLR generated classes, and only after the build is successful, then add the code that references them.
(In other words, what I think that happens, is that your ANTLR sources are compiled after the code that references them. They never have a chance to compile because build fails before)
Also if this is really the case, you can solve it also by splitting into two artifacts and make sure the ANTLR one is built before the one with the code that uses it
Try to add generated sources in idea module like this post from Daniel Dekany here:
apply plugin: "idea"
...
sourceSets.main.java.srcDir new File(buildDir, 'generated/javacc')
idea {
module {
// Marks the already(!) added srcDir as "generated"
generatedSourceDirs += file('build/generated/javacc')
}
}
I am trying to move from Dagger 1.2.2 to Dagger 2.0.1 in AppEngine project (NOT Android one).
With Dagger 1.2.2 simple:
compile 'com.squareup.dagger:dagger-compiler:1.2.2'
compile 'com.squareup.dagger:dagger:1.2.2'
did the trick.
With Dagger 2.0.1:
compile 'com.google.dagger:dagger-compiler:2.0.1'
compile 'com.google.dagger:dagger:2.0.1'
does not work (source is generated but mixed up with *.class files in build/classes/main/..package../).
You can also do it without net.ltgt.apt plugin, (which by the way may conflict with lombok).
apply plugin: 'java'
apply plugin: 'idea'
def generatedMain = new File(buildDir, "generated/main")
compileJava {
doFirst {
generatedMain.mkdirs()
}
options.compilerArgs += ['-s', generatedMain]
}
idea.module.sourceDirs += generatedMain
dependencies {
compileOnly 'com.google.dagger:dagger-compiler:2.8'
compile 'com.google.dagger:dagger:2.8'
}
I've found a solution.
https://github.com/tbroyer/gradle-apt-plugin
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "net.ltgt.gradle:gradle-apt-plugin:0.3"
}
}
apply plugin: "net.ltgt.apt"
dependecies {
apt 'com.google.dagger:dagger-compiler:2.0.1'
compile 'com.google.dagger:dagger:2.0.1'
}
Additionally if you are using Intellij a following configuration is recommended:
When using the Gradle integration in IntelliJ IDEA however, rather
than the idea task, you'll have to manually enable annotation
processing: in Settings… → Build, Execution, Deployment → Compiler →
Annotation Processors, check Enable annotation processing and Obtain
processors from project classpath. To mimic the Gradle behavior and
generated files behavior, you can configure the production and test
sources directories to build/generated/source/apt/main and
build/generated/source/apt/test respectively and choose to Store
generated sources relative to: Module content root.
I've also had to remove Exclude from whole build directory and mark generated/source/apt/main directory as source.