Gradle - don't specify version in included dependencies names - gradle

My application gets packaged as ear and I have used earlib and deploy configuration. However for all those dependencies version gets mentioned in the jar names.
For example, if I mention dependencies as below,
earlib 'com.xyz:abc:1.0.1'
In generated ear I can see jar name as abc-1.0.1.jar but I want to get it included simply as abc.jar.

Declare a dependency without a version
Gradle lets you declare a dependency without a version but you have to define a dependency constraint, which basically is the definition of your dependency version. This is commonly used in large projects:
dependencies {
implementation 'org.springframework:spring-web'
}
dependencies {
constraints {
implementation 'org.springframework:spring-web:5.0.2.RELEASE'
}
}
Declare a dynamic version
Another option is to declare a dynamic version by using the plus operator. This allows you to use the latest relase of a dependency while you pack your application. Doing so is potentially dangerous since its bears the risk of breaking the application:
apply plugin: 'java-library'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework:spring-web:5.+'
}
Declaring a file dependency
If you don't want to rely on a binary repository at all but provide dependencies yourself, you can declare a file dependency, from the directories ant, libs and tools. This allows you to name and version dependencies as you like but you have to maintain them yourself:
configurations {
antContrib
externalLibs
deploymentTools
}
dependencies {
antContrib files('ant/antcontrib.jar')
externalLibs files('libs/commons-lang.jar', 'libs/log4j.jar')
deploymentTools fileTree(dir: 'tools', include: '*.exe')
}

Note I recommend against removing the versions as they are important diagnostic information when the application doesn't work.
The ear task is an instance of the Ear task type, which in turn is basically a specialised form of the standard Zip task type. All archiving tasks allow you to rename files as they are packed.
For example, the following might work:
ear {
rename '(.+)-[^-].+(\\.jar)', '$1$2'
lib {
rename '(.+)-[^-].+(\\.jar)', '$1$2'
}
}
I strongly recommend that you check out the new user manual chapter on Working with files for more information about copying and archiving files. Hopefully I'll remember to update this answer with the non-release-candidate link once Gradle 4.7 is out.
Also, if you have any feedback on that chapter let me know.
EDIT Based on OP's feedback, I discovered that the Ear task uses a child copy specification for the JARs in the earlib configuration. Child specifications are independent of both the main one and other child specs, so the main rename() doesn't apply to the earlib files. That's why we add a rename() via the lib {} block.

Related

How does gradle choose actually libraries ending with "-jvm"?

