Gradle plugin download dependency programmatically - gradle

curently I am writing a gradle plugin and I need to add and download a maven dependency programmatically in a given task.
I evaluated DependencyHandler and ArtifactResolutionQuery but I can't figure out where and how to add a Dependency and resolve it in mavenCentral repository
Similarcoding for maven does look rather easy
Artifact artifact = artifactFactory.createArtifactWithClassifier(groupId, artifactId, version, type, classifier);
artifactResolver.resolve(artifact, remoteRepositories, localRepository);
So i guess/hope there is a similiar easy way in gradle and I am just not seeing it
Regards
Mathias
Update 1:
So here is some of the stuff I tried,wildly c&p from different tries. it is worth saying that the dependency I want to download has the classifier ZIP, so normal in my build.gradle I write
compile 'group:artifact:version#zip
to get the file
ComponentIdentifier componentIdentifier = new DefaultModuleComponentIdentifier("com.sap.cloud",
"neo-java-web-sdk", "3.39.10");
System.out.println("CompIdentifier = " + componentIdentifier.getDisplayName());
//getProject().getDependencies().add("compile", componentIdentifier.getDisplayName());
Configuration configuration = getProject().getConfigurations().getByName("compile");
org.gradle.api.artifacts.Dependency dep2 = new DefaultExternalModuleDependency("com.sap.cloud",
"neo-java-web-sdk", "3.39.10");
boolean depList = configuration.getDependencies().add(dep2);
//
configuration.forEach(file -> {
getProject().getLogger().lifecycle("Found project dependency # " + file.getAbsolutePath());
});
Set<File> files = configuration.resolve();
for (File file2 : files) {
System.out.println("Files: " + file2.getName());
}
DependencyHandler dep = getProject().getDependencies();
ComponentModuleMetadataHandler modules = dep.getModules();
ArtifactResolutionQuery a = getProject().getDependencies().createArtifactResolutionQuery()
.forComponents(componentIdentifier).withArtifacts(MavenModule.class, SourcesArtifact.class);
ArtifactResolutionResult r = a.execute();
Set<ComponentArtifactsResult> set = r.getResolvedComponents();
Set<ComponentResult> c = r.getComponents();

I think the simplest way to download a dependency programmatically in a Gradle plugin is the same as doing it in a build script. Just create a new configuration, add your dependency and resolve the configuration. Watch the example below how this works in Java (the preferred language for Gradle plugins):
Configuration config = project.getConfigurations().create("download");
config.setTransitive(false); // if required
project.getDependencies().add(config.getName(), "com.sap.cloud:neo-java-web-sdk:3.39.10#zip");
File file = config.getSingleFile();
For this example, the name of the configuration ("download") can be any string not already used as configuration name (like compile or runtime). Since the configuration will be resolved afterwards, you must use another name whenever you reuse this code snippet (or if you call it multiple times).

Related

How to manually download file from Maven repository in Gradle

We have a huge monolith application which is build by multiple tools (shell scripts, Ant and Maven). The build process is quite complex:
a lot of manually steps
hidden dependencies between Ant targets
different steps must be executed depending on the used Operating System
We decided to simplify it by creating Gradle scripts which wraps all this logic (it is quite impossible to fix it, so we create a wrapper which standardize the way of executing all the logic). We have to download some files from the Maven repository, but we cannot use the dependencies syntax:
we don't need to always download all files
the versions of the downloaded artifacts are dynamic (depends on configuration located in completely different place)
we need a path to the downloaded files (e.g. we have to unpack an artifact distributed as zip)
How we can achieve it?
The easiest way to achieve it is to create a dynamic configuration with dependencies, and next resolve it. The resolve method returns paths to the dependencies on the local disk. It is important to use a unique name for every configuration. If not, executing the logic twice would fail (cannot overwrite the configuration with XYZ name).
Here is an example method which returns a path to an artifact. If the artifact is already available in the Gradle cache it won't be downloaded for the second time, but of course the path will be returned. In this example all artifacts are downloaded from Maven Central.
Method:
ext.resolveArtifact = { CharSequence identifier ->
def configurationName = "resolveArtifact-${UUID.randomUUID()}"
return rootProject.with {
configurations.create(configurationName)
dependencies.add(configurationName, identifier)
return configurations.getByName(configurationName, {
repositories {
mavenCentral()
}
}).resolve()[0]
}
}
Usage:
def jaCoCoZip = resolveArtifact('org.jacoco:jacoco:0.8.6')
def jaCoCoAgent = resolveArtifact('org.jacoco:org.jacoco.agent:0.8.6')

