Differing behavior in maven multi-module projects when run in TeamCity vs locally - maven

I have a maven multi-module project on TeamCity. I'm using TeamCity's built in maven 3.5 tooling.
In one of the child projects, in the section of its pom.xml I set "<target.env>dev</target.env>".
Later in the pom I use the properties-maven-plugin to load a file with the name "${target.env}.env.properties"
Locally if I run "mvn package -Dtarget.env=prod" in the parent project, the child project loads prod.env.properties as expected.
If I configure my teamcity build with param("system.target.env", "prod"), I can see "-Dtarget.env=prod" passed to the maven execution in the build log (where teamcity invokes the plexus-classworlds launcher to do so), the child project loads dev.env.properties, breaking the build.
Here's my questions:
Why does the behavior differ?
How do I reconcile this?
Update including some of the information #khmarbaise asked for:
The properties-maven-plugin is being used to load an environment specific set of properties based on which environment the application will run in.
It is set up to choose which file to load based on a system property, and a default value is set in the properties block to avoid having to constantly
add -Dtarget.env=dev during development. The properties-maven-plugin configuration for the child project is as follows:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<executions>
<execution>
<id>load-environment-properties</id>
<phase>validate</phase> <!-- Bound to validate phase to ensure it comes before loading of local.build.properties --> # No local.build.properties on TeamCity, so nothing clobbers this in practice
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>src/main/targetEnvironment/default.env.properties</file>
<file>src/main/targetEnvironment/${target.env}.env.properties</file>
</files>
</configuration>
</execution>
<execution>
<id>write-properties</id>
<phase>generate-resources</phase>
<goals>
<goal>write-project-properties</goal>
</goals>
<configuration>
<outputFile>${project.build.directory}/effective.build.properties</outputFile>
</configuration>
</execution>
</executions>
</plugin>
Version info:
Teamcity version: 2020.2.1 (build 85633)
Maven version: 3.3.9
Java version:
openjdk version "1.8.0_272"
OpenJDK Runtime Environment Corretto-8.272.10.3 (build 1.8.0_272-b10)
OpenJDK 64-Bit Server VM Corretto-8.272.10.3 (build 25.272-b10, mixed mode)
This is the line TeamCity is using to invoke maven, I've only included property definitions that seemed relevant since there were so many that are definitely irrelevant (build numbers, names, timestamps, and other TC specifc, maven agnostic, config)
/usr/lib/jvm/java-1.8.0-amazon-corretto.x86_64/bin/java
-Dclassworlds.conf=/home/ec2-user/BuildAgent/temp/buildTmp/teamcity.m2.conf
-Dmaven.home=/home/ec2-user/BuildAgent/tools/maven3_3
-DskipTests=true
-Dteamcity.build.properties.file=/home/ec2-user/BuildAgent/temp/buildTmp/teamcity.build4380238471360533686.properties
-Dtarget.env=prod
-classpath /home/ec2-user/BuildAgent/tools/maven3_3/boot/plexus-classworlds-2.5.2.jar: org.codehaus.plexus.classworlds.launcher.Launcher -f /home/ec2-user/BuildAgent/work/4508a7116faa21f3/pom.xml -B clean package
The contents of teamcity.m2.conf is as follows:
main is org.apache.maven.cli.MavenCli from plexus.core
set maven.home default ${user.home}/m2
[plexus.core]
load ${teamcity.maven.watcher.home}/*.jar
optionally ${maven.home}/lib/ext/*.jar
load ${maven.home}/lib/*.jar
load ${maven.home}/conf/logging
teamcity.build4380238471360533686.properties contains many properties, the value of target.env within that file is 'prod' as expected

This ended up being a known bug in TeamCity
The underlying issue seems to be that TeamCity uses the MAVEN_OPTS environment variable to pass system properties into maven by default, but properties in MAVEN_OPTS are treated differently from properties passed as arguments to the maven command itself.
The workaround is that for any property "foo" that gets set in a POM section, that you want to override in a TeamCity build, you have to specify it in the "Additional Maven command line parameters" with -Dfoo=value, or, if you're setting the value in a system or build property within the TeamCity build -Dfoo=%system.foo%.

Related

How to get the current build version in Maven/Tycho

I have read that the ${project.version} property should be used to obtain the full version of a project.
But if I use this property in a build to pass the currently built version to an external build process, its values is alway 1.0.0-SNAPSHOT where I would need something like 1.0.0-20160220-1234. The phase in which the external build step is called is `packageĀ“.
The tycho-packaging-plugin is configured to produce timestamps like this:
<configuration>
<format>yyyyMMdd-HHmm</format>
</configuration>
And the resulting artifacts do have timestamps in the versions/names
I use Maven 3.3.3 with Eclipse Tycho 0.24, however, with previous versions of Tycho the behavior is the same. Not sure if Tycho behaves differently than plain Maven in this regard.
The build is run with
mvn clean verify
in the directory of the master pom.
The actual project I am using this for is Extras for Eclipse. The external build step is invoked in line 129 of the 'repository' child pom.
I have also used the echo plug-in in the above-mentioned child pom to diagnose the problem like this:
<plugin>
<groupId>com.soebes.maven.plugins</groupId>
<artifactId>maven-echo-plugin</artifactId>
<version>0.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>echo</goal>
</goals>
</execution>
</executions>
<configuration>
<echos>
<echo>actual version: ${project.version}</echo>
</echos>
</configuration>
</plugin>
The output is the same as the external build receives: 1.0.0-SNAPSHOT.
What do I need to do or which property do I need to use to get the qualified version of the current build?
From my understanding the ${project.version} property should hold the qualified version, e.g. 1.0.0-20160218-1234. But either there is a bug in Maven/Tycho or my understanding is plain wrong. And I would be happy if someone could clarify this.
However, I found the ${qualifiedVersion} property that is set by the tycho-packaging:build-qualifier mojo. This property holds the expected value.
Note that even though the documentation states
is assigned to the project property qualifiedVersion
the property cannot be accessed through ${project.qualifiedVersion}. It needs to be referenced as ${qualifiedVersion}.

Override TeamCity environment variable in Maven

TeamCity provides several environment variables that are made available in Maven's pom.xml. For example, my build agent has a property env.JDK_18 pointing to a JRE installation on the server. I use this property in my configuration of the surefire plugin in Maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<jvm>${env.JDK_18}/bin/java</jvm>
<!-- ... -->
</configuration>
</plugin>
Unfortunately, the environment variable is not available on my local machine. Is there any Maven option to define the environment variable locally (in my pom.xml file) and have TeamCity override it, when unit tests are run on the build server?

Sonar configuration in multimodule maven project using tycho for unit tests and jacoco for coverage

We're using maven to run a sonar analysis and it works well except for the code coverage results with jacoco. We have an eclipse project that uses tycho-surefire-plugin for testing. I've not overriden the argLines properties so solutions involving that line may not be appropiate.
Facts :
Maven structure structure:
parent
master
module 1
module ...
module n
Testing structure:
client.admin (eclipse-plugin packaging)
client.admin.test.fragment (eclipse-test-plugin packaging)
Properties that are correctly set and identified
sonar.junit.reportsPath
sonar.jacoco.reportPath,
sonar.jacoco.itReportPath
sonar.core.codeCoveragePlugin
sonar.language
The main problem is with the following properties
sonar.test
sonar.sources
sonar.java.binaries
As seen in the Testing structure in the client.admin.test.fragment tests are contained in the /src folder and the sources are located in the project client.admin in the /src folder too.
When we run the analysis we get the following error :
[WARN] Coverage information was not collected. Perhaps you forget to include
debug information into compiled classes?
I believe this has to do with the properties sonar.java.binaries that goes looking for the sources in target/classes of the fragment project (client.admin.project) that are in fact located in the host project (client.admin). In the fragment project we've configured sonar.tests and sonar.sources properties so that they call the /src folder of the corresponding projects.
In the sonar Analysis Parameters page there says that only sonar.sources is a maven valid property, sonar.tests and sonar.java.binaries cannot apparently be configured in maven. How then could I attach the binaries to the project. I've tried copying the folder target/classes from the host project but I got the same message. Is there any workaround in maven ?
Edit 1
There is one jacoco.exec file that is generated for the whole project that can be found at the parent folder. This was done configuring the jacoco.destFile and sonar.jacoco.reportPath properties
Jacoco plugin in main pom :
<!-- Jacoco Plugin -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<destFile>${sonar.jacoco.reportPath}</destFile>
<append>true</append>
</configuration>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${sonar.jacoco.reportPath}</dataFile>
<outputDirectory>${jacoco.reports.outputDirectory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Maven Plugin Versions:
sonar: 2.4
jacoco: 0.7.1.201405082137
Properties
<sonar.language>java</sonar.language>
<sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
<sonar.junit.reportsPath>${project.build.directory}/surefire-reports/</sonar.junit.reportsPath>
<sonar.jacoco.reportPath>${basedir}/../../../main/**.master/target/jacoco.exec</sonar.jacoco.reportPath>
<jacoco.reports.outputDirectory>${basedir}/../../../main/**.master/target/site/jacoco</jacoco.reports.outputDirectory>
<sonar.sources>src</sonar.sources>
In the test projects (eclipse-test-plugin) we changed added the property sonar.sources to go find the sources from the src folder of the project that we're testing for example in client.admin.test.fragment we go search the src from the client.admin
The following properties were commented in code because they're not supported in maven according to documentation and to the debug output.
<!--<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>-->
<!--<sonar.tests></sonar.tests>-->
<!--<sonar.java.binaries></sonar.java.binaries>-->
First, you must tell the JaCoCo agent to report all coverage data into one common file. Second, you tell the Sonar JaCoCo plugin to read the coverage data from the aggregated file.
To do so, set the properties "jacoco.destFile" and "sonar.jacoco.reportPath" in your parent pom.xml to the same absolute path, e.g.:
<properties>
<jacoco.destFile>/home/jenkins/jobs/my.project/workspace/parent/target/jacoco.exec</jacoco.destFile>
<sonar.jacoco.reportPath>/home/jenkins/jobs/my.project/workspace/parent/target/jacoco.exec</sonar.jacoco.reportPath>
</properties>
Note that these properties will be inherited to all child poms, so you can't use Maven expressions like ${project.build.directory} because this would evaluate to a different directory for each pom.
You could create a small helper Mojo which automatically resolves an absolute path on the current build machine and then injects the properties into the Maven model.

Generated project with gwt-maven-plugin : eclipse

I created a GWT project with
mvn archetype:generate -DarchetypeGroupId=org.codehaus.mojo -DarchetypeArtifactId=gwt-maven-plugin -DarchetypeVersion=2.5.0
Imported the project in eclipse juno.
First error I get is this :
Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:gwt-maven-
plugin:2.5.0:i18n (execution: default, phase: generate-sources)
In the pom file.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>2.5.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test</goal>
<goal>i18n</goal>
<goal>generateAsync</goal>
</goals>
</execution>
</executions>
<!-- Plugin configuration. There are many available options, see
gwt-maven-plugin documentation at codehaus.org -->
<configuration>
<runTarget>dashboard.html</runTarget>
<hostedWebapp>${webappDirectory}</hostedWebapp>
<i18nMessagesBundle>com.farheap.jsi.dashboard.client.Messages</i18nMessagesBundle>
</configuration>
Also the code contains a GreetingServiceAsync that can not be found.
private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class);
You have two options:
You can add special (non-trivial) org.eclipse.m2e:lifecycle-mapping plugin
configuration to your POM. See here: Why am I receiving a "Plugin execution not covered by lifecycle configuration with GWT" error?
Or mark this issue as to be ignored in Eclipse POM editor, and then call mvn gwt:i18n. You can create a handy short cut launcher for it. Eclipse remembers your decisions what to ignore, it stores it into .settings directory permanently for the project.
In course of typical development localization messages do not change often so the second option is usually more convenient and speeds up build.
This applies for most GWT plugin goals! Even GWT compilation is rarely necessary as DevMode works directly with Java code and not generated JavaScrips. So in practice, you have to call all the goals at least once on the beginning and then live weeks without them; basic Eclipse JDT compilation is sufficient.
If you later decide not to use GWT localization framework in your real app then you can remove goal i18n completely from POM. Calling goal i18n generates file {project}/target/generated-sources/gwt/my/code/client/Messages.java which is required by (vanilla) Sample.java.
Also the code contains a GreetingServiceAsync that can not be found.
Run the build mvn install from command line or Eclipse Run as -> Maven install menu.
In case of command line mvn gwt:generateAsync should be enough. This goal generates {project}\target\generated-sources\gwt\my\code\client\GreetingServiceAsync.java and that is what you missing. Eclipse did not do it for you automatically because it was blocked by previous issue of i18n not being covered by lifecycle configuration. So yes, issues you mention are correlated.

maven dynamic version

I searching a way to dynamise the version of my artifact depending on the profile.
Often I use the -SNAPSHOT suffix when I build for dev or preprod. But the database connection depends on the profile and I never know if the latest SNAPSHOT version was build using the dev or preprod profile.
The idea would be having a version like this
<version>1.0${suffix}</version>
with ${suffix} =
"" when building with prod profile
"-SNAPSHOT" when building with preprod profile
"-DEV-SNAPSHOT" when building with dev profile
Is there a way of achieving this ?
thanks
edit :
My goal is when I go on jenkins to build my jar, I build the same "tagged" version of my project with the 3 profiles and it deploys 3 differents artifacts.
Actually I tag my project and go build with the prod profile, then I modify the version to add -SNAPSHOT, commit, move the tag, re build with preprod profile, and then repeat for the dev profile.
Seeing your answer to #Michael-O comments, i'd recommend to configure the maven assembly plugin to create the final name of the artifact according to a system property set on each profile. For example:
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>create jar according to profile</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>${project.artifactId}-${project.version}_${profile}</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
where ${profile} should be a property set to a different value on each profile (for doing that you can see this question). I dunno if there's a variable to get the profile being currently used to build, that would be another question :)
It is not necessary to reassemble the JAR, I would rather use a standard mech: Simply specify a classifier for your artifact in the jar plugin.
Otherwise I would filter a properties in a given properties file and read that in your app. This what I do, e.g. system.env=prod|test|localdev.

Resources