Blueprint XML file scanning with Gradle OSGI plugin - java

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(',')
}
}

Related

Gradle Spring boot failed with error "required a bean of type 'org.springframework.boot.info.BuildProperties' that could not be found." [duplicate]

I'm creating a very simple application with a few REST API's and it's currently working correctly until I try to use the BuildProperties on my health check API. While starting my application I get the following error:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-06-17 09:54:29.210 ERROR 10796 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field buildProperties in com.controller.HealthCheck required a bean of type 'org.springframework.boot.info.BuildProperties' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
The following candidates were found but could not be injected:
- Bean method 'buildProperties' in 'ProjectInfoAutoConfiguration' not loaded because #ConditionalOnResource did not find resource '${spring.info.build.location:classpath:META-INF/build-info.properties}'
Action:
Consider revisiting the entries above or defining a bean of type 'org.springframework.boot.info.BuildProperties' in your configuration.
I went to the build file and I also looked in the jar file created by the build and I see the build-info.properties is in fact there. In the jar file the path to the file is "BOOT-INF\classes\META-INF\". I also have other "Autowired" elements that are not having issues.
Where my code fails:
#RestController
public class HealthCheck {
#Autowired
Environment environment;
#Autowired
BuildProperties buildProperties;
#GetMapping("/health")
public HealthCheckResponse healthCheck() {
return getHealthCheckResponse();
}
private HealthCheckResponse getHealthCheckResponse(){
HealthCheckResponse healthResponse = new HealthCheckResponse();
String[] profiles = environment.getActiveProfiles();
healthResponse.setServerTime(new Date());
healthResponse.setVersion(buildProperties.getVersion());
healthResponse.setEnvironment(profiles[0]);
return healthResponse;
}
My gradle build file:
plugins {
id 'org.asciidoctor.convert' version '1.5.3'
id 'org.springframework.boot' version '2.1.5.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
apply plugin: 'eclipse'
apply plugin: 'java'
group = 'com'
version = '0.0.1'
sourceCompatibility = '12'
repositories {
mavenCentral()
}
ext {
set('snippetsDir', file("build/generated-snippets"))
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-jersey'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:2.1.1'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.restdocs:spring-restdocs-webtestclient'
testImplementation 'org.springframework.security:spring-security-test'
}
test {
outputs.dir snippetsDir
}
asciidoctor {
inputs.dir snippetsDir
dependsOn test
}
springBoot {
buildInfo()
}
build-info.properties:
#Properties
#Mon Jun 17 10:52:04 EDT 2019
build.version=0.0.1
build.group=com
build.name=app
build.artifact=app
build.time=2019-06-17T14\:52\:04.829909200Z
Just run a mvn clean package and then restart the spring boot application from either Eclipse/IntelliJ.
Add/Modify at the end of pom.xml:
<build>
<finalName>{NAME_YOUR_PROJECT}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
In short: This problem is IDE related (I'm checked on Eclipse and Idea), and this is not affecting running/debugging spring boot application in startup scripts over the gradle build system.
Also, premise that boot plugin for eclipse and JDK are producing this problem is not entirely correct.
Root of this problem: Different location of build artifacts which are compiled with different compilers and missing build-info.properties.
Explanation:
When gradle performs the build, it ussually uses JDK compiler to produce Java artifacts and output is placed into build directory.
On the other side, when eclipse performs the build, it produces arifacts with Eclipse JDT and output is placed into bin directory.
Note that mixing those two could lead to unexpected behaviours. This 'feature' is already analyzed by eclipse team and it is rejected (closed as invalid). More information here.
According to fact that gradle task buildInfo is ran by gradle, that is explaining the fact that build-info.properties file exists in gradle's default output folder (by default it has to be located here: build/resources/main/META-INF/).
From #ROOTKILL's question, visible is that he is tried to obtain information from BuildProperties class. Under the hood, when Spring detects there is build-info.properties file on the classpath, it creates BuildProperties bean unless it is explicitly declared.
Useful information is here.
Please, take a look at this method:
#ConditionalOnResource(resources = "${spring.info.build.location:classpath:META-INF/build-info.properties}")
#ConditionalOnMissingBean
#Bean
public BuildProperties buildProperties() throws Exception {
return new BuildProperties(
loadFrom(this.properties.getBuild().getLocation(), "build"));
}
According to the fact that IDE is using different output dir, there is missing build-info.properties file and this produces displayed error (Bean method 'buildProperties' in 'ProjectInfoAutoConfiguration' not loaded because #ConditionalOnResource did not find resource '${spring.info.build.location:classpath:META-INF/build-info.properties}').
And on other side, this explains why everything is runnable with gradle.
Solution:
According to those facts, solution is clear: Both eclipse and IntelliJ Idea IDE's must use gradle's tasks instead of it's own for running/debugging.
For the Eclipse IDE: Application can be started over the gradle task (bootRun from gradle tasks view).
For the Idea IDE: There can be added setting that delegates IDE build/run actions to gradle, which is #user666 already pointed before.
Since this solution uses gradle, build-info.properties file will be used from build/resources/main/META-INF/ location (gradle's default), and off course it will be visible. As a consequence, bean BuildProperties will be created and will be usable.
As correctly mentioned by #André Schonrock, the cause of issue is here:
Both the Maven plugin and the Gradle plugin allow generating build
information containing the coordinates, name, and version of the
project. The plugins can also be configured to add additional
properties through configuration. When such a file is present, Spring
Boot auto-configures a BuildProperties bean.
So you need either to add for spring-boot-maven-plugin in POM:
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
or to add in build.gradle:
springBoot {
buildInfo()
}
As alternative way can be explicitly added Bean in a shorter form than was shown by #djangofan:
#Bean #ConditionalOnMissingBean(BuildProperties.class)
BuildProperties buildProperties() {
return new BuildProperties(new Properties());
}
in configuration file.
Notice: if you updated POM or build.gradle, but the error still appears, try to use (e.g. for Maven) lifecycle commands clean and then install and run again the project.
As #Borislav Markov suggested I tried running it via command line and it seems to work perfectly regardless if I use JDK 12 or JDK 8. I think the issue has to do with the eclipse plugin I am using to run the application via the IDE.
If using Intelli and maven, here is screenshot on how to change the runner and use maven runner. Ideally this is best suggested, as we can run exactly how maven runs in the ide also.
My Spring Boot service has a Maven build-info section in the maven-spring-boot plugin and so I get this error BuildProperties cannot be found when I try to run the service when it is not from a .jar archive. So, to run my service as a regular Spring Boot run configuration, I had to add this condition bean and now everything works , both as a .jar release and also it runs as a debug non-release:
#ConditionalOnMissingBean
#Bean
public BuildProperties buildProperties() {
Properties properties = new Properties();
properties.put("group", "com.example");
properties.put("artifact", "demo");
properties.put("version", "not-jarred");
return new BuildProperties(properties);
}
If you are using lombok, make sure to exclude it configuration for spring-boot-maven-plugin, e.g.,
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
As other answers have made clear, this is due to the IDE not creating the file. For vscode, I solved it by defining a task to copy the build-info.properties file from Gradle's build/resources/main/META-INF directory into vscode's bin/main/META-INF so that it would be where the build expects to find it.
Normally I have run a Gradle build recently, and that slightly stale version of build-info.properties is good enough for my debugging run in vscode.
Create a .vscode/tasks.json similar to this:
{
"version": "2.0.0",
"tasks": [
{
// This task keeps vscode from failing when using BuildProperties
"label": "Copy build-info from Gradle",
"type": "shell",
"command": "mkdir -p ./bin/main/META-INF/ && cp ./build/resources/main/META-INF/build-info.properties ./bin/main/META-INF/build-info.properties"
}
]
}
Then add the task as a preLaunchTask in your .vscode/launch.json:
{
"configurations": [
{
"type": "java",
"name": "Spring Boot-Application<example>",
"request": "launch",
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"mainClass": "com.example.Application",
"projectName": "example",
"args": "",
"preLaunchTask": "Copy build-info from Gradle"
}
]
}
I would suggest to try running under JDK 8 and to run from the command line with
java -jar <your jar name> just to be sure you get the build properties correctly.
Maybe Jdk 12 is not suitable for spring-boot yet. I think you might have other problems as well. Many Java frameworks are not 100% certified that they will work with JDK 12.
I think the plan is to officially support Java 12 as of Spring Boot 2.2
I think your IDE is confused by the fact that the "fat jar" overwrites the normal jar. The IDE understands the classpath of the normal jar + generated resource of `build-info.properties.
I always build the jars under different names, so I can avoid this kind of issues with partial builds.
https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/#packaging-executable-and-normal
To avoid the executable archive and the normal archive from being written to the same location, one or the other should be configured to use a different location. One way to do so is by configuring a classifier:
bootJar {
classifier = 'boot'
}
I tried ProjectInfoAutoConfiguration solution and thought it was better to use #ConditionalOnMissingBean (BuildProperties.class) instead
#ConditionalOnResource (resources = "$ {spring.info.build.location: classpath: META-INF / build-info.properties}").
Because I can control the way BuildProperties are created:
#ConditionalOnMissingBean(BuildProperties.class)
#Bean
public BuildProperties buildProperties() throws IOException {
Resource r = this.properties.getBuild().getLocation();
if (r.exists())
// build-info.properties exists in my jar
return new BuildProperties(
loadFrom(r, "build")); // see ProjectInfoAutoConfiguration code
else {
// No, we running via IDE. So let's build a stub:
Properties properties = new Properties();
properties.put("group", "com.example");
properties.put("artifact", "demo");
properties.put("version", "not-jarred");
return new BuildProperties(properties);
}
}
For Gradle Project: add this on build.gradle file
springBoot {
buildInfo {
properties {
name = PROJECT_NAME
additional = [
'yourCustomInfo': 'someInfo'
]
}
}
}
The issue is STS and Intellij doesn't run the buildinfo task while running the application as a Spring Boot Application. Since most developers run the app through this mechanism rather than using gradle / maven build need to have a solution that works for both. I followed Sergey K's answer and it worked for IDE based Spring boot run. But for gradle run it failed because the BuildProperties was getting autowired from the Configuration file instead of the generated build-info.properties file.
I was able to overcome this by having the following Component and autowiring it
#Component
public BuildValues{
#Autowired(required = false)
private buildProperties;
public getBuildProperties(){
if(buildProperties==null) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource r = resourceLoader.getResource("classpath:META-INF/build-
info.properties");
if (r.exists()) {
// build-info.properties exists in my jar
Properties prop = PropertiesLoaderUtils.loadProperties(r);
buildProperties = new BuildProperties(prop);
}else {
// No, we running via IDE. So let's build a stub:
Properties properties = new Properties();
properties.put("buildTime", "2022-01-13T14:38:06.567Z");
buildProperties = new BuildProperties(properties);
}
}
return buildProperties;
}
}

Gradle War plugin - Rename libraries

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.

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).

How to build uberJar with Quarkus-Gradle-Plugin

I'm trying to build an uberJar with all the dependencies (runnable) using the Quarkus Gradle plugin.
With maven you can build it by adding a config to the plugin.
That's what it looks like in maven:
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<configuration>
<uberJar>true</uberJar>
</configuration>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
Is there any possibility to set this property in the gradle plugin?
id 'io.quarkus.gradle.plugin' version '0.12.0'
The name of that option is "uber-jar".
To set this property you have to start the build like that from command line:
>gradle quarkusBuild --uber-jar
I had some bugs during the build, like that one
Caused by: java.nio.file.NoSuchFileException: /Users/sven/Idea/getting-started/build/getting-started.jar
but in the end the build was successful
The quarkusBuild task contains a property named uberJar that you can be used to control the uberJar behavior (see this).
You can directly configure the task in your build.gradle using something like:
task buildUberJar(type: io.quarkus.gradle.tasks.QuarkusBuild, dependsOn: build) {
uberJar = true
}
However, I see a lot of issues with overlapping resources between the jars with this approach. Here is a subset of my output:
> Task :service-asset-management:buildUberJar
building quarkus runner
Duplicate entry META-INF/quarkus-extension.json entry from io.quarkus:quarkus-jackson::jar:0.26.1(runtime) will be ignored. Existing file was provided by io.quarkus:quarkus-kubernetes-client::jar:0.26.1(runtime)
Duplicate entry NOTICE entry from org.apache.kafka:kafka-clients::jar:2.2.1(runtime) will be ignored. Existing file was provided by org.ehcache:ehcache::jar:3.6.1(runtime)
Duplicate entry META-INF/quarkus-extension.json entry from io.quarkus:quarkus-arc::jar:0.26.1(runtime) will be ignored. Existing file was provided by io.quarkus:quarkus-kubernetes-client::jar:0.26.1(runtime)
Duplicate entry META-INF/quarkus-extension.json entry from io.quarkus:quarkus-core::jar:0.26.1(runtime) will be ignored. Existing file was provided by io.quarkus:quarkus-kubernetes-client::jar:0.26.1(runtime)
Dependencies with duplicate files detected. The dependencies [org.apache.kafka:kafka-clients::jar:2.2.1(runtime), org.ehcache:ehcache::jar:3.6.1(runtime)] contain duplicate files, e.g. NOTICE
Dependencies with duplicate files detected. The dependencies [io.quarkus:quarkus-core::jar:0.26.1(runtime), io.quarkus:quarkus-jackson::jar:0.26.1(runtime), io.quarkus:quarkus-kubernetes-client::jar:0.26.1(runtime), io.quarkus:quarkus-arc::jar:0.26.1(runtime)] contain duplicate files, e.g. META-INF/quarkus-extension.json
Dependencies with duplicate files detected. The dependencies [commons-logging:commons-logging::jar:1.2(runtime), org.slf4j:jcl-over-slf4j::jar:1.7.25(runtime)] contain duplicate files, e.g. org/apache/commons/logging/impl/SimpleLog$1.class

How to add and use a non-maven dependency (jni4net) with a bunch of runtime-dependant .dll's? [duplicate]

Maven 2 is driving me crazy during the experimentation / quick and dirty mock-up phase of development.
I have a pom.xml file that defines the dependencies for the web-app framework I want to use, and I can quickly generate starter projects from that file. However, sometimes I want to link to a 3rd party library that doesn't already have a pom.xml file defined, so rather than create the pom.xml file for the 3rd party lib by hand and install it, and add the dependency to my pom.xml, I would just like to tell Maven: "In addition to my defined dependencies, include any jars that are in /lib too."
It seems like this ought to be simple, but if it is, I am missing something.
Any pointers on how to do this are greatly appreciated. Short of that, if there is a simple way to point maven to a /lib directory and easily create a pom.xml with all the enclosed jars mapped to a single dependency which I could then name / install and link to in one fell swoop would also suffice.
Problems of popular approaches
Most of the answers you'll find around the internet will suggest you to either install the dependency to your local repository or specify a "system" scope in the pom and distribute the dependency with the source of your project. But both of these solutions are actually flawed.
Why you shouldn't apply the "Install to Local Repo" approach
When you install a dependency to your local repository it remains there. Your distribution artifact will do fine as long as it has access to this repository. The problem is in most cases this repository will reside on your local machine, so there'll be no way to resolve this dependency on any other machine. Clearly making your artifact depend on a specific machine is not a way to handle things. Otherwise this dependency will have to be locally installed on every machine working with that project which is not any better.
Why you shouldn't apply the "System Scope" approach
The jars you depend on with the "System Scope" approach neither get installed to any repository or attached to your target packages. That's why your distribution package won't have a way to resolve that dependency when used. That I believe was the reason why the use of system scope even got deprecated. Anyway you don't want to rely on a deprecated feature.
The static in-project repository solution
After putting this in your pom:
<repository>
<id>repo</id>
<releases>
<enabled>true</enabled>
<checksumPolicy>ignore</checksumPolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>file://${project.basedir}/repo</url>
</repository>
for each artifact with a group id of form x.y.z Maven will include the following location inside your project dir in its search for artifacts:
repo/
| - x/
| | - y/
| | | - z/
| | | | - ${artifactId}/
| | | | | - ${version}/
| | | | | | - ${artifactId}-${version}.jar
To elaborate more on this you can read this blog post.
Use Maven to install to project repo
Instead of creating this structure by hand I recommend to use a Maven plugin to install your jars as artifacts. So, to install an artifact to an in-project repository under repo folder execute:
mvn install:install-file -DlocalRepositoryPath=repo -DcreateChecksum=true -Dpackaging=jar -Dfile=[your-jar] -DgroupId=[...] -DartifactId=[...] -Dversion=[...]
If you'll choose this approach you'll be able to simplify the repository declaration in pom to:
<repository>
<id>repo</id>
<url>file://${project.basedir}/repo</url>
</repository>
A helper script
Since executing installation command for each lib is kinda annoying and definitely error prone, I've created a utility script which automatically installs all the jars from a lib folder to a project repository, while automatically resolving all metadata (groupId, artifactId and etc.) from names of files. The script also prints out the dependencies xml for you to copy-paste in your pom.
Include the dependencies in your target package
When you'll have your in-project repository created you'll have solved a problem of distributing the dependencies of the project with its source, but since then your project's target artifact will depend on non-published jars, so when you'll install it to a repository it will have unresolvable dependencies.
To beat this problem I suggest to include these dependencies in your target package. This you can do with either the Assembly Plugin or better with the OneJar Plugin. The official documentaion on OneJar is easy to grasp.
For throw away code only
set scope == system and just make up a groupId, artifactId, and version
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swingx</artifactId>
<version>0.9.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/swingx-0.9.3.jar</systemPath>
</dependency>
Note: system dependencies are not copied into resulted jar/war
(see How to include system dependencies in war built using maven)
You may create local repository on your project
For example if you have libs folder in project structure
In libs folder you should create directory structure like: /groupId/artifactId/version/artifactId-version.jar
In your pom.xml you should register repository
<repository>
<id>ProjectRepo</id>
<name>ProjectRepo</name>
<url>file://${project.basedir}/libs</url>
</repository>
and add dependency as usual
<dependency>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>version</version>
</dependency>
That is all.
For detailed information: How to add external libraries in Maven (archived)
Note: When using the System scope (as mentioned on this page), Maven needs absolute paths.
If your jars are under your project's root, you'll want to prefix your systemPath values with ${basedir}.
This is what I have done, it also works around the package issue and it works with checked out code.
I created a new folder in the project in my case I used repo, but feel free to use src/repo
In my POM I had a dependency that is not in any public maven repositories
<dependency>
<groupId>com.dovetail</groupId>
<artifactId>zoslog4j</artifactId>
<version>1.0.1</version>
<scope>runtime</scope>
</dependency>
I then created the following directories repo/com/dovetail/zoslog4j/1.0.1 and copied the JAR file into that folder.
I created the following POM file to represent the downloaded file (this step is optional, but it removes a WARNING) and helps the next guy figure out where I got the file to begin with.
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dovetail</groupId>
<artifactId>zoslog4j</artifactId>
<packaging>jar</packaging>
<version>1.0.1</version>
<name>z/OS Log4J Appenders</name>
<url>http://dovetail.com/downloads/misc/index.html</url>
<description>Apache Log4j Appender for z/OS Logstreams, files, etc.</description>
</project>
Two optional files I create are the SHA1 checksums for the POM and the JAR to remove the missing checksum warnings.
shasum -b < repo/com/dovetail/zoslog4j/1.0.1/zoslog4j-1.0.1.jar \
> repo/com/dovetail/zoslog4j/1.0.1/zoslog4j-1.0.1.jar.sha1
shasum -b < repo/com/dovetail/zoslog4j/1.0.1/zoslog4j-1.0.1.pom \
> repo/com/dovetail/zoslog4j/1.0.1/zoslog4j-1.0.1.pom.sha1
Finally I add the following fragment to my pom.xml that allows me to refer to the local repository
<repositories>
<repository>
<id>project</id>
<url>file:///${basedir}/repo</url>
</repository>
</repositories>
This is how we add or install a local jar
<dependency>
<groupId>org.example</groupId>
<artifactId>iamajar</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/iamajar.jar</systemPath>
</dependency>
i gave some default groupId and artifactId because they are mandatory :)
You really ought to get a framework in place via a repository and identifying your dependencies up front. Using the system scope is a common mistake people use, because they "don't care about the dependency management." The trouble is that doing this you end up with a perverted maven build that will not show maven in a normal condition. You would be better off following an approach like this.
Maven install plugin has command line usage to install a jar into the local repository, POM is optional but you will have to specify the GroupId, ArtifactId, Version and Packaging (all the POM stuff).
Using <scope>system</scope> is a terrible idea for reasons explained by others, installing the file manually to your local repository makes the build unreproducible, and using <url>file://${project.basedir}/repo</url> is not a good idea either because (1) that may not be a well-formed file URL (e.g. if the project is checked out in a directory with unusual characters), (2) the result is unusable if this project’s POM is used as a dependency of someone else’s project.
Assuming you are unwilling to upload the artifact to a public repository, Simeon’s suggestion of a helper module does the job. But there is an easier way now…
The Recommendation
Use non-maven-jar-maven-plugin. Does exactly what you were asking for, with none of the drawbacks of the other approaches.
I found another way to do this, see here from a Heroku post
To summarize (sorry about some copy & paste)
Create a repo directory under your root folder:
yourproject
+- pom.xml
+- src
+- repo
Run this to install the jar to your local repo directory
mvn deploy:deploy-file -Durl=file:///path/to/yourproject/repo/ -Dfile=mylib-1.0.jar -DgroupId=com.example -DartifactId=mylib -Dpackaging=jar -Dversion=1.0
Add this your pom.xml:
<repositories>
<!--other repositories if any-->
<repository>
<id>project.local</id>
<name>project</name>
<url>file:${project.basedir}/repo</url>
</repository>
</repositories>
<dependency>
<groupId>com.example</groupId>
<artifactId>mylib</artifactId>
<version>1.0</version>
</dependency>
What seems simplest to me is just configure your maven-compiler-plugin to include your custom jars. This example will load any jar files in a lib directory.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<includes>
<include>lib/*.jar</include>
</includes>
</configuration>
</plugin>
After having really long discussion with CloudBees guys about properly maven packaging of such kind of JARs, they made an interesting good proposal for a solution:
Creation of a fake Maven project which attaches a pre-existing JAR as a primary artifact, running into belonged POM install:install-file execution. Here is an example of such kinf of POM:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>image-util-id</id>
<phase>install</phase>
<goals>
<goal>install-file</goal>
</goals>
<configuration>
<file>${basedir}/file-you-want-to-include.jar</file>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<packaging>jar</packaging>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
But in order to implement it, existing project structure should be changed. First, you should have in mind that for each such kind of JAR there should be created different fake Maven project (module). And there should be created a parent Maven project including all sub-modules which are : all JAR wrappers and existing main project. The structure could be :
root project (this contains the parent POM file includes all sub-modules with module XML element) (POM packaging)
JAR 1 wrapper Maven child project (POM packaging)
JAR 2 wrapper Maven child project (POM packaging)
main existing Maven child project (WAR, JAR, EAR .... packaging)
When parent running via mvn:install or mvn:packaging is forced and sub-modules will be executed. That could be concerned as a minus here, since project structure should be changed, but offers a non static solution at the end
The problem with systemPath is that the dependencies' jars won't get distributed along your artifacts as transitive dependencies. Try what I've posted here: Is it best to Mavenize your project jar files or put them in WEB-INF/lib?
Then declare dependencies as usual.
And please read the footer note.
If you want a quick and dirty solution, you can do the following (though I do not recommend this for anything except test projects, maven will complain in length that this is not proper).
Add a dependency entry for each jar file you need, preferably with a perl script or something similar and copy/paste that into your pom file.
#! /usr/bin/perl
foreach my $n (#ARGV) {
$n=~s#.*/##;
print "<dependency>
<groupId>local.dummy</groupId>
<artifactId>$n</artifactId>
<version>0.0.1</version>
<scope>system</scope>
<systemPath>\${project.basedir}/lib/$n</systemPath>
</dependency>
";
A quick&dirty batch solution (based on Alex's answer):
libs.bat
#ECHO OFF
FOR %%I IN (*.jar) DO (
echo ^<dependency^>
echo ^<groupId^>local.dummy^</groupId^>
echo ^<artifactId^>%%I^</artifactId^>
echo ^<version^>0.0.1^</version^>
echo ^<scope^>system^</scope^>
echo ^<systemPath^>${project.basedir}/lib/%%I^</systemPath^>
echo ^</dependency^>
)
Execute it like this: libs.bat > libs.txt.
Then open libs.txt and copy its content as dependencies.
In my case, I only needed the libraries to compile my code, and this solution was the best for that purpose.
To install the 3rd party jar which is not in maven repository use maven-install-plugin.
Below are steps:
Download the jar file manually from the source (website)
Create a folder and place your jar file in it
Run the below command to install the 3rd party jar in your local maven repository
mvn install:install-file -Dfile= -DgroupId=
-DartifactId= -Dversion= -Dpackaging=
Below is the e.g one I used it for simonsite log4j
mvn install:install-file
-Dfile=/Users/athanka/git/MyProject/repo/log4j-rolling-appender.jar -DgroupId=uk.org.simonsite -DartifactId=log4j-rolling-appender -Dversion=20150607-2059 -Dpackaging=jar
In the pom.xml include the dependency as below
<dependency>
<groupId>uk.org.simonsite</groupId>
<artifactId>log4j-rolling-appender</artifactId>
<version>20150607-2059</version>
</dependency>
Run the mvn clean install command to create your packaging
Below is the reference link:
https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html
A strange solution I found:
using Eclipse
create simple (non-maven) java project
add a Main class
add all the jars to the classpath
export Runnable JAR (it's important, because no other way here to do it)
select Extract required libraries into generated JAR
decide the licence issues
tadammm...install the generated jar to your m2repo
add this single dependency to your other projects.
cheers,
Balint
Even though it does not exactly fit to your problem, I'll drop this here. My requirements were:
Jars that can not be found in an online maven repository should be in the SVN.
If one developer adds another library, the other developers should not be bothered with manually installing them.
The IDE (NetBeans in my case) should be able find the sources and javadocs to provide autocompletion and help.
Let's talk about (3) first: Just having the jars in a folder and somehow merging them into the final jar will not work for here, since the IDE will not understand this. This means all libraries have to be installed properly. However, I dont want to have everyone installing it using "mvn install-file".
In my project I needed metawidget. Here we go:
Create a new maven project (name it "shared-libs" or something like that).
Download metawidget and extract the zip into src/main/lib.
The folder doc/api contains the javadocs. Create a zip of the content (doc/api/api.zip).
Modify the pom like this
Build the project and the library will be installed.
Add the library as a dependency to your project, or (if you added the dependency in the shared-libs project) add shared-libs as dependency to get all libraries at once.
Every time you have a new library, just add a new execution and tell everyone to build the project again (you can improve this process with project hierachies).
For those that didn't find a good answer here, this is what we are doing to get a jar with all the necessary dependencies in it. This answer (https://stackoverflow.com/a/7623805/1084306) mentions to use the Maven Assembly plugin but doesn't actually give an example in the answer. And if you don't read all the way to the end of the answer (it's pretty lengthy), you may miss it. Adding the below to your pom.xml will generate target/${PROJECT_NAME}-${VERSION}-jar-with-dependencies.jar
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<!-- get all project dependencies -->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<!-- MainClass in mainfest make a executable jar -->
<archive>
<manifest>
<mainClass>my.package.mainclass</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- bind to the packaging phase -->
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
I alluded to some python code in a comment to the answer from #alex lehmann's , so am posting it here.
def AddJars(jarList):
s1 = ''
for elem in jarList:
s1+= """
<dependency>
<groupId>local.dummy</groupId>
<artifactId>%s</artifactId>
<version>0.0.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/manual_jars/%s</systemPath>
</dependency>\n"""%(elem, elem)
return s1
This doesn't answer how to add them to your POM, and may be a no brainer, but would just adding the lib dir to your classpath work? I know that is what I do when I need an external jar that I don't want to add to my Maven repos.
Hope this helps.
What works in our project is what Archimedes Trajano wrote, but we had in our .m2/settings.xml something like this:
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://url_to_our_repository</url>
</mirror>
and the * should be changed to central. So if his answer doesn't work for you, you should check your settings.xml
I just wanted a quick and dirty workaround... I couldn't run the script from Nikita Volkov: syntax error + it requires a strict format for the jar names.
I made this Perl script which works with whatever format for the jar file names, and it generates the dependencies in an xml so it can be copy pasted directly in a pom.
If you want to use it, make sure you understand what the script is doing, you may need to change the lib folder and the value for the groupId or artifactId...
#!/usr/bin/perl
use strict;
use warnings;
open(my $fh, '>', 'dependencies.xml') or die "Could not open file 'dependencies.xml' $!";
foreach my $file (glob("lib/*.jar")) {
print "$file\n";
my $groupId = "my.mess";
my $artifactId = "";
my $version = "0.1-SNAPSHOT";
if ($file =~ /\/([^\/]*?)(-([0-9v\._]*))?\.jar$/) {
$artifactId = $1;
if (defined($3)) {
$version = $3;
}
`mvn install:install-file -Dfile=$file -DgroupId=$groupId -DartifactId=$artifactId -Dversion=$version -Dpackaging=jar`;
print $fh "<dependency>\n\t<groupId>$groupId</groupId>\n\t<artifactId>$artifactId</artifactId>\n\t<version>$version</version>\n</dependency>\n";
print " => $groupId:$artifactId:$version\n";
} else {
print "##### BEUH...\n";
}
}
close $fh;
The solution for scope='system' approach in Java:
public static void main(String[] args) {
String filepath = "/Users/Downloads/lib/";
try (Stream<Path> walk = Files.walk(Paths.get(filepath))) {
List<String> result = walk.filter(Files::isRegularFile)
.map(x -> x.toString()).collect(Collectors.toList());
String indentation = " ";
for (String s : result) {
System.out.println(indentation + indentation + "<dependency>");
System.out.println(indentation + indentation + indentation + "<groupId>"
+ s.replace(filepath, "").replace(".jar", "")
+ "</groupId>");
System.out.println(indentation + indentation + indentation + "<artifactId>"
+ s.replace(filepath, "").replace(".jar", "")
+ "</artifactId>");
System.out.println(indentation + indentation + indentation + "<version>"
+ s.replace(filepath, "").replace(".jar", "")
+ "</version>");
System.out.println(indentation + indentation + indentation + "<scope>system</scope>");
System.out.println(indentation + indentation + indentation + "<systemPath>" + s + "</systemPath>");
System.out.println(indentation + indentation + "</dependency>");
}
} catch (IOException e) {
e.printStackTrace();
}
}

Categories