How to publish programmatically manipulated (e.g. instrumented) jars in Gradle? - java

I have a multi-project build. Sub-project CoolApp depends on sub-project CrazyJar. Project CrazyJar has no sources: it uses a tool to perform byte code manipulation on an existing jar derived from non-Gradle project BigProject and wishes to publish the resulting manipulated jar to be depended on by CoolApp. It needs to publish this as a local Maven package.
Since it seems the "maven-publishing" plugin only supports "java" and "web" projects, I apply the "java" plugin to CrazyJar. But CrazyJar has no sources! I end up publishing an empty artifact. Is there any way I can mangle this to work as though it were a normal Java project? If not, anything else I might do?

Hmm, yeah, I misunderstood my problem slightly. Well, here was my solution anyway (based mostly off documentation for the "maven-publish" plugin):
Root project
subprojects {
repositories {
maven {
url "${rootDir}/repo"
}
}
}
CrazyJar project
apply plugin: "maven-publish"
...
task createCrazyJar(type: Exec) {
...
}
publish {
dependsOn "createCrazyJar"
}
publishing {
publications {
mavenJava(MavenPublication) {
artifact createCrazyJar.outputs.getFiles().getSingleFile()
}
}
repositories {
maven {
url project.repositories.maven.url
}
}
}
CoolApp project
dependencies {
compile group: "coolproject", name: "crazyjar", version: project("CrazyJar").version
}

Related

Gradle maven publish plugin fails in a multi-module project with error "Artifact wasn't produced by this build"

I have a multi-module project which supports maven and gradle builds hence it contains pom.xml files along side with build.gradle. I'm working on a demo and I would like to show how to build and deploy to nexus the same project using either gradle or maven. That's why I have two different build systems in case you wonder why.
You may see the project structure below.
You may look into the code here.
I've configured the gradle maven-publish plugin in order to publish all modules to my local nexus repository however when I run gradle publish I hit this error:
Execution failed for task ':publishMavenJavaPublicationToMavenRepository'.
> Failed to publish publication 'mavenJava' to repository 'maven'
> Artifact machinery-config-0.0.1.jar wasn't produced by this build.
The issue is related with having the publishing section within $rootDir/build.gradle.
It's confusing maven-publish somehow which is trying to publish an artifact that doesn't exist machinery-config-0.0.1.jar, machinery-config is the name of the rootProject.
One workaround could be to remove the publishing section from the rootProject and duplicate it in the sub-projects. I don't like that approach cuz I will end up with a lot of duplicated code.
Could you point me out a better way to use maven-publish within a multi-module project ?
After some digging I realized that I had two issues:
publishing section was outside of subprojects section, then gradle tried to deploy an artifact using rootProject.name ignoring the included modules.
group & version properties were outside subprojects therefore deployed artifacts had undefined as version number, e.g machinery-config-core-undefined.jar
In order to fix issue number two I had moved group & version into subprojects section.
My build also produces a BOM artifact hence I need two publications from components.java and from components.javaPlatform, so I wrote this script gradle/ext/publish-common.gradle which exports two functions I will use later on both publications to keep code duplication at bay.
def pom(it) {
it.licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
it.developers {
developer {
id = 'eljaiek'
name = 'Eduardo Eljaiek'
email = 'eduardo.eljaiek#gmail.com'
}
}
it.scm {
connection = 'scm:git:git://github.com/eljaiek/machinery-config.git'
developerConnection = 'scm:git:git#github.com:eljaiek/machinery-config.git'
url = 'https://github.com/eljaiek/machinery-config'
}
}
def nexusRepository(it) {
it.url = version.endsWith('SNAPSHOT') ? project.nexusSnapshotsUrl : project.nexusReleasesUrl
it.credentials {
username project.nexusUser
password project.nexusPasswd
}
}
ext {
pom = this.&pom
nexusRepository = this.&nexusRepository
}
I had added from components.javaPlatform publication into machinery-config-dependencies/build.gradle in order to deploy the BOM artifact
apply plugin: 'maven-publish'
apply from: "$rootDir/gradle/ext/publish-common.gradle"
publishing {
publications {
mavenBom(MavenPublication) {
from components.javaPlatform
pom { x -> pom(x)}
}
}
repositories {
maven { x -> nexusRepository(x) }
}
}
apply from: "$rootDir/gradle/ext/publish-common.gradle" will aplly the script I wrote, this is required in order to import pom() and nexusRepository() functions into my build script.
To deploy from components.java artifacts I had added a publishing section in subprojects under if (it.name != 'machinery-config-dependencies') statement into $rootDir/build.gradle
apply plugin: 'ru.vyarus.pom'
apply from: "$rootDir/gradle/ext/publish-common.gradle"
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
pom { x -> pom(x)}
}
}
repositories {
maven { x -> nexusRepository(x) }
}
}
I had used Gradle POM plugin instead of maven-publish. It provides maven's dependencies declaration simplicity and implicitly applies maven-publish plugin.
See the code here
References
Extract common methods from Gradle build script
The Java Platform Plugin

