How can I download and reference a single artifact in Gradle? - gradle

I am creating a Gradle task in which I want to download and reference the jar file for an artifact that exists in a Maven repository. Specifically, I plan to call an external script through the Gradle exec task using the location of that jar as an argument to the script. Using the exec task is straightforward; what I am trying to figure out is how to acquire the location of artifact's jar file on the filesystem.
As a simple yet concrete example, assume I have the following files in my Gradle project directory:
build.gradle:
task myScript(type: Exec) {
executable './my_script.sh'
args jarFilePath // How do I get this value?
}
my_script.sh:
#!/usr/bin/env bash
echo "Jar file location: $1"
How can I get the file path to the artifact jar, downloading it from the remote repository if necessary? For instance, it should be downloaded if it's not in my local artifact cache, or it's an updated snapshot version.

Let's assume that the jar file we want is guava-26.0-jre.jar. The following build.gradle file will retrieve the artifact (if necessary) and provide the file location as an argument to the script:
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
repositories {
mavenCentral()
}
configurations {
myArtifact
}
dependencies {
myArtifact group: 'com.google.guava', name: 'guava',
version: '26.0-jre', transitive: false
}
task myScript(type: Exec) {
Path artifactPath =
temporaryDir.toPath().resolve('guava.jar').toAbsolutePath()
doFirst {
Files.copy(configurations.myArtifact.singleFile.toPath(), artifactPath,
StandardCopyOption.REPLACE_EXISTING)
}
executable './my_script.sh'
args artifactPath.toString()
}
This creates a custom configuration named "myArtifact" with a single dependency on guava-26.0-jre. By marking it as transitive: false none of the artifact's dependencies will be included in the configuration, nor will they be downloaded if they do not yet exist in the local artifact cache.
The call to configurations.myArtifact.singleFile returns a java.io.File reference to the artifact's jar file. This getSingleFile() method also ensures that there is exactly one file in the collection, throwing an IllegalStateException if there are zero or more than one. This guards against referencing the wrong artifact if the configuration gets incorrectly misconfigured in the future to having multiple artifacts (e.g. if somebody removes the "transitive" option from our script).
The toAbsolutePath() method ensures that the path to the script is absolute, and therefore does not need to be resolved relative to any particular directory.
Note that this is copying the artifact to the task's temporary directory in a doFirst block, rather than referencing the file's path directly. This is so that the artifact doesn't need to be resolved when loading the script. Otherwise, the artifact would need to be resolved (and possibly downloaded) even when doing unrelated tasks — including such basic tasks as clean and tasks — causing a build failure if the artifact could not be resolved.
The StandardCopyOption.REPLACE_EXISTING option is used to overwrite any previous jar file from previous builds.

The following snippet with a copy task copyDependencies works for both single and multiple dependencies. You can define any other tasks (e.g. type Exec) that depend on this task.
configurations {
web {
transitive = false
}
}
dependencies {
web "com.foo:foo-messaging:$version:#war",
"com.foo:foo-ng:$version:#war"
}
task copyDependencies(type: Copy) {
from configurations.web
into "$buildDir/web"
}

Related

Get full artifact name generated by gradle

I'm publishing an artifact using maven-publish.
When I'm pushing a release artifact, the resulting artifact version is the same as the project version itself. For example if gradle.properties has version=1.2.3, the artifact would be something like foo-1.2.3.zip.
When I run a SNAPSHOT publish, the resulting artifact will include additional information in the version. For example version=1.2.4-SNAPSHOT gives foo-1.2.4-20220427.094127-1.zip. The additional information would appear to be the time and date to avoid clashes, I assume.
Is there anyway I can access this full artifact name in my gradle scripts?
If you don't specify the artifact names explicitly like this for example
publishing {
publications {
maven(MavenPublication) {
groupId = 'org.gradle.sample'
artifactId = 'library'
version = '1.1'
from components.java
}
}
}
Then gradle will use the defaults that are taken from your build.gradle, gradle.properties and the module directory names.
I'm not sure if there's a way to configure the mavenPublish plugin to print out the maven artifact upon publishing, but you can look in your ~/.m2 directory to see what exactly has been published to your local if you run ./gradlew publishToMavenLocal
I'm also thinking you could create a custom task to print out the group, artifact and version, something like this in the build.gradle of your project you are publishing:
project.task('getMavenCoordinates') {
doLast {
println "Maven Artifact: ${project.group}:${project.name}:${project.version}"
}
}
project.task("publishToMavenLocal").dependsOn("getMavenCoordinates")
If you wanted to configure the task to run after a different maven-pubish task, just change it accordingly! But something like that could help achieve what you want.