Custom configuration and resolving only compile dependencies

i'm currently writing a small plugin but i'm stuck when i want to get a list of all dependencies that are used.
what i'm doing
inside the plugin i create a new configuration
def config = project.configurations.create(sourceSet.getTaskName('foo', 'bar'))
in the build.gradle that uses the plugin i add some dependencies to this configuration
dependencies {
fooTestBar(project(':module'))
}
and in module/build.gradle i have
plugins {
id 'java-library'
}
dependencies {
implementation('org.apache.commons:commons-collections4:4.4')
api('org.springframework:spring-context:5.2.11.RELEASE')
}
when i now do the following in the plugin
List<ResolvedArtifact> = config.resolvedConfiguration.firstLevelModuleDependencies.allModuleArtifacts.flatten()
i get the artifacts from both declarations in :module, but what i'm interested in is only the api dependency, means the one that is also used when compiling the project
it looks like the entire configurations is treated as a runtime configuration, so i have all artifacts including the transitive ones from both declarations, instead of only the api one including the transitive ones from api
until now i was not able to find any way to see if a resolved dependency / artifact is of type api which i do not want to have in my result list
i had to add the attribute for the usage
config.attributes {
attribute( Usage.USAGE_ATTRIBUTE, objects.named( Usage, Usage.JAVA_API ) )
}
https://docs.gradle.org/current/userguide/variant_model.html
https://docs.gradle.org/current/userguide/variant_attributes.html
thanks Chris Doré on https://discuss.gradle.org/t/custom-configuration-and-resolving-only-compile-dependencies/38891

Resolve a dependency dynamically inside a gradle task

