How to define gradle task for several child modules? - gradle

I have an android project that contains different library modules. I defined a task which uploads artifacts to a maven repository. It works when I define it directly in the build.gradle of the modules. What I would like to do is to define the task once in the parents build.gradle and reuse it in each module.
I followed the guide here to write following build configuration:
My project setup is like this:
parent_project
|- module1
|- module2
|- module3
parent/build.gradle, default
subprojects {
task installToLocalMaven(type: Upload) {
description "Installs artifacts to local Maven repository"
configuration = configurations['archives']
repositories {
mavenDeployer {
pom.groupId = commonGroupId
pom.artifactId = artifactId
pom.version = gitBranch() + '-' + android.defaultConfig.versionName
if (android.defaultConfig.versionName.endsWith("-SNAPSHOT")) {
snapshotRepository(url: repositories.mavenLocal().url)
} else {
repository(url: repositories.mavenLocal().url)
}
}
}
}
}
project(':module1') {
ext.artifactId = "module1"
}
...
Executing gradlew :module1:installToLocalMaven leads to this Error Configuration with name 'archives' not found.
I assume that the configuration object from the parent project is used.
How can I make sure that the configurations object of the child module is used from within the parents build.gradle?

You can use the task's project property. Just add task.project. in front of the sub-project object you are trying to access like this:
configuration = task.project.configurations['archives']

Related

Gradle how to publish a gradle-plugin to maven central

Hi I have a java project, which contains a submodule which is a gradle plugin. I want to publish this module to maven central.
I used this two plugins in my build.gradle:
plugins {
id 'java-gradle-plugin'
id 'maven-publish'
}
and my publishing block looks something like:
publishing {
publications {
javaLibrary(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
artifactId = project.archivesBaseName
pom {
name = artifactId
description = "..."
url =
licenses {
license {
}
}
developers {
...
}
scm {
...
}
issueManagement {
...
}
ciManagement {
...
}
}
}
}
repositories { maven { url = "some local repo" } }
}
I noticed that when I build this module, the generated pom-default.xml is what I expected, but when I run gradle publishToMavenLocal and manually checked the pom.xml file in the .m2 folder, all the metadata like name description licenses are gone!
I also noticed in the .m2 folder there are 2 artifacts that are related to this single plugin, I think it's somewhat related with https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers but I don't fully understand the meaning. Both these 2 artifacts' pom are missing the pom metadata as I described above.
Could some gradle expert help me here: how to keep the metadata in the published pom?
You should not need to manually define a MavenPublication for your plugin submodule. The java-gradle-plugin reacts to the application of the maven-publish plugin and automatically configures/creates publications for the plugin artifacts. See this line.
You are correct for the (2) artifacts produced. One is the plugin marker (single pom.xml) and the other is the actual plugin JAR artifact.
As for POM customization, Gradle seemingly provides its own the POM irrespective of any POM customization(s) you have defined: https://github.com/gradle/gradle/issues/17022
Sorry for late answer, but you can do something like this:
afterEvaluate {
tasks.withType(GenerateMavenPom) { task ->
doFirst {
// Update POM here
def pom = task.pom
pom.name = ...
pom.url = ...
pom.description = ...
pom.scm {
...
}
}
}
}
This will catch the pom of the plugin marker artifact as well.

Define an artifact to be used as a dependency in another project