How to prevent gradle from downloading SNAPSHOT jar

I am using a dependency module-x which has may or may not have SNAPSHOT version of trivial/other dependencies. When I build the application I wanted to make sure that all the trivial/other dependencies are of release type and not SNAPSHOT as SNAPSHOT keeps changing.
build.gradle file
dependencies {
implementation 'org.my-group-x:module-x:1.2'
}
it downloads a bunch of dependencies and it may have multiple dependencies which are of SNAPSHOT typed
module-y:2.0-SNAPSHOT.jar
module-z:3.1-SNAPSHOT.jar
module-k:2.7-SNAPSHOT.jar
How I can make sure it is rejected and not added to the application? Also i dont know the dependencies to exclude it specifically.
You can exclude dependencies in build tools, see:
https://docs.gradle.org/current/userguide/dependency_downgrade_and_exclude.html#sec:excluding-transitive-deps
You can also configure the Maven repositories used in Gradle, see:
https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:repository-content-filtering
Here is an example to use only release versions or only snapshot version:
repositories {
maven {
url "https://repo.mycompany.com/releases"
mavenContent {
releasesOnly()
}
}
maven {
url "https://repo.mycompany.com/snapshots"
mavenContent {
snapshotsOnly()
}
}
}

Gradle custom plugin jar with dependencies

