Gradle: Publish a pre-built jar to a Maven repository with dependencies - maven

There are already questions on Stackoverflow about publishing pre built jars to Maven from Gradle. However, this is slightly different: how do I publish a pre-built Jar to a Maven repo and at the same time provide the dependencies to include in the pom.xml file?
I have a jar that is being pre-built external to this script. I need to publish this jar to our Maven repo (Nexus) and specify dependencies in the pom.xml. I have been able to get a pre built jar published to a Maven repo using the artifacts closure but it ignores the dependencies closure. If I add the java plugin then Maven plugin creates a pom with the dependencies but will upload a zero byte jar file. I guess this is because the Java plugin expects to compile and package source in the src dir, which does not exist in this project.
Is there a way I can 'inject' a pre-built Jar into the Java plugin process so that I can the jar uploaded along with the dependencies? Or am I missing something else that's obvious?
Of course the best thing would be for the pre-built Jar's build process to outline its dependencies and upload to Maven but unfortunately it's a 3rd party piece of software and we have no control.
Below script publishes a zero kb jar file...
apply plugin: 'java'
apply plugin: 'maven'
jar = file(projectHome + '/build/lib').listFiles()[0]
configurations {
archives
runtime
}
dependencies {
runtime 'org.apache.tika:tika-app:1.3'
}
artifacts {
archives jar
}
uploadArchives {
repositories {
mavenDeployer {
repository(url: "http://build.com/nexus/content/repositories/releases/")
pom.version = tag
pom.artifactId = "artifact"
pom.groupId = "group"
}
}
}
Many thanks! Rob

We can publish a pre-built jar into a Maven repo using Gradle by using java plugin and maven-publish plugin. maven-publish has a feature to inject dependencies into generated pom using pom.withXml block.
Hereby I'm giving a sample build.gradle file which generates a pom.xml of the pre-built artifact with its specified dependencies. You can try this suggestion as shown below.
apply plugin: "maven-publish"
apply plugin: "java"
dependencies {
compile "dependency:antlr:2.7.7"
compile "dependency:commons-beanutils:1.9.3"
compile "dependency:dom4j:1.6.1"
compile "dependency:jettison:1.3.8"
}
publishing {
repositories {
maven {
url "$mavenUrl"
credentials {
username = "$mavenUser"
password = "$mavenPwd"
}
}
}
publications {
maven(MavenPublication) {
groupId "test"
artifactId "myArtifact"
version "1.0"
artifact ("/scratch/test/jars/myArtifact.jar")
pom.withXml {
def dependencies = asNode().appendNode("dependencies")
configurations.compile.allDependencies.each { dep ->
def depNode = dependencies.appendNode("dependency")
depNode.appendNode("groupId", dep.group)
depNode.appendNode("artifactId", dep.name)
depNode.appendNode("version", dep.version)
}
}
}
}
}
The generated pom will look like as shown below.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>myArtifact</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>dependency</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>dependency</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>dependency</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>dependency</groupId>
<artifactId>jettison</artifactId>
<version>1.3.8</version>
</dependency>
</dependencies>
</project>
I guess this might answer your question

Related

Gradle maven-publish dependency scope

I have a pretty simple Gradle Kotlin project.
plugins {
id 'application'
id 'maven-publish'
}
repositories { mavenCentral() }
dependencies {
compile 'com.google.guava:guava:31.1-jre' // 'compile' is deprecated
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'de.mabe'; artifactId = 'project1'; version = '1.0'
from components.java
}
}
}
When I start gradle publishToMavenLocal it generates a correct pom file with a correct dependency:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>compile</scope> <!-- this scope is important -->
</dependency>
Now I replaced the compile with implementation in the gradle script.
implementation 'com.google.guava:guava:31.1-jre'
Unexpectedly this changes the scope in the pom file from compile to runtime.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>runtime</scope> <!-- compile EXPECTED ?!?! -->
</dependency>
What do I have to do to get the previous pom back?
That is by design. The semantics of the implementation configuration is to declare dependencies that are internal to a module. By mapping it to the runtime scope of a Maven pom, it ensures that it doesn't leak onto the compilation classpath of consumers. This has a few advantages like being more free to swap out transitive depencies with less risk of affecting consuming modules, to make compilation faster and more.
If you need to make a transitive dependency part of the compile scope, i.e. expose it on the compilation classpath of consuming projects, you need to use the api configuration instead. This is available by applying the java-library plugin.
For example (Groovy DSL):
plugins {
id 'java-library'
id 'maven-publish'
}
dependencies {
implementation 'org.apache.commons:commons-math3:3.6.1' // <- Maps to the Maven runtime scope
api 'com.google.guava:guava:30.1.1-jre' // <- Maps to the Maven compile scope
}
publishing {
publications {
maven(MavenPublication) {
groupId = 'de.mabe'; artifactId = 'project1'; version = '1.0'
from components.java
}
}
}
You can read more about the separation between API and implementation in the Gradle user guide here.

How to use gradle feature variant dependecies in tests?

