Gradle War plugin - Rename libraries - java

I am starting to get deeper into Gradle by migrating one project I have From Maven 3.6.3 to Gradle 6.5.1.
I'm arriving at the stage where I have to build a War file in the impl module that is slightly customized: I rename the Jars in the lib folder, and include via an overlay (from a Jar built in the api module from the same project) some resources.
The current Maven configuration is the following:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<outputFileNameMapping>#{groupId}#-#{artifactId}#-#{version}#.#{extension}#</outputFileNameMapping>
<webResources>
<resource>
<directory>src/main/webapp</directory>
</resource>
</webResources>
<overlays>
<!-- Include the OpenAPI spec -->
<overlay>
<groupId>com.project.rest</groupId>
<artifactId>api</artifactId>
<type>jar</type>
<includes>
<include>specs/</include>
</includes>
</overlay>
</overlays>
</configuration>
</plugin>
So I'm trying to come up with something similar for Gradle regarding the libraries renaming.
I saw in the documentation of the plugin that there is a rename method in the plugin:
Renames a source file. The closure will be called with a single parameter, the name of the file. The closure should return a String object with a new target name. The closure may return null, in which case the original name will be used.
The issue is that is takes the name of file in parameter, whereas I would need (in order to mimic the outputFileNameMapping option) to get the dependency so I could extract its metadata.
So I assume this is not the right option. Is there a way to achieve this with Gradle?
Thanks

The gradle war plugin has a number of configuration options, including archiveFileName (or archiveName on older versions of gradle). archiveFileName by default is set to: [archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension]
This can be declared in the war {} block in your build.gradle. You should be able to do something like this:
war {
archiveFileName = "${project.group}-${project.name}-$archiveVersion.$archiveExtension"
}
For more on the available configuration options, see the War documentation
This will rename the war file itself.
If you want to rename contents of the war file instead, you can use the war.rootSpec.rename(), like so:
// make a copy of the implementation configuration that can be resolved so we can loop over it.
configurations {
implementationList {
extendsFrom implementation
canBeResolved true
}
}
war {
rootSpec.rename({ fileInWar ->
def returnValue = fileInWar
project.configurations.implementationList.resolvedConfiguration.resolvedArtifacts.each {
if (it.file.name == fileInWar) {
def depInfo = it.moduleVersion.id
print "$returnValue -> "
returnValue = "${depInfo.group}.${depInfo.name}-${depInfo.version}.${it.extension}"
println "$returnValue"
}
}
return returnValue
})
}
However, note that this will not resolve conflicts if you have duplicate dependencies.

Related

Maven Resources plugin ignoring exclusion list