TL;DR
I'm trying to configure two Gradle projects in a way that one project uses files built by the other one.
The first project is added to the second one by includeBuild and the file is defined in the second project as a dependency.
Project testA
settings.gradle:
rootProject.name = 'testA'
build.gradle:
group = 'org.test'
version = '0.0.0.1_test'
task someZip (type: Zip) {
from './settings.gradle'
archiveName = 'xxx.zip'
destinationDir = file("${buildDir}/test")
}
artifacts {
//TODO add something here?
}
Project testB
settings.gradle:
rootProject.name = 'testB'
if (System.getenv('LOCAL_COMPILATION') == 'true') {
includeBuild '../testA'
}
build.gradle:
if (System.getenv('LOCAL_COMPILATION') != 'true') {
repositories {
maven { url '192.168.1.100' }
}
}
configurations {
magic
}
dependencies {
magic 'org.test:xxx:0.0.0.+#zip'
}
task ultimateZip (type: Zip) {
from configurations.magic
archiveName = 'ultimate.zip'
destinationDir = file("${buildDir}/ultimate-test")
}
Description
You may noticed that the example has an option use a maven repository. I wanted to highlight that eventually there will be a possibility to do that.
Using Maven repository is not the point of this question, though, other than the solution should not interfere with that.
(In other words you can assume that System.getenv('LOCAL_COMPILATION') == 'true'.)
The question is how to define the artifact in a way that the other project is able to recognize it.
The preferred solution should be similar to what the Java plugin does because I'm using jar dependencies in my projects and they are working both through includeBuild and through a repository.
The following setup should work (tested with Gradle 5.5.1). It mostly corresponds to your original setup with the exception of the changes indicated by XXX.
Project testA
settings.gradle:
rootProject.name = 'testA'
build.gradle:
group = 'org.test'
version = '0.0.0.1_test'
task someZip (type: Zip) {
from './settings.gradle'
archiveName = 'xxx.zip'
destinationDir = file("${buildDir}/test")
}
// XXX (replaced your empty "artifacts" block)
configurations.create('default')
def myArtifact = artifacts.add('default', someZip) {
name = 'xxx'
}
// XXX (only added to show that publishing works)
apply plugin: 'maven-publish'
publishing {
repositories {
maven { url = 'file:///tmp/my-repo' }
}
publications {
myPub(MavenPublication) {
artifactId myArtifact.name
artifact myArtifact
}
}
}
Project testB
settings.gradle:
rootProject.name = 'testB'
if (System.getenv('LOCAL_COMPILATION') == 'true') {
// XXX (added a dependency substitution to let Gradle know that
// "org.test:xxx" corresponds to the testA project)
includeBuild('../testA') {
dependencySubstitution {
substitute module('org.test:xxx') with project(':')
}
}
}
build.gradle:
if (System.getenv('LOCAL_COMPILATION') != 'true') {
repositories {
// XXX (only changed to show that resolution still works after
// publishing)
maven { url = 'file:///tmp/my-repo' }
}
}
configurations {
magic
}
dependencies {
magic 'org.test:xxx:0.0.0.+#zip'
}
task ultimateZip (type: Zip) {
from configurations.magic
archiveName = 'ultimate.zip'
destinationDir = file("${buildDir}/ultimate-test")
}
As requested in the comments, here’s some more explanation on the created default configuration and the added artifact in project testA.
Composite builds in Gradle currently have the limitation that substituted project dependencies “will always point to the default configuration of the target project”. In your example this means that testA needs to publish in the default configuration. We thus first create the default configuration. Note that some plugins (like java) already create this configuration; you don’t need to create it yourself when using such plugins.
It doesn’t seem to be mentioned explicitly anywhere but as you seem to have found out yourself already, the PublishedArtifacts of a project (as declared with project.artifacts) are important for Gradle to figure out the dependency wiring in composite builds. Hence, we make sure to declare such a PublishedArtifact in testA using this API. The artifact (e.g., its extension) is configured based on the properties of the someZip task. The name seems to not be taken from the someZip task in your example because you manually set archiveName; hence we need to explicitly declare it. If you use archiveBaseName = 'xxx' in someZip instead, then you don’t need the closure when adding the artifact.

Publish (rootproject) pom without (rootproject) publishing artifact / packaging = pom