I'm trying to build a jar for a custom gradle plugin to be used by other gradle projects. I'm using java to write the plugin. I'm having a problem including dependencies in my jar. If I build the jar using the below build.gradle
plugins {
id 'groovy'
}
repositories{
mavenCentral()
}
dependencies {
compile gradleApi()
compile localGroovy()
compile 'com.google.guava:guava:27.0-jre'
testCompile 'junit:junit:4.12'
//compile 'org.apache.commons:commons-lang3:3.8.1'
}
group = 'com.mine'
version = '1.0'
I get a NoClassDefFound exception for guava classes when applying the plugin on a project. If I include a task to create a jar with dependencies like below in the build.gradle
jar {
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it)}
}
}
It says Plugin with Id 'my-plugin' not found. How do I include dependencies in a gradle plugin jar?
Your plugin project should be configured as a standalone Plugin project and then published to a maven repository, which will make dependencies resolution work; there is good documentation about writing custom plugin here, specially the following part : using Gradle plugin development plugin
There is also a good example of writing/publishing/consuming a custom Plugin in the Gradle examples here : https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/plugins (see the two subprojects publishing and consuming )
And here is a working example with a plugin that has dependency on external library (commons-lang for example):
Plugin project
build.gradle
plugins {
id 'java-gradle-plugin'
id 'groovy'
id 'maven-publish'
}
group 'org.gradle.sample.plugin'
version '0.1'
// pugin metadata configuration
gradlePlugin {
plugins {
myplugin {
id = "org.gradle.sample.plugin.myplugin"
implementationClass = "org.gradle.sample.plugin.MyPlugin"
}
}
}
// publish to local maven repo for testing
publishing {
repositories {
maven {
url "../repos/maven-repo"
}
}
}
// repo for dependences resolution
repositories{
jcenter()
}
// dependencies of this plugin
dependencies {
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
}
Plugin implementation : src/main/groovy/org/gradle/sample/plugin/MyPLugin.groovy
package org.gradle.sample.plugin
import org.apache.commons.lang3.StringUtils
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyPlugin implements Plugin<Project> {
#Override
void apply(final Project project) {
println "Applying custom plugin... "
project.tasks.create('testPlugin'){
doLast{
println " custom plugin task executing."
println "Result: " + StringUtils.capitalize("stringtotest")
}
}
}
}
Build and publish this plugin ./gradlew publish : the plugin jar and "plugin marker artefacts" will be published to local maven repo in ../repos/maven-repo
Consumer project
build.gradle
plugins {
id 'java'
// import/apply your custom plugin
id 'org.gradle.sample.plugin.myplugin' version '0.1'
}
group 'org.gradle.sample.plugin'
version '0.1'
repositories{
maven {
url "../repos/maven-repo"
}
jcenter()
}
To test the plugin, try to execute the plugin task testPlugin
> Task :testPlugin
custom plugin task executing.
Result: Stringtotest
Sorry to add this as an answer but I don't have enough points to comment (yes it is a bit late in coming but I found this in a search and it came so close, maybe this will help someone else).
The answer by #M.Ricciuti is correct, just missing one file, namely a settings.gradle in the referencing project (not the plugin) directory:
pluginManagement {
repositories {
maven {
url '../repos/maven-repo'
}
gradlePluginPortal()
ivy {
url '../repos/ivy-repo'
}
}
}
Many thanks, I have tried many things that didn't work before finding this, even the examples by gradle didn't work (or more likely I didn't run them correctly). Anyway I merged what I saw in the answers with M. Ricciuti's answer and saw that file in the sample.
My complete project is at https://github.com/reddierocket/sampleGradlePlugin
The readme has instructions to run it. (Note I did not include the wrapper but I am using gradle version 5.3.1.)

Including Java library built with Gradle throws NoClassDefFoundError

I am writing a Java library and I would like to build the library with Gradle and then test it from a local test project.
I would prefer using Gradle 3.3 for my objective.
The library should be built for Java5 and higher.
So far my build.gradle looks like this:
plugins {
id 'jvm-component'
id 'java-lang'
}
repositories {
mavenCentral()
}
model {
components {
main(JvmLibrarySpec) {
sources {
java {
dependencies {
module 'commons-codec:commons-codec:1.10'
module 'org.apache.httpcomponents:httpcore:4.4.6'
module 'org.apache.httpcomponents:httpclient:4.5.3'
}
}
}
api {
exports 'io.simplepush'
}
targetPlatform 'java5'
}
}
}
The source code of the library is located in src/main/java/io/simplepush/Notification.java and depends on the dependencies stated in the build.gradle file.
Building the library with ./gradlew build works fine and generates build/jars/main/jar/main.jar.
However when I run a test project from IntelliJ (after including main.jar into the test project), I get the following runtime error:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/HttpEntity.
It seems like the test project does not know about the runtime dependencies needed by my library.
I am not sure on what is the correct way to tell the test project about the dependencies of my library.
I do not want a fat jar which includes all dependencies.
Listing all dependencies in the test project itself is also not an option.
Preferably I want the library itself to tell the test project about which dependencies it needs.
The library jar which you have created does not contain any dependency information which the IDE/Gradle can then resolve to be able to compile/run the test project. I see that you are using the maven central repository so what you need to do is to publish your library to your local maven repository and in the test project just add a dependency information (no just plain jar file).
So in both library and test project build.gradle add a maven local repository config.
repositories {
mavenLocal()
mavenCentral()
}
And now you need to publish the library to local repository. As you are using the gradle 3.3 you can use the Maven Publishing.
So in the library build.gradle add a maven publishing information.
publishing {
publications {
maven(MavenPublication) {
groupId 'io.simplepush'
artifactId 'project1-sample'
version '1.1'
from components.java
}
}
}
Gradle “maven-publish” plugin makes this easy to publish to local repository automatically creating a PublishToMavenLocal task.
So you can just run
gradle publishToMavenLocal
Which will publish your library with all the dependency information into local maven repository.
And then you just need to add a library information to you test projects build.gradle
dependencies {
// other dependencies .....
module 'io.simplepush:project1-sample:1.1'
}
I solved it by changing several things.
Thanks to #Babl for pointing me in the right direction.
My new library build.gradle looks like this:
plugins {
id 'java'
id 'maven-publish'
}
sourceCompatibility = 1.5
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile 'commons-codec:commons-codec:1.10'
compile 'org.apache.httpcomponents:httpcore:4.4.6'
compile 'org.apache.httpcomponents:httpclient:4.5.3'
}
publishing {
publications {
maven(MavenPublication) {
groupId 'io.simplepush'
artifactId 'project1-sample'
version '1.1'
from components.java
}
}
}
Now I can push the library to the local maven repository with ./gradlew publishToMavenLocal.
The build.gradle of the test project uses the application plugin and defines a main class (which is Hello in my case). Then I can run ./gradlew installDist to generate an executable file (see Application plugin docs) which puts all dependencies in the classpath and runs just fine.
group 'com.test'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'application'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile 'io.simplepush:project1-sample:1.1'
}
mainClassName = "Hello"
This specify what repositories to check to fetch the dependencies from
repositories {
mavenCentral()
}
Therefore, anything that is in the dependecies{} will be fetched from those above.
If the test project is not coupled with the library project, (#RaGe example) new test project needs to know where to take the dependency from - you need to publish it, using preferred method.
After that, your new test project needs to specify the library with the preferred configuration (compile...runtime etc) in the build.gradle dependencies{}
After that depending on IDE you need to refresh the classpath and download the dependency from the specified before repository, the transitive dependencies specified in the library dependency (in this case) will get fetched from test projects repositories{}
Library build.gradle
repositories {
mavenCentral()
}
dependencies {
module 'commons-codec:commons-codec:1.10'
module 'org.apache.httpcomponents:httpcore:4.4.6'
module 'org.apache.httpcomponents:httpclient:4.5.3'
}
test project build.gradle
repositories {
mavenCentral() repository to fetch transitives
mavenLocal() or any other repo that you published the library to
}
dependencies {
pref-conf librarygroup:name:version
}
You can use idea or eclipse plugin in gradle for gradle idea or gradle eclipseClasspath tasks to refresh it with your freshly added dependencies.
With this solution, you should not need to pack the transitive dependencies within the library,
PS. I am just confused after you said you want executable jar.

How to migrate gradle publication script to publish OSS library to Bintray's JCenter instead of Sonatype's Maven Central

I am the mantainer of the Java 8 java.time JSP tags library. I have little experience publishing libraries on my own. For the publication of this library I did some research and ended with a gradle build script that you can check in GitHub. The process is a bit clunky but it works in the end.
There seem to be a general understanding that the jcenter() repository is gaining a lot of attention. Probably because of android. Anyway I saw an encouraging blog post and decided to give it a try and migrate that library to JCenter publishing insted of Maven Central. Should be easy.
It is not, for me at least. Probably my fault as my knowledge of Maven, artifacts and all that stuff is poor. Anyway I gave it some hours of research and come up with a new gradle build to publish to my Bintray maven repository. Which, if I'm not wrong, is the first step towards publishing to JCenter.
This is what I have so far:
plugins {
id "com.jfrog.bintray" version "1.6"
}
apply plugin: 'java'
apply plugin: 'maven-publish'
group = 'net.sargue'
version = '1.1.2'
sourceCompatibility = 1.8
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'
repositories {
jcenter()
}
configurations {
testCompile.extendsFrom compileOnly
}
dependencies {
compileOnly 'javax.servlet:javax.servlet-api:3.0.1'
compileOnly 'javax.servlet.jsp:javax.servlet.jsp-api:2.2.1'
compileOnly 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
testCompile 'junit:junit:4.12'
testCompile 'org.springframework:spring-test:4.1.7.RELEASE'
}
jar {
manifest {
attributes 'Implementation-Title': 'Java 8 java.time JSP tags',
'Implementation-Version': version
}
}
task javadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc
}
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allSource
}
publishing {
publications {
MyPublication(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
artifactId 'java-time-jsptags'
pom.withXml {
asNode().children().last() + {
resolveStrategy = Closure.DELEGATE_FIRST
name 'Java 8 java.time JSP tags'
description 'JSP tag support for Java 8 java.time (JSR-310)'
url 'https://github.com/sargue/java-time-jsptags'
scm {
connection 'scm:git:git#github.com:sargue/java-time-jsptags.git'
developerConnection 'scm:git:git#github.com:sargue/java-time-jsptags.git'
url 'git#github.com:sargue/java-time-jsptags.git'
}
licenses {
license {
name 'The Apache License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'sargue'
name 'Sergi Baila'
email 'sargue#gmail.com'
}
}
}
}
}
}
}
bintray {
user = BINTRAY_USER
key = BINTRAY_KEY
publications = ['MyPublication']
pkg {
repo = 'maven'
name = 'java-time-jsptags'
licenses = ['Apache-2.0']
vcsUrl = 'https://github.com/sargue/java-time-jsptags.git'
version {
name = project.version
desc = 'Java 8 java.time JSP tags'
gpg {
sign = true
passphrase = BINTRAY_GPG
}
}
}
}
You can find the result of the latest publication on my public Bintray maven repository. You can compare it to the files for the same version currently available on Maven Central.
Congratulations if you are reading this so far, because I haven't formulated ANY question yet. Sorry about that.
My questions:
Is the gradle build script correct and the proper/canonical way? Given that the library is quite simple I found the build script huge and clunky. It is supposed to be easier and it even has a gradle plugin. But the new script is longer than the maven central one.
What about the *.md5 and *.sha1 files? Will be generated by JCenter, Maven Central, the sync proces... or should I do it?
There is some way of testing all these without publishing an actual version of the library given that there is no unpublish capabilities on the repositories? (and for a good reason, eh? leftpad anyone?).
First, great job figuring it out. It looks good and works good.
It's bigger than the other one not because you use Bintray instead of Central, but because you use maven-publish plugin instead of maven, and being more powerful, the config is a bit more verbose. You can use Bintray (and the bintray plugin) with both maven and maven-publish, whatever you prefer.
Re testing it – you can always run a test build against your private repository (click in the Set Me Up button to get instructions on how to set up your Maven and/or Gradle to resolve from it).
Another validation will be syncing to Maven Central. It will fail if something is wrong with your package metadata.
Re md5 and sha1, we don't see a reason to store computable metadata as files on a modern distribution platform, but we send them to Maven Central when we sync.

Categories