Gradle set name for jar created with spring-boot's assemble - java

I want to create an executable jar with gradle (kotlin-dsl) and I want to give it a custom name. For the executable jar I'm using the spring boot plugin and ./gradlew :app1:assemble:
plugins {
id("myproject.java-application-conventions")
id("org.springframework.boot") version "2.2.2.RELEASE"
}
dependencies {
implementation(project(":lib"))
}
application {
mainClass.set("org.myproject.app.Main")
}
init created two more files, buildSrc/build.gradle.kts:
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
and buildSrc/src/main/kotlin/myproject.java-application-conventions.gradle.kts:
plugins {
id("lomboker.java-common-conventions")
application
}
With ./gradlew :app1:assemble I can create an executable jar but I don't see how I can set its name.
This question deals with naming jars but I don't know how to apply any answers to my problem.
Adding a jar block to my gradle file does not work: Expression 'jar' cannot be invoked as a function. it is interpreted as sun.tools.jar.resources.jar. So I try tasks.jar instead.
For
tasks.jar {
archiveBaseName.set("myapp")
archiveVersion.set("version")
}
./gradlew :app1:jar while building successful creates no jar (it wouldn't be executable anyway) and ./gradlew :app1:assemble ignores the properties and just creates ./app1/build/libs/app1.jar.
Since I'm not using jar but assemble I guess I should use a tasks.assemble block. But that doesn't recognize archiveBaseName or archiveVersion and I don't know what the API is.
This is the page: https://plugins.gradle.org/plugin/org.springframework.boot but I find no API.

assemble is a lifecycle task which means that it doesn’t create anything. Its role is to trigger other tasks that it depends upon and that do have some output. You can see those tasks by running your build with --console=plain.
The task that creates the Spring Boot fat jar is named bootJar. As you can see from its javadoc, it’s a customization of Gradle’s Jar and can be configured in the same way:
tasks.bootJar {
archiveBaseName.set("myapp")
archiveVersion.set("version")
}

Related

Maven Plugin: Manipulate resources during packaging

in my maven project, I've got a xml file in resources. Depending on some input parameter I want the file to be adapted before packaged into a jar or war. Of course, the original file shall not be touched.
It is not an option to create multiple xml-files and select a suitable one, for example, with spring profiles as there can be numerous combinations of contents in the xml file.
So, I thought of creating a maven plugin, that manipulates the file before packaging. Probably, I need to manipulate the file, when maven has copied the file to the target folder but before maven packages the file into the jar/war.
#Mojo(name = "manipulate-xml", defaultPhase = LifecyclePhase.PREPARE_PACKAGE)
public class MyMojo extends AbstractMojo {
#Parameter(defaultValue = "${project}", required = true, readonly = true)
MavenProject project;
#Parameter(property = "option")
String option;
public void execute() throws MojoExecutionException {
if (option.equals("optionA")) {
// get file from target and manipulate
} else if (option.equals("optionB")) {
// get file from target and manipulate
}
}
}
Then, I could embedded the maven plugin into my project and build the project with
mvn clean package -Doption=optionA
However, now I am stuck. I do not know, how to get the file from target and even if this is the right approach.
Besides, is it possible during the packaging to prevent some dependencies from being packaged into the jar/war?
I appreciate any help.
Depending on what manipulating means, you can use the possibilities of the maven resources plugin (https://maven.apache.org/plugins/maven-resources-plugin/index.html).
If you need to modify some simple values inside the xml, use properties in the xml and let the resources plugin replace them during build. The values for the build can be either in the pom.xml or given to maven via -Dproperty=value.
If you want to select a different files, define multiple maven profiles, in each you can configure the resources plugin to copy only the wanted files and then select the correct profile in the build.
If the built-in possibilities are not enough, you might even program your own filter for the resources plugin, that might be easier than writing a custom full fledged maven plugin.

Unable to integration-testing gradle multi-module Spring-Boot-Application

Within a Gradle multi-module project with the bootstrapping in its own module I'm unable to use MockMvc, because its need to reference the bootstrapping-module. I'm not sure if I have misconfigured something. The basic structure is:
module: a module containing some REST-Services and needs a testImplementation-Dependency on starter
starter: the bootstrapping-module which gets the spring-boot-plugin applied and depends on module
I have set up a minimal example on github using Spring-Boot 2.3.1.RELEASE and Gradle 6.4 with the following configuration:
./settings.gradle.kts
rootProject.name = "spring-multimodule-integrationtest"
include("starter", "module")
./build.gradle.kts
subprojects {
repositories {
jcenter()
}
dependencies {
apply(plugin = "java-library")
"testImplementation"("junit:junit:4.12")
}
}
./starter/build.gradle.kts
plugins {
id("org.springframework.boot") version "2.3.1.RELEASE"
}
dependencies {
implementation(project(":module"))
}
./module/build.gradle.kts
dependencies {
testImplementation(project(":starter"))
}
The starter-module contains only one a single class "Starter" referencing the module-module:
public class Starter {
public String info() { return "starter"; }
public static void main(String[] args) {
System.out.println(new Starter().info() + " and " + new Module().info());
}
}
The module-module (*sigh I should have chosen a different name for this module) contains only this implemenation-class:
public class Module {
public String info() { return "module"; }
}
Additionally, the module-module has the following test-class doing the integration-test:
public class IntegrationTest
{
#Test public void testSomeLibraryMethod() {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
Starter.main(new String[0]);
assertEquals("starter and module\n", out.toString());
}
}
This code runs fine until the applying of the spring-boot-plugin within "./starter/build.gradle.kts". When the tasks "clean test" issued on the shell I get:
❯ ./gradlew clean test
> Task :module:test FAILED
de.kramhal.multi.IntegrationTest > testSomeLibraryMethod FAILED
java.lang.NoClassDefFoundError at IntegrationTest.java:17
Caused by: java.lang.ClassNotFoundException at IntegrationTest.java:17
1 test completed, 1 failed
This problem does not occur, when tests are executed within the IDE (IntelliJ to be exact).
I already tried unsuccessfully to use the spring-dependency-management as suggested in this answer (as well as in several other answers).
What have I done wrong?
First off, I would recommend restructuring your project so you don't have cyclic dependencies. As it is now, in order to build starter, you need to build module. And in order to test module, you need to build starter. Gradle can do it, but it is usually a smell.
In terms of troubleshooting: when you get a test failure like this, look at the test report as that has the full stack trace. You should see that it complains that it can't find the Starter class (Caused by: java.lang.ClassNotFoundException: de.kramhal.multi.Starter), which is of cause in the starter module.
You mentioned the spring-dependency-management plugin, but that is only relevant for managing Maven dependencies, and not project dependencies like this. So it is not helpful here.
I am not entirely sure if this is Windows specific or not as I remember there were some discussions around performance a while back when having a lot of classes. But I believe the java-library plugin will look for jar files in other projects, and not the folder for compiled classes. This is a problem for you since the spring-boot plugin will by default disable the standard jar task and instead create "fat" a jar file through the bootJar task. Because you need both the fat jar for packaging the application to run stand-alone but also the normal jar for consuming it as a dependency, you need to do some tweaks to the starter project (Kotlin DSL):
tasks {
jar {
enabled = true
}
bootJar {
archiveClassifier.set("boot")
}
}
This will enable the normal jar file, but because the name will conflict with the one produced by the bootJar task, you need to rename one of them. I chose to rename the bootJar one.
I don't know why the test works for you in IntelliJ as that should, by default, delegate everything to Gradle. But maybe you have an old version, or done some manual configuration to let IntelliJ both compile and run your tests.

How to get the next build number in Gradle

Is there any way to get the next version when publishing to a repository in gradle?
For e.g. if I have the version 3.0.1 in my repository I want the published version to be 3.0.2.
ivy has a task for ant named buildnumber which does exactly that:
<project xmlns:ivy="antlib:org.apache.ivy.ant"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<target name="ivyBuildNumber" description="Use ivy get the next build number">
<ivy:buildnumber
resolver="url-chain"
organisation="${ivy.organisation}"
module="${ivy.module}"
revision="${version.base}"/>
<echoproperties prefix="ivy.new."/>
</target>
Is there a way to do so in gradle? if not how can I access ivy tasks from gradle's ant?
In my build.gradle I calling to the ant
ant.importBuild 'build.xml'
I don't think there is support in Gradle, but you can try to use the Ant task.
https://docs.gradle.org/current/userguide/ant.html#sec:import_ant_build
Another way to do this is to use some sort of plugin, or customized task for managing the version.
Plugin: https://github.com/researchgate/gradle-release
Custom task: https://www.tikalk.com/devops/increment-version-numbers-in-gradle/
Yes, you can access ivy tasks from the ant script by importing ant's build.xml file to gradle's build.gradle file. Following is the syntax to do so.
ant.importBuild 'build.xml'
Please refer : https://docs.gradle.org/current/userguide/ant.html#sec:import_ant_build
I recommend you to use ResearchGate release plugin
https://github.com/researchgate/gradle-release
It has a pretty documentation. Easy to read.
Also, check out how I used it in my personal project.
https://github.com/vatolinrp/bitcoin-esb/blob/master/build.gradle
It would be a nice example for you.
After a long work, I managed to do that.
In my build.gradle I added this following code
ant.importBuild 'build.xml'
task getNextBuild(dependsOn : ivyBuildNumber) {
doLast{
def nextVersion = ant.properties['ivy.new.revision']
println nextVersion
}
}
I imported my ant build file, and created a task that calls the ivy buildnumber task.
There is my build.xml
<project xmlns:ivy="antlib:org.apache.ivy.ant">
<target name="ivyBuildNumber">
<path id="ivy.classpath" path="lib/ivy.jar" />
<typedef resource="org/apache/ivy/ant/antlib.xml" uri="antlib:org.apache.ivy.ant" classpathref="ivy.classpath" />
<ivy:buildnumber
organisation="daniel"
module="hello"/>
<echoproperties prefix="ivy.new."/>
</target>
</project>
Because my IDE (Intellij), didn't have ivy.jar in the content,
I imported the ivy.jar from my root dir (lib/ivy.jar)
For this exact behavior, Ivy buildnumber task can be invoked using pure Gradle without importing the Ant build:
configurations {
antTasks // define a new configuration
}
repositories {
mavenCentral()
}
dependencies {
antTasks("org.apache.ivy:ivy:2.4.0") // add Ivy library to it
}
ext {
// define the Ivy task, using the extra configuration as classpath extension
ant.taskdef(name: "ivyBuildNumber",
classname: "org.apache.ivy.ant.IvyBuildNumber",
classpath: configurations.antTasks.asPath)
ant.ivyBuildNumber(organisation: "daniel", module: "hello")
nextVersion = ant.properties["ivy.new.revision"]
}
task demo {
doLast {
println nextVersion
}
}
In general, Gradle doesn't have any bundled equivalent to Maven Release Plugin, so one has to rely on plugins. One solid plugin is gradle-release by ResearchGate, the other is axion by Allegro Tech. The former is classic Maven-style versioning, the latter takes SCM itself as the only source of truth, eliminating the versioning in the build files. But neither of these plugins does provide the exact requested behavior.
My personal take on the versioning problem was initially to use some plugins. Since I use Bamboo as CI server at work, literally everything I did with release plugins using Gradle crashed on CI server sooner or later. It might have worked for some weeks, but every server update brought some problems. I ended up using SCM-less approach with a simple convention: use branch name as base version, concatenate it with build number (both values are provided by the CI server):
ext {
branch = System.getProperty("branch", "develop")
buildNumber = System.getProperty("buildNumber", "latest")
isRelease = System.getProperty("isRelease", "false").toBoolean()
artifactVersion = "${branch}${(isRelease ? ".$buildNumber" : "-SNAPSHOT")}"
}
CI server then can be set up for executing the following command
./gradlew -DisRelease=true -Dbranch=${git.branch} -DbuildNumber=${build.number} mavenPublish
when 'Release' button is pushed. For example, build 12 of the 3.0 branch will produce version 3.0.12 in the binary repository.
The advantages are:
+ the version comes for free, assuming the branches are named accordingly
+ the auto-incremented build number also comes for free
+ one can easily publish custom revisions
+ no plugins means no problems with Gradle version updates
+ this approach is dead simple and always works
The downsides are:
- additional script tasks are required for tags
- some build numbers will be skipped, obviously (e.g. next version after 3.5.76 can be 3.5.84)

Gradle with Eclipse - resources are not filtered

I have a gradle configuration setup to filter some resources and substitute properties depending on the environment (e.g. dev, production). These are located at:
src/main/resources/config.xml
WebContent/WEB-INF/web.xml
An example of a property from my web.xml file is below:
<context-param>
<param-name>server.url</param-name>
<param-value>${server_url}</param-value>
</context-param>
An excerpt from my build.gradle is presented below:
apply plugin: 'eclipse-wtp'
processResources {
expand(props) // filter properties by environment
exclude 'log4j.properties'
}
war {
from 'WebContent'
exclude('WEB-INF/web.xml')
webInf {
from 'WebContent/WEB-INF/web.xml'
expand(props)
}
webXml = null
}
This works fine when I build a war from the command-line, but when I use this configuration from Eclipse it does not seem to filter the resource appropriately.
I previously had the Maven plugin working where the resources would get filtered as a part of the Eclipse build. Is it possible to get Eclipse to filter the resources?
You should use webAppDirNameproperty for your project and remove the manuell stuff in war { ... } block. I am not sure which Gradle version introduced this property but at least 2.0. See also http://www.gradle.org/docs/current/userguide/war_plugin.html.
Afterwards gradle eclipse should generate proper Eclipse artifacts to build your war or directly deploy to any server using wtp!

Gradle tomcat plugin and properties files

I would like to use the gradle tomcat plugin in order to do integration tests with gradle. The current project relies on some .properties files underneath the running tomcat's catalina.base directory (cannot be changed because another dependent project relies on them as well).
Does anybody know how to deploy those files to the embedded tomcat instance?
I figured out it's just a simple copy task issue. Here's my solution:
task copyDMConfigFiles << {
def srcDir = new File('src/test/resources/conf')
if(!srcDir.isDirectory())
println "Outlet configuration files missing!!!"
def buildDir = new File('build/tmp/tomcatRunWar/conf')
if(!buildDir.isDirectory()) {
println "Outlet target directory missing. Creating one"
buildDir.mkdirs()
}
copy {
from(srcDir)
into(buildDir)
include '**/*.properties'
include '**/*.xml'
}
copy {
from('src/main/webapp/WEB-INF')
into('build/tmp/tomcatRunWar/work/Tomcat/localhost/digitalmedia/WEB-INF')
include 'web.xml'
include 'dispatcherservlet.xml'
}
}

Categories