I'm migrating from Gradle to Bazel.
I had in my gradle build a testImplementation dependency to io.kotest:kotest-runner-junit5:5.4.2. It works perfectly.
I add the same dependency to my Bazel config files (WORKSPACE and BUILD), but I get compilation errors, as if the library doesn't exist.
I go and check if Bazel doesn't bring transitive dependencies, but it does.
I check the POM of the library and it turns out it has no dependencies.
I see in maven there's another called io.kotest:kotest-runner-junit5-jvm:5.4.2.
I use that one instead. Voilá, it works!
But why? how is gradle picking the -jvm artifact instead?
There are 3 parts to your question
How does Gradle what variants are available?
How does Gradle find the variants?
How does Gradle figure out how to pick the right variant?
The short answer is that
Gradle publishes an additional Module Metadata file that contains info on what variants are available, and where to find them.
Gradle doesn't use just the Maven group:artifact:version coordinates to find dependencies - it uses variant attributes to 'tag' available modules and the requests for modules, so it can match the requested dependency with those available in a very fine grained way.
Kotlin Multiplatform Variants
Kotlin Multiplatform libraries publish several artifacts: a 'common' published artifact, and a variant for each platform that it targets.
For io.kotest:kotest-runner-junit5 that means there's
A 'common' library
https://repo1.maven.org/maven2/io/kotest/kotest-runner-junit5/5.4.2/
and a -jvm variant
https://repo1.maven.org/maven2/io/kotest/kotest-runner-junit5-jvm/5.4.2/
Typically dependencies are determined by Maven POM and maven-metadata.xml files. That will be what Bazel is doing. The same is true for a project that uses Maven. So how does Gradle figure out what variants are available, and which ones to use?
Variant availability: Gradle Module Metadata
Gradle publishes an additional metadata file. https://docs.gradle.org/current/userguide/publishing_gradle_module_metadata.htm. It's like a pom.xml, but with a lot more info. The extension is .module, but the content is JSON.
Looking in https://repo1.maven.org/maven2/io/kotest/kotest-runner-junit5/5.4.2/, we can see the file.
Because it's JSON, we can look inside. There's some metadata
{
"formatVersion": "1.1",
"component": {
"group": "io.kotest",
"module": "kotest-runner-junit5",
"version": "5.4.2",
"attributes": {
"org.gradle.status": "release"
}
},
...
There's also a variants array. One of the variants is the -jvm variant, as well as a available-at.url, which is a relative path linking to the available variants within the Maven repository.
...
"variants": [
...
{
"name": "jvmRuntimeElements-published",
"attributes": {
"org.gradle.category": "library",
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime",
"org.jetbrains.kotlin.platform.type": "jvm"
},
"available-at": {
"url": "../../kotest-runner-junit5-jvm/5.4.2/kotest-runner-junit5-jvm-5.4.2.module",
"group": "io.kotest",
"module": "kotest-runner-junit5-jvm",
"version": "5.4.2"
}
}
...
]
}
That's how Gradle discovers the available variants.
Variant selection: Attribute matching
There's actually several variants in the module, and there would be even more if more Kotlin Multiplatform targets were enabled, so the final question "how does Gradle figure out what variant is needed?"
The answer comes from the "attributes" that are associated with the variant. They're just key-value strings that Gradle uses to match what's required, to what's available.
https://docs.gradle.org/current/userguide/variant_attributes.html#attribute_matching
The attributes might say
I want a Java 8 JAR for org.company:some-artifact:1.0.0
or
I want a Kotlin Native 1.7.0 source files for io.kotest:something:2.0.0
They're just key-value strings, so they can really be anything. I've created attributes for sharing TypeScript files, or JaCoCo XML report files.
Why do we never see these attributes when we write Gradle files?
When you add a dependency in Gradle
// build.gradle.kts
plugins {
kotlin("jvm") version "1.7.20"
}
dependencies {
testImplementation("io.kotest:kotest-runner-junit5:5.4.2")
}
There's no attributes. So how does Gradle know to select the -jvm variant?
Gradle sees you've added a dependency using testImplementation, which is a Configuration.
(Aside: I think the name 'Configuration' is confusing. It's not configuration for how the project behaves. I like to think of it more like how a group of naval warships might have a 'configuration' for battle, or a 'configuration' for loading supplies. It's more about the 'shape', and it's not about controlling Gradle properties or actions.)
When Configurations are defined, they're also tagged with attributes, which Gradle will use to play matchmaker between the request for "kotest-runner-junit5" and what it discovers in the registered repositories
In the case of testImplementation("io.kotest:kotest-runner-junit5:5.4.2"), Gradle can see that testImplementation has Attributes that say "I need a JVM variant", and it can use that to find a matching dependency using the module metadata in Maven Central.

What is supposed to happen to dependencies after gradle build?

I am trying out Gradle, and am wondering, what is supposed to happen to a project's dependencies after you run gradle build? For example, my sample projects don't run on the command line after they are built, because they are missing dependencies. They seem to compile fine, as gradle doesn't give me errors or warnings about finding the dependencies.
Gradle projects I've made in IntelliJ Idea have the same problem. They compile and run inside the IDE, but are missing dependencies and can't run on the command line.
So what is supposed to happen to the dependencies I declare in the build.gradle file? Shouldn't they be output somewhere together with my .class files? Otherwise, what is the point of gradle when I could manage this by editing my classpath?
Edit: Here is my build.gradle file:
apply plugin: 'java'
jar {
manifest {
attributes('Main-Class': 'Animals')
}
}
repositories {
flatDir{
dirs "D:\\libs\\gradleRepo"
}
}
dependencies {
compile name: "AnimalTypes-1.0-SNAPSHOT"
}
sourceSets{
main{
java {
srcDirs=['src']
}
}
}
Your Gradle build only takes care of the compile time and allows you to use the specified dependencies in your code (it adds them to the compile classpath). But it does not take care of the runtime. Once the JAR is build, you need to specify the runtime classpath and provide all required dependencies.
You may think, that this is bad or a disadvantage, but actually it is totally fine and intended, because if you build a Java library, you won't need to execute it, you just want to specify it as a dependency for another project. If you would distribute your library to a Maven repository, all dependencies from Maven repositories (module dependencies) would end up in a POM descriptor as transitive dependencies.
Now, if you want to build a runnable Java application, simply use the Gradle Application Plugin (apply plugin: 'application'), which will create a ZIP file containing the dependencies and start scripts providing your runtime classpath for execution.
Third-party plugins can also produce so-called fat JARs, which are JAR files with all dependencies included. It depends on your use case if you should use them, because often dependency management via repositories is the better way to go.

Gradle filter duplicate dependencies after they have been resolved

I have to develop a module against a live system that has a lib folder.
In order to get all the dependencies, I need to add a dependency on that folder.
Then I add my own new dependencies using the gradle way compile ...
The problem is the system already contains some of the libs I add as dependencies or that are resolved as transitive dependencies.
I would like to be able to though each dependency and if I find one with the same name in the lib folder, remove it, so I can use the one resolved from the maven repository.
Any idea where I can insert that code, or if it is possible ?
** I guess one other option would be to copy them and filter by hand into a new lib folder, I am wondering if I can make something automatic that may take into account future upgrades
I have to develop a module against a live system that has a lib folder.
The problem is the system already contains some of the libs I add as dependencies or that are resolved as transitive dependencies.
The best way to manage this case is to avoid the lib folder and just move all these libraries/dependencies in a maven (private) repo.
In this way gradle will manage all nested dependencies avoiding to duplicate libraries with different versions.
I'm guessing you've got something like this
dependencies {
compile fileTree(dir: 'lib', include: '*.jar')
}
Unfortunately when you do this, the GAV (group, artifact, version) is not known to gradle. Each jar is simply a file without a GAV or any other metadata (eg transitive dependencies). None of these jars can participate in Gradle's dependency resolution.
If you want to put your jars in a local folder, I suggest you use the maven repository directory layout (eg /someFolder/$groupIdWithSlashes/$artifactId/$version/$artifactId-$version.$extension)
You could then specify the local folder as a maven repository
repositories {
maven {
url uri('mavenRepo')
}
}
dependencies {
compile 'group1:artifact1:1.0'
compile 'group2:artifact2:2.0'
}
Jars could then be stored as
mavenRepo/group1/artifact1/1.0/artifact1-1.0.jar
mavenRepo/group2/artifact2/2.0/artifact2-2.0.jar
Optionally, you might want to add poms with transitive dependencies etc at
mavenRepo/group1/artifact1/1.0/artifact1-1.0.pom
mavenRepo/group2/artifact2/2.0/artifact2-2.0.pom
More details on maven directory layout here
Adding another answer because you don't want to do it properly, you want a hack
You could do something like this:
def libJars = fileTree(dir: 'lib', include: '*.jar')
dependencies {
compile 'foo:bar:1.0'
compile project(':anotherProject')
compile libJars
}
libJars.files.each { File libJar ->
// assuming jars are named $module-$version.jar
Pattern pattern = Pattern.compile("(.+)-.+?\\.jar")
Matcher matcher = pattern.matcher(libJar.name)
if (!matcher.matches()) throw new RuntimeException("${libJar.name} does not match ${pattern.pattern()}")
String module = matcher.group(1)
// exclude other dependencies with the same module as this "libJar"
configurations.compile.exclude [module: module]
}

How do I use only file based dependencies in gradle instead of specifying groupId:artifactId:versionId

I have the following entry in my gradle file
dependencies {
compile 'org.A:A:1.0'
}
which downloads 'org.B:B:1.0' because that's it's dependency.(not mentioned explicitly in gradle)
What I want to use in my project is A* and B* which are shadows(changed namespace) of A and B respectively.
Now, I have specified the dependency for A* as
dependencies{
compile file('libs/A*.jar')
}
But, this one still downloads 'org.B:B:1.0'
How do I wire the gradle to use file('libs/B*.jar')?
The first solution that comes to mind is to exclude the transitive dependency of compile 'org.A:A:1.0'
This works like this:
dependencies {
compile('org.A:A:1.0') {
exclude 'org.B:B:1.0'
}
}
Have a look at the Gradle User Guide for more details on that subject.
As mentioned in the comments, file dependencies can't have transitive dependencies. So A*.jar either has the contents of the B.jar rolled into it somehow, or you have B.jar on the build path somewhere else.
File dependencies are generally a tool of last resort, they do not participate in conflict resolution; you need a dependency repository like maven or ivy for that.

How can I make a transitive dependency in gradle also use the dependency's original source?

I have a library, which I call core, which is a dependency of another project, called Museum. In core's build.gradle, I am using gson-fire, which is specified as a dependency in the following manner:
repositories {
maven { url 'https://raw.github.com/julman99/mvn-repo/master'}
}
...
dependencies {
compile 'com.github.julman99:gson-fire:0.11.0'
}
This works fine - core is compiled. When I go to use it in my Museum project, though, I get the following:
A problem occurred configuring project ':Museum'.
> Could not resolve all dependencies for configuration ':Museum:_debugCompile'.
> Could not find com.github.julman99:gson-fire:0.11.0.
Searched in the following locations:
file:/Users/jwir3/.m2/repository/com/github/julman99/gson-fire/0.11.0/gson-fire-0.11.0.pom
file:/Users/jwir3/.m2/repository/com/github/julman99/gson-fire/0.11.0/gson-fire-0.11.0.jar
http://download.crashlytics.com/maven/com/github/julman99/gson-fire/0.11.0/gson-fire-0.11.0.pom
http://download.crashlytics.com/maven/com/github/julman99/gson-fire/0.11.0/gson-fire-0.11.0.jar
https://repo1.maven.org/maven2/com/github/julman99/gson-fire/0.11.0/gson-fire-0.11.0.pom
https://repo1.maven.org/maven2/com/github/julman99/gson-fire/0.11.0/gson-fire-0.11.0.jar
Required by:
museum:Museum:unspecified > com.jwir3.core:core:1.4.0-SNAPSHOT
The build.gradle of Museum looks like the following:
dependencies {
compile ('com.thisclicks.core:core:' + project.CORE_LIB_VERSION+ '+#aar') {
transitive = true
}
}
Presumably, this is because the core library is specified as transient = true in the build.gradle of Museum, but it doesn't have the correct location to search for the Maven repository of gson-fire. Is there a way to make these search locations transient as well as the dependencies themselves?
Not automatically, no. Transitive dependencies do not bring in repository information, only the artifacts themselves. If you want this to work you'll have to add the repositories { } block from the core project to the Museum project.
Additionally, adding transitive = true is unnecessary in this case. This is the default anyway, and as explained above, is unrelated to this particular issue.

Resources