I'm migrating one of our projects from maven to gradle: it's a gradle multi-project & all subprojects are publishing artifacts to artifactory. So far so good.
The legacy (maven-based) build environment however also expects the root project to publish a pom file with the "packaging" node equal to "pom" (common maven behaviour, so it seems)
So now, I'm trying to have this generated by Gradle, but only find ways to customize an automatically generated pom for each artifact, I can't find a way to generate/upload a pom without publishing an actual artifact.
Workaround for now is to have the root project use the java plugin, generate/install an empty jar and manipulate the generated pom to conform to maven expectations (packaging=pom), but that's a hack.
Is there a way to have this root pom file generated with gradle ?
Example project:
settings.gradle
rootProject.name = 'MultiProject'
include 'child01', 'child02'
rootProject.children.each { it.name = rootProject.name + "-" + it.name }
build.gradle
subprojects {
apply plugin: 'java'
}
allprojects {
apply plugin: 'maven'
group = 'my_group'
version = '0.0.1-SNAPSHOT'
}
EDIT (current workaround), addition to build.gradle
// workaround to generate pom
apply plugin: 'java'
configurations {
pomCreation
}
task createPom {
ext.newPomFile = "${buildDir}/blabla.pom"
doLast {
pom {
project {
packaging 'pom'
}
}.writeTo(newPomFile)
}
}
install.dependsOn(createPom)
artifacts {
pomCreation file(createPom.newPomFile)
}
I would use the gradle maven-publish plugin for that. With that plugin you can define your specific pom and don't have to upload other artifacts. Here an example:
publishing {
publications {
maven(MavenPublication) {
pom.withXml{
def xml = asNode()
xml.children().last() + {
delegate.dependencies {
delegate.dependency {
delegate.groupId 'org.springframework'
delegate.artifactId 'spring-context'
delegate.version( '3.2.8.RELEASE' )
}
}
}
}
}
}

Gradle Setting Manifest Class-Path on Jars in Ear

Set Up
I'm using Gradle and have a multi-project build using Java EE with IBM WebSphere Application Server. The project directory structure looks like this:
--/build.gradle
--/defaults.gradle
--/settings.gradle
--/common-ejb
--/common-ejb/build.gradle
--/logging
--/logging/build.gradle
--/project1
--/project1/build.gradle
--/project1-ejb
--/project1-ejb/build.gradle
--/project2
--/project2/build.gradle
--/project2-ejb
--/project2-ejb/build.gradle
project1 and project2 are individual ears that get deployed. They both reuse a number of EJBs from common-ejb and share some other library dependencies that aren't relevant for this question.
After performing the build: project1.ear looks like:
--/lib/log4j.jar
--/lib/logging.jar
--/META-INF/application.xml
--/META-INF/MANIFEST.MF
--/common-ejb.jar
--/project1-ejb.jar
Gradle properly creates the application.xml to load EJBs from both projects. Unfortunately, project1-ejb.jar will fail to load due to dependencies on common-ejb.jar. The project1-ejb.jar/META-INF/MANIFEST.MF needs to have the Class-Path set with common-ejb.jar since it's not in the lib/ directory.
I was able to set it by explicitly defining it as done below. Gradle knows the dependencies for the Class-Path, so it should be able do this automatically. Is there a way to set this up?
Gradle Files
Not including project2, but you can guess what it looks like.
--/build.gradle
apply from: 'defaults.gradle'
defaultTasks 'clean', 'build'
--/defaults.gradle
defaultTasks 'build'
repositories {
mavenCentral()
}
--/settings.gradle
include 'common-ejb'
include 'project1'
include 'project1-ejb'
include 'logging'
--/logging/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'log4j:log4j:1.2.+'
}
--/common-ejb/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'javax:javaee-api:6.0'
compile project(':logging')
}
--/project1-ejb/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'javax:javaee-api:6.0'
compile project(':common-ejb')
compile project(':logging')
}
// THIS IS THE WORKAROUND, I don't want to explicitly modify the Class-Path for each EJB based on the EAR the EJB is going to be included in.
jar {
manifest {
attributes("Class-Path": project(':common-ejb').jar.archiveName)
}
}
--/project1/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'ear'
apply plugin: 'java'
dependencies {
deploy project(':project1-ejb')
deploy project(':common-ejb')
earlib project(':logging')
}
Using some information from and modifying code from a question about getting all dependencies of a project from the Gradle forums.
Essentially, you want to take the EAR's deploy dependencies and see if any deploy dependencies depend on each other. If they do, you set the Class-Path to include the referenced jars.
Remove the manifest lines from project1-ejb and project2-ejb. Add the following to your defaults.gradle:
def getAllDependentProjects(project) {
if ( !project.configurations.hasProperty("runtime") ) {
return []
}
def projectDependencies = project.configurations.runtime.getAllDependencies().withType(ProjectDependency)
def dependentProjects = projectDependencies*.dependencyProject
if (dependentProjects.size > 0) {
dependentProjects.each { dependentProjects += getAllDependentProjects(it) }
}
return dependentProjects.unique()
}
gradle.projectsEvaluated {
if (plugins.hasPlugin('ear')) {
def deployProjectDependencies = configurations.deploy.getAllDependencies().withType(ProjectDependency)*.dependencyProject
deployProjectDependencies.each {
def cur = it
def cur_deps = getAllDependentProjects(cur)
def depJars = []
deployProjectDependencies.each {
def search = it
if ( cur_deps.contains(search)) {
depJars += search.jar.archiveName
}
}
depJars = depJars.unique()
if ( depJars.size() > 0 ) {
logger.info("META-INF Dependencies for deploy dependency " + cur.name + ": " + depJars)
cur.jar.manifest.attributes(
'Class-Path': depJars.join(' ')
)
}
}
}
}
This will have the desired affect. Directly after the configuration step and before build, the EAR projects will reevaluate their dependencies to see if any are cross-referenced. There may be a more efficient way, but this gets the job done.