I am migrating a Maven library project to Gradle. The original project also has optional dependencies. I use the java-library plugin but moving the formerly optional dependencies to implementation results in runtime dependencies instead of compile. So I tried the gradle feature variants which results in the right dependencies in the pom.xml. But doing so results is failing test compile as the dependencies of the feature variant are missing on the test compile classpath!
Here is my current setup in build.gradle:
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'maven-publish'
sourceCompatibility = 1.8
java {
registerFeature('oSupport') {
usingSourceSet(sourceSets.main)
}
}
dependencies {
api 'my.compile:dep-a:1.0.0'
implementation 'my.runtime:dep-i:1.0.0'
oSupportApi 'my.optional:dep-o:1.0.0'
}
Let's assume there is a class O available from my.optional:dep-o. If I import O in any class in src/main/java it works perfectly. Also the dependencies are exported right to Maven (using gradle generatePomFileForMavenJavaPublication, see the dependencies from the generated pom.xml below). But any test in src/test/java using class O will not compile (import my.optional.O; creates error: package my.optional does not exist)
<dependencies>
<dependency>
<groupId>my.compile</groupId>
<artifactId>dep-a</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>my.rintime</groupId>
<artifactId>dep-r</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>my.optional</groupId>
<artifactId>dep-0</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
How to solve this? I know I could have used the nebula.optional-base plugin instead of the buildin Gradle feature variant but I would prefer the new gradle builtin support for optional dependencies instead.
PS: I use Java 8 and Gradle 5.6.2
This looks like a bug when the feature source set uses the main source set. Can you report on https://github.com/gradle/gradle/issues?
In the meantime, this should fix it:
configurations {
testCompileClasspath.extendsFrom(oSupportApi)
testRuntimeClasspath.extendsFrom(oSupportApi)
testRuntimeClasspath.extendsFrom(oSupportImplementation)
}
Really weird, I agree with #melix this seems to be a Gradle bug.
The following will fix it but should not be needed, imho:
dependencies {
api 'my.compile:dep-a:1.0.0'
implementation 'my.runtime:dep-i:1.0.0'
oSupportApi 'my.optional:dep-o:1.0.0'
testImplementation(project(":${project.name}")) {
capabilities {
requireCapability("${project.group}:${project.name}-o-support")
}
}
}
For this simplified setup with only one feature dependency could be replaced by testImplementation 'my.optional:dep-o:1.0.0' but for a general larger dependency list this approch avoids repetition of the dependencies as the extendsFrom solution of #melix.

Gradle 5: BOM dependencies are not written to pom file

I'm trying to use a BOM to build project A with Gradle 5. The pom file that's created does not correctly contain the BOM or the dependencies coming from it (see below). Moreover, Trying to build project B that depends on A fails on project A's pom.
When working with DependencyManagementPlugin (i.e. Not using Gradle 5 native support), everything works:
apply plugin:
io.spring.gradle.dependencymanagement.DependencyManagementPlugin
dependencyManagement {
imports {
mavenBom 'myGroup:infra-bom:1.0.+'
}
}
Creates this in project A's pom in a dependencyManagement block:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>myGroup</groupId>
<artifactId>infra-bom</artifactId>
<version>1.0.25</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
Trying to make it work with Gradle 5 isn't so lucky:
dependencies {
compile platform ('myGroup:infra-bom:1.0.+')
...
...
}
gradle dependencies shows correct versions from the BOM, but it creates this in project A's pom in the existing dependencies block:
<dependency>
<groupId>myGroup</groupId>
<artifactId>infra-bom</artifactId>
<version>null</version> // note the null here
<scope>compile</scope>
</dependency>
Which fails builds trying to use it.
Am I correctly using Gradle 5 native support for BOM? What does it include?

How to use pom type dependency in Gradle

I need to produce transitive dependency from my Java library which is of type pom. Here is an example on how I'm doing it:
plugins {
`java-library`
`maven-publish`
}
repositories {
// some maven repo
}
dependencies {
// This is POM type dependency:
api("org.apache.sshd:apache-sshd:1.6.0") {
exclude(group = "org.slf4j")
}
}
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
The problem with this configuration is that in the published pom.xml of my library the dependency is of type jar (by default) and declared like that:
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>apache-sshd</artifactId>
<version>1.6.0</version>
<!-- Should declare pom type -->
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>org.slf4j<groupId>
</exclusion>
</exclusions>
</dependency>
So when I try to use my published library from another project it fails as there is no such artifact as apache-sshd because it's type should be pom. So how to correctly publish desired dependency using Gradle?
Running on Gradle 5.3.1 with Kotlin DSL.
Try to use following construction for declaring dependency in Gradle
api("org.apache.sshd:apache-sshd:1.6.0#pom") {
exclude(group = "org.slf4j")
isTransitive = true
}
Looks like Gradle consumes all dependencies as jar type by default. And Maven plugin generates dependency section in pom file by using this extracted type. For pom dependency it is necessary to put correct value into type field of generated file. But if you put pom extension for your dependency, Gradle won't resolve transitive dependencies that are declared in this artifact. Set the value of transitive flag resolves this issue.
I have used it in the following way:
compile(group: "dependency_group", name: "dependency_name", version: "dependency_name", extension: "pom")
and if you want to exclude the transitive dependencies
add transitive flag and set it to false
compile(group: "dependency_group", name: "dependency_name", version: "dependency_name", extension: "pom"){
transitive = false
}