TL; DR
Maven's Resources plugin doesn't seem to respect excludes elements in the resource configuration.
Setting
I have a large Java/Dart project where I need to deploy a WAR file that has both my UI and my backend in separate JARs. I want to cut down on the size of the deployed file, and I want to drop certain folders from the WAR. Based on the plugin documentation, I thought I could simply set excludes in my plugin configuration, and it won't copy over the unnecessary folders. However, it seems the Resources plugin is outright ignoring these, despite, the Maven model package including a setExcludes function.
Current Attempts
So far, I've tried two main approaches. My configuration is as follows:
<configuration>
<outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
<resources>
<resource>
<directory>src/main/webapp</directory>
<excludes>
<exclude>web.xml</exclude>
<exclude>appengine-web.xml</exclude>
<exclude>**/web/_el/*</exclude>
<exclude>WEB-INF/pages/frontend/**</exclude>
<exclude>**/_el/js/frontend/**</exclude>
<exclude>**/_el/dart/app/dashboard/lib/**</exclude>
<exclude>**/_el/dart/app/dashboard/.dart_tool/**</exclude>
</excludes>
</resource>
</resources>
</configuration>
I tried to use this config inside the execution element, as well as outside from directly under the plugin element, but both times it was ignored, and everything in the webapp directory was copied over mindlessly.
On a hunch, I did try setting filtering to true, but that just ate up all the memory in my computer, and it didn't even work - what it did process was copied over.
I also tried using the Shade plugin, but gave up on that pretty quickly, as the DontIncludeResourceTransformer only permits suffix-filtering, which is not adequate for my use case.
Question
So what am I doing wrong? Based on the docs, I believe the plugin should respect my excludes list and skip the vast majority of files, but it's evidently not doing that.
You need to use apache **maven war plugin**.
The WAR Plugin is responsible for collecting all artifact dependencies, classes and resources of the web application and packaging them into a web application archive.
It is possible to include or exclude certain files from the WAR file, by using the and configuration parameters. They each take a comma-separated list of Ant file set patterns. You can use wildcards such as ** to indicate multiple directories and * to indicate an optional part of a file or directory name.
Here is an example where we exclude all JAR files from WEB-INF/lib:
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<packagingExcludes>WEB-INF/lib/*.jar</packagingExcludes>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
https://maven.apache.org/plugins/maven-war-plugin/examples/including-excluding-files-from-war.html

Get (maven) artifact version at runtime of Java 9+ modular application

I have a Java 11 application which I develop using Maven and in the pom.xml I have a version declared.
<groupId>my.group.id</groupId>
<artifactId>artifact</artifactId>
<version>0.1.2.3</version>
I want to get this version at runtime e.g. using getClass().getPackage().getImplementationVersion() as it's described in this question. This works as long as I don't package my application as a modular runtime image using Jlink. Then I only get null returned from above call.
I package my application using:
jlink --output target/artifact-image --module-path target/dependencies --launcher MyApp=my.module.name/my.main.Class --add-modules my.module.name
Jlink has actually a parameter --version but this returns the Jlink version instead setting it for the generated artifact.
So, how can I get the version (of my Maven project) at runtime?
How to define it in the modular application?
How to get it into the modular application?
How to read it in the modular application?
I know I could define it in a resource file and simply read it from there, however I prefer to have it only in the pom.xml (= to have a single source of truth).
In the end I did this using the filtering function of the Maven Resources Plugin.
First, enable filtering in the pom.xml:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
Then add a src/main/resources/my-version.properties file containig:
my.version=${project.version}
So you can use the following code in Java:
Properties myProperties = new Properties();
try {
myProperties.load(getClass().getResourceAsStream("/my-version.properties"));
} catch (IOException e) {
throw new IllegalStateException(e);
}
String theVersion = Objects.requireNonNull((String) myProperties.get("my.version"));
I had a similar problem in my last job. I needed to get the version for modules/jars that are not a direct dependency of the application, as well as the module's version itself. The classpath is assembled from multiple modules when the application starts, the main application module has no knowledge of how many jars are added later.
That's why I came up with a different solution, which may be a little more elegant than having to read XML or properties from jar files.
The idea
use a Java service loader approach to be able to add as many components/artifacts later, which can contribute their own versions at runtime. Create a very lightweight library with just a few lines of code to read, find, filter and sort all of the artifact versions on the classpath.
Create a maven source code generator plugin that generates the service implementation for each of the modules at compile time, package a very simple service in each of the jars.
The solution
Part one of the solution is the artifact-version-service library, which can be found on github and MavenCentral now. It covers the service definition and a few ways to get the artifact versions at runtime.
Part two is the artifact-version-maven-plugin, which can also be found on github and MavenCentral. It is used to have a hassle-free generator implementing the service definition for each of the artifacts.
Examples
Fetching all modules with coordinates
No more reading jar manifests or property files, just a simple method call:
// iterate list of artifact dependencies
for (Artifact artifact : ArtifactVersionCollector.collectArtifacts()) {
// print simple artifact string example
System.out.println("artifact = " + artifact);
}
A sorted set of artifacts is returned. To modify the sorting order, provide a custom comparator:
new ArtifactVersionCollector(Comparator.comparing(Artifact::getVersion)).collect();
This way the list of artifacts is returned sorted by version numbers.
Find a specific artifact
ArtifactVersionCollector.findArtifact("de.westemeyer", "artifact-version-service");
Fetches the version details for a specific artifact.
Find artifacts with matching groupId(s)
Find all artifacts with groupId de.westemeyer (exact match):
ArtifactVersionCollector.findArtifactsByGroupId("de.westemeyer", true);
Find all artifacts where groupId starts with de.westemeyer:
ArtifactVersionCollector.findArtifactsByGroupId("de.westemeyer", false);
Sort result by version number:
new ArtifactVersionCollector(Comparator.comparing(Artifact::getVersion)).artifactsByGroupId("de.", false);
Implement custom actions on list of artifacts
By supplying a lambda, the very first example could be implemented like this:
ArtifactVersionCollector.iterateArtifacts(a -> {
System.out.println(a);
return false;
});
Installation
Add these two tags to all pom.xml files, or maybe to a company master pom somewhere:
<build>
<plugins>
<plugin>
<groupId>de.westemeyer</groupId>
<artifactId>artifact-version-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<goals>
<goal>generate-service</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>de.westemeyer</groupId>
<artifactId>artifact-version-service</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
Feedback
It would be great if you could give the solution a try. Getting feedback about whether you think the solution fits your needs would be even better. So please don't hesitate to add a new issue on any of the github projects if you have any suggestions, feature requests, problems, whatsoever.
Licence
All of the source code is open source, free to use even for commercial products (MIT licence).

Declare resource included by maven-bundle-plugin as optional

I have a multi-module build creating multiple artifacts with package type "bundle".
Some of them create some information in the META-INF directory during compile time, some don't.
I tried to define an instruction in the parent pom.xml that adds the META-INF directory as a resource to the bundle.
Unfortunately this fails for those artifacts not creating the META-INF directory during the build time.
I tried to avoid defining this rule on all modules that currently DO creating the META-INF directory since
There is a lot and
maybe the others will create the META-INF directory in the future and this will require future developers to know that they have to add this directory as a resource now.
Is it somehow possible to make this "include-resource" instruction optional, meaning it ignores this resource if it's missing?
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<instructions>
<Include-Resource>META-INF=${project.build.outputDirectory}/META-INF</Include-Resource>
</instructions>
</configuration>
</plugin>
Prefixing the resource pattern with - should suffice, e.g.:
<Include-Resource>-META-INF=${project.build.outputDirectory}/META-INF</Include-Resource>
Documentation here.

Blueprint XML file scanning with Gradle OSGI plugin

I'm investigating migrating an existing OSGI/blueprint project from Maven to Gradle. In Maven the maven-bundle-plugin plugin scans context XML files for imports that might not occur in the code, however I can't get this to work with the Gradle OSGI plugin.
For example, blueprint XML contains an import like this
<reference id="exampleService" availability="mandatory" interface="com.adamish.test.Test" />
Maven
Using the Bundle-Blueprint instruction in the POM with maven-bundle-plugin...
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.4.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Blueprint>OSGI-INF/blueprint/context.xml</Bundle-Blueprint>
</instructions>
</configuration>
</plugin>
Then the generated MANFEST.MF will contain an import like this
Import-Package: com.adamish.test
Gradle
However using the following build.gradle file does not generate a MANIFEST.MF with a Import-Package for com.adamish.test
apply plugin: 'osgi'
jar {
manifest {
instruction 'Bundle-Blueprint', 'OSGI-INF/blueprint/context.xml'
}
}
Analysis
Both Maven and Gradle use BND which seems to contain the Bundle-Blueprint instruction, however when invoked via Gradle it does not cause imports to be added to the MANIFEST.
I've tested this in Gradle 2.4 and now latest 2.10
The Blueprint parsing capability of maven-bundle-plugin is provided by the class BlueprintPlugin part of maven-bundle-plugin, not BND. BND does contain some blueprint-aware code, however that is part of a repoindex command tool.
I've been able to workaround this issue temporarily by parsing the XML files manually and building list of Java packages
def importPackages = new LinkedHashSet<String>();
fileTree(dir: 'src/main/resources/OSGI-INF/blueprint/', include: '*.xml').each {
new XmlSlurper().parse(it).'**'.findAll { it.#availability == "mandatory" }.each {
def iFace = it.#interface.text()
importPackages.add(iFace.substring(0, iFace.lastIndexOf('.')))
}
}
importPackages.add('com.adamish.foo')
jar {
manifest {
instruction 'Import-Package', importPackages.join(',')
}
}

how to get property value from pom.xml?

I have added a node in my pom.xml:
<properties>
<getdownload-webapp.version>1.5</getdownload-webapp.version>
</properties>
how could I get this 1.5 value in code?
String version = System.getProperty("getdownload-webapp.version"); // output version = null
This code gave me null while running(
ps: there is no settings.xml in this project
So you have a property like this.
<properties>
<getdownload-webapp.version>1.5</getdownload-webapp.version>
</properties>
Create a file as follows in your Maven project.
src/main/resources/project.properties
Or as follows if it is for tests only.
src/test/resources/project.properties
Add this line inside the new file. Please note that you should not prefix with "properties" (e.g. don't write "properties.getdownload-webapp.version").
version=${getdownload-webapp.version}
Note that you can also add flags like this to the file.
debug=false
If not already done, you have to enable Maven filtering for your project. It is the feature that will look for placeholders inside the files of your project to be replaced by values from the pom. In order to proceed, you need add these lines inside the <build> tag of your pom.xml file. This is how to do with src/main:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
...
And here is how to do for src/test:
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
...
Finally, in your source code (MyClassName.java), add a block like
Properties props = new Properties();
props.load(MyClassName.class.getClassLoader().getResourceAsStream("project.properties"));
String version = props.getProperty("version");
You can add as many variables as you want to the project.properties file and load each one using this method.
The mechanism chiefly in charge to transfer Maven properties to Java application is provided by the Maven Resource Plugin
The plugin is part of the Maven Super Pom and executed during the process-resources phase of the Jar Default Lifecyle. The only thing you have to do is to active filtering.
This will replace any placeholders, e.g. ${my.property} in any of the files in src/main/resources with the corresponding property from your pom, e.g. <property><my.property>test</my.property></property>
How you make this property then available to your Java application is up to you - reading it from the classpath would work.
I assume you want to get it in the code to check something right? You can use filtering from maven that will inject the value in the source code, similar to the filtering option
http://mojo.codehaus.org/templating-maven-plugin/
String version = project.getProperties().getProperty("getdownload-webapp.version");
Where project is of type MavenProject

Categories