Downloading only a POM file from Maven/JFrog repository

I would like to fetch/download only! a POM file from a properly published (with metadata) artifact.
It is easy to write a task downloading jars only
task getArtifacts(type: Copy) {
from configurations.myConf
into myWorkingDir
}
and setting the transitive flag to false for myConf(iguration).
But how to download the related POM files only!
The real reason for this is: I obfuscate the downloaded jars, then deploy them back to the repository as an obfuscated version. But I would like provide the pom metadata for the obfuscated artifacts … just using artifacts.add(xxx.pom) before publicizing.
Maybe you know any other idea how to implement the publication of obfuscated jars with metadata in Gradle?
You can use an ArtifactResolutionQuery to manually resolve artifacts of a specific type:
def result = dependencies.createArtifactResolutionQuery()
.forModule('group', 'name', 'version')
.withArtifacts(JvmLibrary, MavenPomArtifact)
.execute()
for (component in result.resolvedComponents) {
component.getArtifacts(MavenPomArtifact).each { println it.file }
}
I'm not sure if the actual artifact will be resolved with this method, too, but you may just use the path of the POM file. Please note, that you should call this functionality during the execution phase, either by defining a new task type with a #TaskAction or by using a doFirst/doLast closure.

How can I get gradle to populate jars with dependency metadata?

So if I build a jar in Maven, say for example jackson-core-2.5.1.jar, I find the following in the artifact:
META-INF/maven/
META-INF/maven/com.fasterxml.jackson.core/
META-INF/maven/com.fasterxml.jackson.core/jackson-core/
META-INF/maven/com.fasterxml.jackson.core/jackson-core/pom.properties
META-INF/maven/com.fasterxml.jackson.core/jackson-core/pom.xml
Gradle, however, does not seem to create this data. Problem is that we have several components of our build, including a parent project, that aren't hosted in the same SCM location. For our large and complex build, how would Gradle know that a locally built artifact in one SCM location depends on a locally built artifact in another, if there's no metadata? What is the Gradle way to manage this situation?
Repositories contain a separate copy of pom.xml. It usually lives next to the JAR file in the physical structure on the disk. This applies to binary repositories like Nexus or Artifatory and also to your local Maven repository (under $HOME/.m2/repo).
If for some reason you want to copy the behavior of Maven you can tell Gradle to do create those files. We use this customization in subprojects closure that configures our multi-project Gradle build.
jar {
// until we have better solution
// https://discuss.gradle.org/t/jar-task-does-not-see-dependency-when-using-maven-publish/11091
if (project.tasks.findByName('generatePomFileForMavenJavaPublication')) {
dependsOn 'generatePomFileForMavenJavaPublication'
} else {
project.tasks.whenTaskAdded { addedTask ->
if (addedTask.name == 'generatePomFileForMavenJavaPublication') {
project.tasks.jar.dependsOn 'generatePomFileForMavenJavaPublication'
}
}
}
into("META-INF/maven/$project.group/$project.archivesBaseName") {
/*
from generatePomFileForMavenJavaPublication
*/
from new File(project.buildDir, 'publications/mavenJava')
rename ".*", "pom.xml"
}
}
It would be simpler if there wasn't a problem with generatePomFileForMavenJavaPublication task being created lazily sometimes. And you need to check how to create your properties file. I guess you can dump properties from a running Gradle process there.