Uploading artifacts and downloading dependencies with Gradle or Maven from Nexus Repository

It has been 2 weeks since I've managed to setup Nexus OSS, Maven and then Gradle to be able to realize the following goals:
Upload files into a corporate Nexus Repository linking them together. So the files must keep a link in order to define a "package" populated by their groupId/artifactId/version.
Download a "package" (set of files) linked together by something (like a POM dependency schema).
Currently, I am able to use Maven and Gradle to upload ONE file on Nexus. Even if I specify dependencies in the POM file or in the build.gradle with Gradle, I am not able to download the file with all of its dependencies at once.
The goal here is only to be able to define a set of component ordered by group/name/version, and be able to download them all at once for one package. This is like version packaging management.
I have look 2 weeks and wasn't able to use Maven or Gradle to achieve these goals.
Please can anyone tell me EXACTLY how to use Maven or Gradle to achieve this work ?
This is the POM files i use to link artifactA with artifactB in Maven:
ArtifactA POM file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupA</groupId>
<artifactId>artifactA</artifactId>
<version>1.2.4</version>
<packaging>jar</packaging>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<distributionManagement>
<repository>
<id>nexus</id>
<name>Nexus Test Repository</name>
<url>http://localhost:7080/repository/content/repositories/releases/</url>
</repository>
</distributionManagement>
</project>
ArtifactB POM file:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupA</groupId>
<artifactId>artifactB</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>groupA</groupId>
<artifactId>artifactA</artifactId>
<version>1.2.4</version>
<type>jar</type>
</dependency>
</dependencies>
</dependencyManagement>
<distributionManagement>
<repository>
<id>nexus</id>
<name>Nexus Test Repository</name>
<url>http://localhost:7080/repository/content/repositories/releases/</url>
</repository>
</distributionManagement>
</project>
Once I have uploaded A then B using these commands:
mvn deploy:deploy-file \
-Dfile=artifactA_package.jar \
-Dpackaging=jar \
-DpomFile=pomA1.2.4.xml \
-Durl=http://localhost:7080/repository/content/repositories/releases/ \
-DrepositoryId=nexus
mvn deploy:deploy-file \
-Dfile=artifactB_package.jar \
-Dpackaging=jar \
-DpomFile=pomB1.0.0.xml \
-Durl=http://localhost:7080/repository/content/repositories/releases/ \
-DrepositoryId=nexus
The two files are well uploaded on my Nexus repository BUT, even if the artifactB POM file specify a dependency to A, when I download B using this script:
mvn dependency:get \
-Dartifact=groupA:artifactB:1.0.0:jar \
-DremoteRepositories=nexus::default::http://localhost:7080/repository/content/repositories/releases/
Maven only downloads B, but not A. And even if I use this command on B:
mvn dependency:resolve
It tells me something like "dependency: none".
So Nexus or Maven is not aware of the dependency from B to A.
Gradle also does not work to achieve my goal when I use this "build.gradle" file:
apply plugin: 'maven'
repositories {
maven {
url "http://localhost:7080/repository/content/groups/public/"
}
}
artifacts {
archives file('artifactB_package.jar')
}
dependencies {
archives files('artifactA_package.jar')
}
uploadArchives {
repositories {
mavenDeployer {
repository(url: "http://localhost:7080/repository/content/repositories/releases/") {
authentication(userName: "deployment", password: "deployment123")
}
pom.project {
groupId "groupA";
artifactId "artifactB";
version "1";
dependencies {
dependency {
groupId "groupA";
artifactId "artifactA";
version "1";
}
}
}
}
}
}
It only uploads B.
You are some sort of working around the system? I'm not sure what you try to achieve. But if you want to upload several artifacts at once during one build you need to create a multi-module project.
In maven there is a convention: one pom - one artifact
(this is more of a pirate guideline, its not true, for example source jars and javadoc jars can be in the same build - so there are 3 files uploaded for one pom).
The deploy command you use mainly tells maven to upload the artifact manually. The goal you need would be mvn deploy. This executes the build phases and uploads the created artifacts to nexus (using the configuration in the distributionManagement section. With a multi-module build this is done for every module. There is a guide and there are also several archetypes if you need examples (for ex. at appfuse).
I don't know how gradle handles this or what best practice they use.
There are example project for Maven, Gradle and others in the Nexus evaluation guide. The whole docs are at http://books.sonatype.com/nexus-book/reference/eval.html and the example projects are at https://github.com/sonatype/nexus-book-examples
Beyond that if you want to create a project that adds all dependencies together with Maven you can use at the assembly plugin e.g. at http://maven.apache.org/plugins/maven-assembly-plugin/descriptor-refs.html

Resources