How to upload an existing collection of 3rd-party Jars to a Maven server in Gradle?

How can I upload a collection of existing Jars to a Maven repository? The Jars are built from an ANT Task imported to Gradle, and used as a dependency to my task... The Jars don't have version tag, so they should ALL receive the same version number when they are uploaded...
apply plugin: 'java'
apply plugin: 'maven'
version = "6.1.1"
group = "com.oahu"
ant.importBuild "$projectDir/tools/ant/package.xml"
uploadArchives(dependsOn: ["oahu-jar", "client-sdk-jar", "common-jar"]) << {
// the dependencies will generate "oahu.jar", "oahu_client_sdk.jar", "common.jar"
// UPLOAD THE DEPENDENCIES LISTED ABOVE LOCATED AT the subdirectory "build/"
description = "Uploads the generated jar ${archivesBaseName}-${version}.jar to ${cn_mvn_serverUrl}"
repositories.mavenDeployer {
repository(url: "${cn_mvn_releaseUrl}") {
authentication(userName: "${cn_mvn_username}", password: "${cn_mvn_password}")
}
}
}
The tasks "oahu-jar", "client-sdk-jar", "common-jar" are the ones imported from ANT... I have the Maven repositories configuration already working from another project... But the Maven plugin uploads the Jar generated by the Jar task from the Java plugin... Considering the imported ANT tasks generates:
build.gradle
src
build
|-"oahu.jar"
|-"oahu_client_sdk.jar"
|-"common.jar"
The result of this should be the upload of those Jars with the given version...
"oahu-6.1.1.jar", "oahu_client_sdk-6.1.1.jar", "common-6.1.1.jar"... all uploaded to the Maven repository...
Add sourceSets? Configuration? Artifacts?
currently that is not explicitly supported by gradle so you have to do some scripting for that. Based on your snippet above, I've created a sample snippet, that should be easy to adapt:
apply plugin:'java'
apply plugin:'maven'
import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
version = "6.1.1"
group = "com.oahu"
ant.importBuild "$projectDir/tools/ant/package.xml"
// a list of the ant tasks that create a jar
// I assumed the following convention:
// ant task named "SampleAntJar-jar" creates the jar "build/SampleAntJar.jar"
def antJarTasks = ["SampleAntJar-jar", "SecondSampleAntJar-jar"]
artifacts{
//for each ant task add a defaultpublishArtifact to the archives configuration
antJarTasks.each{ taskName ->
def artifactName = taskName - '-jar'
archives new DefaultPublishArtifact(artifactName, "jar", "jar", null, new
Date(), new File("$buildDir", "${artifactName}.jar"))
}
}
uploadArchives(){
dependsOn: antJarTasks
repositories {
mavenDeployer {
repository(url: "file://{'/Users/Rene/.m2/repository/'}")
antJarTasks.each{ antJarTask ->
antJarName = antJarTask - "-jar"
addFilter(antJarName) {artifact, file ->
artifact.name == antJarName
}
pom(antJarName).artifactId = antJarName
}
}
}
}
regards,
René

Resources