Gradle: Applying from external gradle file which is downloaded as artifact

I have a test lrg.gradle file which is supposed to do the following
lrg.gradle
dependencies {
classpath group: '<group path>', name: '<gradle file>', version: "${Version}",
}
apply from <above downloaded gradle file>
I am looking on how to apply from the above downloaded gradle file. Where will the gradle file downloaded and which variable i need to refer to access the cached location. Assuming it is stored in GRADLE_USER_HOME/caches.
Basically we want to have this in all Test LRG files so that all common tasks and dependencies can be applied from common artifact gradle file. So that they can be used like dependent tasks for the tasks defined.
You can apply from a URL like so:
apply from: "http://artifactory.url/maven/repo/$group/$name/$version/common.gradle"
That should allow you to avoid declaring a dependency on the common.gradle file.

Uploading multiple existing JARs to Maven repository with Gradle

I need to implement a Gradle task that will upload multiple existing JARs to the Maven repository.
The tricky part is that list of JARs is not known in advance. The way I want it to work is to dowload certain ".tar.gz" file first, untar it, then scan for JARs and upload them with some sort of naming convention (like use JAR names as artifactId's and version of the .tar.gz as version).
What would be the easiest way to do that with Gradle? Currently it is a simple bash script which searches for JARs and runs Maven deploy:deploy-file for each of them, but I need to incorporate that functionality in gradle build script.
Ideally, I need task like "deployJars" that would upload everything and depend on "downloadTarGz" task.
Update:
How about uploading POMs without any JAR attached to it? I need to generate several POMs that will depend on these dynamically detected JARs and upload them, too. "artifacts" requires me to specify file for upload.
To upload a jar via gradle you must declare that jar as a publish artifact and add it to a specific configuration using the artifacts closure:
apply plugin:'maven'
configurations{
allJars
}
artifacts{
allJars file("path/to/jarFile.jar")
}
now you can configure the dynamically created uploadAllJars task:
uploadAllJars {
repositories {
mavenDeployer {
repository(url: 'http://localhost:8081/artifactory/acme') {
authentication(userName: 'admin', password: 'password');
}
}
}
The problem is that you want to upload multiple artifacts. To achieve that you need some more dynamic in your build script. The dynamic creation of publishartifacts for all discovered jars can be wrapped in a task. In my example the discoverAllJars task simply looks in a specified folder for jar files. Here you need to implement your own logic to lookup the jars in your tgz archive.
group = "org.acme"
version = "1.0-SNAPSHOT"
task discoverAllJars{
ext.discoveredFiles = []
doLast{
file("jars").eachFile{file ->
if(file.name.endsWith("jar")){
println "found file ${file.name}"
discoveredFiles << file
artifacts{
allJars file
}
}
}
}
}
To be able to upload multiple artifacts within the uploadAllJars task you have to use pom filter. For details about pom filter have a look at the gradle userguide at http://www.gradle.org/docs/current/userguide/maven_plugin.html#uploading_to_maven_repositories
Since we moved the configuration of the published artifacts into the execution phase of gradle we must configure the uploadAllJars in the execution phase too. Therefore I'd create a configureUploadAllJars task. Note how we reference the jar files discovered by using 'discoverAllJars.discoveredFiles':
task configureUploadAllJars{
dependsOn discoverAllJars
doLast{
uploadAllJars {
repositories {
mavenDeployer {
repository(url: 'http://yourrepository/') {
authentication(userName: 'admin', password: 'password');
}
discoverAllJars.discoveredFiles.each{discoveredFile ->
def filterName = discoveredFile.name - ".jar"
addFilter(filterName) { artifact, file ->
file.name == discoveredFile.name
}
pom(filterName).artifactId = filterName
}
}
}
}
}
}
Now you just need to add a dependency between uploadAllJars and configureUploadAllJars:
uploadAllJars.dependsOn configureUploadAllJars
This example uses the same group and version for all discovered jar files and the jar name as artifactId. you can change this as you like using the pom filter mechanism.
hope that helped,
cheers,
René

Resources