I am trying to build a gradle plugin, which does the following:
As part of one its tasks, it creates a new configuration
It adds a DefaultExternalModuleDependency to this configuration - more specifically, it constructs a dependency to the application server zip file (available on Nexus). This information can be overridden by the invoking project as well.
Tries to resolve this newly added dependency and then unpacks the file to a local folder
All of this was working well when I had the details hard coded in a build file, but it looks like adding dependencies as part of a task are not treated the same way as having that information available at the parsing time.
So my question is, how do I get the project to reload the configurations / dependencies?
The code looks like the following:
#TaskAction
void installAppserver() {
Dependency dependency = new DefaultExternalModuleDependency(group,name,version)
Configuration configuration = project.configurations.detachedConfiguration(dependency)
configuration.setTransitive(false)
configuration.files.each { file ->
if (file.isFile() && file.name.endsWith('.zip')) {
println 'Attempting to unzip: ' + file + ' into folder: ' + appServerFolder
new Copy().from(project.zipTree(file)).into(appServerFolder).execute()
}
}
}
The problem is that the actual artifacts are not getting resolved!
A task can't configure the build model (that's what plugins do). It's fine to create and resolve a detached configuration in a task. If this doesn't work, there is likely a problem with the task's code, or the dependency it tries to resolve. Note that dependencies can only be resolved if the correct repository(s) are defined.
Instead of new DetaultExternalModuleDependency() (which is an internal class), project.dependencies.create() should be used. Instead of new Copy().execute() (Task#execute must not be called from user code), project.copy should be used.

OSGi bundle build issue in Gradle

I have a simple use case of building an OSGi bundle using Gradle build tool. The build is successful if there are java files present in the build path, but it fails otherwise.
I am using 'osgi' plugin inside the gradle script and trying to build without any java files. The build always fails with following error:
Could not copy MANIFEST.MF to
I am sure there must be some way to do it in Gradle but not able to fine. Any idea what can be done to resolve this depending on your experience.
I ran into this today as well, and #Peter's fix didn't work for me (I hadn't applied the java plugin in the first place...). However, after hours of Googling I did find this thread, which helped me find the problem.
Basically, it seems that the error occurs (as Peter stated) when no class files are found in the jar - my guess is because the plugin then cannot scan the classes for package names on which to base all the Import and Export information.
My solution was to add the following to the manifest specification:
classesDir = theSourceSet.output.classesDir
classpath = theSourceSet.runtimeClasspath
In my actual build code, I loop over all source sets to create jar tasks for them, so then it looks like this:
sourceSets.each { ss ->
assemble.dependsOn task("jar${ss.name.capitalize()}", type: Jar, dependsOn: ss.getCompileTaskName('Java')) {
from ss.output
into 'classes'
manifest = osgiManifest {
classesDir = ss.output.classesDir
classpath = ss.runtimeClasspath
// Other properties, like name and symbolicName, also set based on
// the name of the source set
}
baseName = ss.name
}
}
Running with --stacktrace indicates that the osgi plugin doesn't deal correctly with the case where both the osgi and the java plugins are applied, but no Java code is present. Removing the java plugin should solve the problem.
I had the same issue also when java code was present.
Adding these two lines to the osgiManifest closure fixed the problem:
classesDir = sourceSets.main.output.classesDir
classpath = sourceSets.main.runtimeClasspath
-- erik

Maven Plugin Fork Process With Proper Classpath

I am creating a Maven plugin with a rather unique requirement for proper operation: it needs to spawn new processes of itself and then wait for those processes to complete a task.
While this is relatively trivial to do on the command line, Maven plugins do not get invoked in the same manner as traditional Java code and thus there is no classpath. I cannot figure out how to resolve the correct classpath inside the plugin such that I can spawn a new JVM (invoking the Main method of another class inside the plugin).
Using the current artifact's MavenProject I am able to get an Artifact reference to myself (the plugin) and get it's relative directory inside the local Maven repository:
Artifact self = null;
for (Artifact artifact : project.getPluginArtifacts()) {
if ("my-group-id".equals(artifact.getGroupId()) && "my-artifact-id".equals(artifact.getArtifactId())) {
self = artifact;
break;
}
}
if (self == null) {
throw new MojoExecutionException("Could not find representation of this plugin in project.");
}
for (ArtifactRepository artifactRepository : project.getPluginArtifactRepositories()) {
String path = artifactRepository.pathOf(self);
if (path != null) {
getLog().info("relative path to self: " + path);
break;
}
}
How do I get a reference to all of its dependencies (and transitive dependencies) such that I can construct a full classpath for a new invocation? I see that self has a dependency filter but I don't know where to apply it.
Is this the proper way to create a new process of "myself" inside a plugin? Is there a better way?
I found a great article on the differences between dependency resolution on Maven 2 and Maven 3.
Given an Artifact it boils down to the following:
private Set<Artifact> getDependenciesForArtifact(Artifact artifact) {
ArtifactResolutionRequest arr = new ArtifactResolutionRequest()
.setArtifact(artifact)
.setResolveTransitively(true)
.setLocalRepository(local);
return repositorySystem.resolve(arr).getArtifacts();
}
With the Set you can construct a by calling pathOf on an ArtifactRepository for each element and joining with File.pathSeparator.
Hm. Not really an answer but some hints. Why do you need such a complex thing? Furthermore i would take a deep look into the maven-surefire-plugin which can fork a jvm for unit tests and can handle classpath. On the other hand you can take a look into the maven-invoker or in the maven-invoker-plugin which can fork maven completely. Ah..what i missed. Take a look into the maven-dependency-plugin which has a particular goal for creating the classpath where you can take a look into the sources how they construct the classpath.

Resources