I'm having a hard time applying my custom external gradle plugins to an existing multi-module project. My project's structure looks like this:
my-app/
├─ buildSrc/
│ ├─ build.gradle.kts
├─ module1/
│ ├─ build.gradle.kts
├─ module2/
│ ├─ build.gradle.kts
├─ settings.gradle.kts
I've now implemented a couple of custom binary plugins which live in another repository and publishing them to mavenLocal() like so:
Plugin Repo Code
gradlePlugin {
plugins {
create("plugin") {
id = "organization.gradle.my-conventions"
implementationClass = "organization.gradle.MyConventionsPlugin"
version = "0.0.1"
}
create("plugin2") {
id = "organization.gradle.my-other-conventions"
implementationClass = "organization.gradle.MyOtherConventionsPlugin"
version = "0.0.1"
}
}
}
Main Project Code
In my main project, I've configured settings.gradle.kts to resolve plugins from mavenLocal and apply one of the plugins (let's say plugin1) in the build.gradle.kts of module1. This looks like this:
plugins {
id("organzation.gradle.MyConventionsPlugin") version "0.0.1"
}
This doesn't work though. I end up getting a stacktrace saying:
> Failed to apply plugin class 'org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin'.
> Cannot run Project.afterEvaluate(Action) when the project is already evaluated.
I tried the same way of applying the plugins in a non multi-module project (i.e. a single build.gradle.kts) and the plugins are applied as expected. So this most likely has to do with the multi-module aspect of it and some additional config?
Related
application is a Gradle based project made up of a collection of separate projects, each project published independently and individually added as dependencies based on application requirements:
.
└── application
├── build.gradle.kts
├── framework-lib1
│ └── build.gradle.kts
└── framework-lib2
└── build.gradle.kts
framework-lib2 is optional, it provides persistence capabilities, application can run without it.
framework-lib2 has dependencies on Liquibase/Postgres/Hibernate/SpringData and exposes them as api so they're consumable by application code.
build.gradle.kts for framework-lib2 applies Liquibase plugin, registering additional Liquibase related tasks to the Gradle context.
plugins {
id("org.springframework.boot") apply false
id("org.liquibase.gradle") version "2.0.4"
}
The problem is that these new Liquibase tasks are registered under the framework-lib2 grouping e.g. IntelliJ Gradle task UI.
Is there a way to apply the liquibase plugin from the dependency project to the project consuming the dependency?
This is definitely a beginner gradle question, but I just can't make it work.
I have a library and an application which depends on the library.
.
/ library
/ application
I can install the library through gradle install in ./library, then have the application depend on the library through the group/name/version, and pick up the library through the mavenLocal() repository in the application build.gradle, then build the app in the application folder. And that works.
But I'd like to have a project, and that I could have a single command to build the application and, if needed, the library too (similar to the maven -am flag).
Here's what I have right now, in the parent folder, build.gradle:
subprojects {
apply plugin: "java"
repositories {
mavenCentral()
}
}
project(':application') {
dependencies {
compile project(':library')
}
}
and in the same folder I have settings.gradle:
include ':library', ':application'
Again, if I go in the library folder and run gradle install, it works. I get the artifact in my ~/.m2.
But now with the configuration I described, if I go in the root folder and run gradle shadowJar.. and the shadowJar task is present only in application, then gradle tries to compile the library but fails, apparently because it doesn't pick up the library dependencies.
* What went wrong:
Execution failed for task ':library:compileJava'.
> Compilation failed; see the compiler error output for details.
the errors are like, for instance:
Task :library:compileJava FAILED
.../DateTimeAdapter.java:5: error: package javax.xml.bind.annotation.adapters does not exist
import javax.xml.bind.annotation.adapters.XmlAdapter;
and sure enough these are dependencies in the library/build.gradle =>
implementation 'com.fasterxml.jackson.core:jackson-core:2.8.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.8'
So my question is how do I specify the application dependency on library, so that I can run a single command and have maven build the library if needed, then the application.. I thought what I did would have gotten the first part through, but it fails, apparently due to library's transitive dependencies. I actually expect I need to handle a second part after that, which is how to specify the dependency in the application build.json. I may have to switch from specifying the maven group/name/version coordinates to the project name, but I didn't get that far yet.
I would definitely not want to list the library dependencies in the root gradle file: I'd like library to handle its dependencies, and application its dependencies, I'd like to keep the root gradle file small.
application is unable to see the dependencies of library because you have declared the Jackson dependencies as an implementation detail of library.
What you intended to do was expose your library and it's dependencies as an api for consumers.
Dependencies declared in the implementation configuration can be thought of as "private" meaning consumers of your library should not access methods/classes that use those dependencies otherwise they will face errors like the one you are. api is basically the opposite of implementation.
The api configuration is available via the java-library plugin.
Full working example for what you're trying to achieve (Kotlin DSL):
├── application
│ └── build.gradle.kts
├── build.gradle.kts
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
│ └── build.gradle.kts
└── settings.gradle.kts
Root project build.gradle.kts:
subprojects {
apply {
plugin("java-library")
}
repositories {
jcenter()
}
}
settings.gradle.kts:
rootProject.name = "example-proj"
include("application")
include("library")
Library build.gradle.kts:
dependencies {
api("com.fasterxml.jackson.core:jackson-core:2.10.0")
api("com.fasterxml.jackson.core:jackson-databind:2.10.0")
}
Application build.gradle.kts:
dependencies {
implementation(project(":library"))
}
I have a spring project build by gradle. I upgraded gradle from 1.7 to 2.13
Project Structure
Project
├─ modules
│ └───apps
│ ├─service-engine
│ │ └──build.gradle
│ └─ build.gradle
└─ build.gradle
settings.gradle file
include 'modules:apps:service-engine',
Root build script
def prepareWar(war, project) {
//do somthing here
}
build script in apps module
subprojects {
apply plugin: 'war'
}
build script in service-engine
project.ext.set('moduleName', 'Service-Engine')
prepareWar(war, project)
But it gives error
What went wrong: A problem occurred evaluating project >':modules:apps:service-engine'.
Could not find property 'war' on project ':modules:apps:service-engine'.
It works fine with gradle 1.7, I could not find the reason for this error.
Please anybody can help me?
Adding build.gradle file in modules and add subprojects {
apply plugin: 'war'} to build script work for me.
Suppose I'm using Gradle for a modular library development. In my root project I have subprojects geometry, algorithms, visualizer, and I'd like to publish a jar artifact of each.
As for now in my root build.gradle I have the following part:
apply plugin: 'maven-publish'
publishing {
publications {
publishDemos(MavenPublication) {
groupId 'ru.ifmo.ctddev.igushkin.cg'
artifactId 'geometry'
version globalVersion
artifact project(':geometry').tasks.getByName('jar')
}
publishAlgorithms(MavenPublication) {
groupId 'ru.ifmo.ctddev.igushkin.cg'
artifactId 'algorithms'
version globalVersion
artifact project(':algorithms').tasks.getByName('jar')
}
publishVisualizer(MavenPublication) {
groupId 'ru.ifmo.ctddev.igushkin.cg'
artifactId 'visualizer'
version globalVersion
artifact project(':visualizer').tasks.getByName('jar')
}
}
}
My first question: is there a shorter way of describing the publications? For example, I'd like to state that for each subproject I need a publication with the artifactId set from its name.
Next, my subprojects depend on each other, both algorithms and visualizer depend on classes from geometry, but at this point the jars do not contain the dependencies, and, for example, the user will have to add dependencies to both geometry and algorithms if they want to use algorithms.
So, is there a way for to provide some sort of auto-dependency, so that adding algorithms would also add geometry? If yes, how do I do it? If no, what is the idiomatic way of providing modular libraries? Should I assemble jars with dependencies instead?
UPD: Am I right that instead of artifact ... I should just use from project(':...').components.java, because it will pick up both artifacts and dependencies? How do I pick dependencies separately if I use artifact ...?
You can do less verbose publication declaration by injecting the same publication config into each subproject. For example for a multi-build project with the structure:
ROOT
│ build.gradle
│ settings.gradle
├───subA
│ build.gradle
│
├───subB
│ build.gradle
│
└───subC
build.gradle
In your root build.gradle, you can do:
apply plugin:'maven-publish'
subprojects{
publishing {
publications {
"$project.name"(MavenPublication) {
groupId project.group
artifactId project.name
version project.version
from components.java
}
}
}
}
Each subproject defines its own groupid and version like so:
group = 'org.test.sample.A'
version = '1.0'
The artifactId is picked up from the subproject name. Running gradle publish results in a repo of this structure:
org
└───test
└───sample
├───A
│ └───subA
│ └───1.0
│ subA-1.0.jar
│ subA-1.0.pom
├───B
│ └───subB
│ └───1.0
│ subB-1.0.jar
│ subB-1.0.pom
└───C
└───subC
└───1.0
subC-1.0.jar
subC-1.0.pom
Dependencies
This config also automatically takes care of dependencies. For instance if in subproject subA you had:
dependencies{
compile project(':subB')
}
Since I am using from components.java instead of artifact, the plugin knows to look for dependencies and generates a pom for subA that includes:
<dependencies>
<dependency>
<groupId>org.gradle.sample.B</groupId>
<artifactId>subB</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
When applying a multi-project Gradle structure to our project, my settings.gradle looks like this:
include "source:compA:api"
include "source:compA:core"
include "source:compB"
gradle projects give me
Root project 'tmp'
\--- Project ':source'
+--- Project ':source:compA'
| +--- Project ':source:compA:api'
| \--- Project ':source:compA:core'
\--- Project ':source:compB'
This is exactly the directory structure!
In my root directory I have a build.gradle which applies the java plugin to all subprojects:
subprojects {
apply plugin: 'java'
}
When building I end up having artifacts for :source:compA which are empty because this is actually not a project just the subdirectories api and core are proper Java projects.
What's the best way to avoid having an empty artifact?
You can try using the trick they use in Gradle's own settings.gradle file. Note how each of the sub projects are located in the 'subprojects/${projectName}' folder, but the subprojects folder itself is not a project.
So in your case you'd do something like:
include "source:compA-api"
include "source:compA-core"
include "source:compB"
project(':source:compA-api').projectDir = new File(settingsDir, 'source/compA/api')
project(':source:compA-core').projectDir = new File(settingsDir, 'source/compA/core')
I have intentionally omitted the colon between compA and api to make sure source:compA does not get evaluated as a project container.
Alternatively, you can try excluding the source:compA project from having the java plugin applied to it, by doing something like:
def javaProjects() {
return subprojects.findAll { it.name != 'compA' }
}
configure(javaProjects()) {
apply plugin: 'java'
}
Edit:
Alternatively you can try something like this (adjust to your liking):
def javaProjects() {
return subprojects.findAll { new File(it.projectDir, "src").exists() }
}
configure(javaProjects()) {
apply plugin: 'java'
}
Starting with Gradle 6.7, the Gradle user manual recommends against configuring subprojects using the "cross project configuration" feature using subprojects and allprojects:
Another, discouraged, way to share build logic between subproject is cross project configuration via the subprojects {} and allprojects {} DSL constructs. With cross configuration, build logic can be injected into a subproject and this is not obvious when looking at the subproject’s build script, making it harder to understand the logic of a particular subproject. In the long run, cross configuration usually grows complex with more and more conditional logic and a higher maintenance burden. Cross configuration can also introduce configuration-time coupling between projects, which can prevent optimizations like configuration-on-demand from working properly.
The suggested approach is to instead use convention plugins to define the common traits:
Gradle’s recommended way of organizing build logic is to use its plugin system. A plugin should define the type of a subproject. In fact, Gradle core plugins are modeled in the same way - for example, the Java Plugin configures a generic java project, while Java Library Plugin internally applies the Java Plugin and configures aspects specific to a Java library in addition. Similarly, the Application Plugin applies and configures the Java Plugin and the Distribution Plugin.
You can compose custom build logic by applying and configuring both core and external plugins and create custom plugins that define new project types and configure conventions specific to your project or organization. For each of the example traits from the beginning of this section, we can write a plugin that encapsulates the logic common to the subproject of a given type.
We recommend putting source code and tests for the convention plugins in the special buildSrc directory in the root directory of the project. For more information about buildSrc, consult Using buildSrc to organize build logic.
In your particular case, you could follow the approach given in Gradle's sample:
├── buildSrc
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ └── groovy
│ │ │ ├── source.java-conventions.gradle
The buildSrc/build.gradle file would consist of just the groovy-gradle-plugin:
plugins {
id 'groovy-gradle-plugin'
}
The buildSrc/src/main/groovy/source.java-conventions.gradle would contain the common logic for your Java projects. In your example, you just had the application of the Java plugin, but you would add any other commonality of the Java plugins that wouldn't be shared with non-Java projects:
plugins {
id 'groovy-gradle-plugin'
}
Each Java project would then include the convention plugin:
plugins {
id 'source.java-conventions'
}
Note that this doesn't buy much if literally the only thing that's common is the java plugin; you're replacing one plugin inclusion with another. But as soon as you end up with more shared build logic than that, it starts to pay off in terms of cross-project consistency & reduction of duplicated code.
I have the situation two. The empty parent directory is regarded as project. We can have some check to ignore the project.
project.getBuildFile().exists()