Archive for May, 2012
Maven Best Practices
TL;DR
Love or hate it… he will stay for a moment.
So let’s apply the best practices to our poms and maven builds.
Make the build reproducible - Always specify a version for Maven2 plugins - Minimize number of SNASPHOT dependencies - Use dependency management section - Beware of relocation in maven repo - After a dependency modification, double check the produced artifacts Use and abuse of modules - more “technical/layered” - more business oriented Make the build maintainable - Prefer default directory layout - Avoid duplication by moving common tags to parent pom - Always specify a version of dependencies in a parent pom - Use Properties Liberally - Minimize the number of Profiles Make the build portable - Don’t commit eclipse and maven artifacts - Don't modify pom/artifactsin your "enterprise" repository
Make the build reproducible
Always specify a version for Maven2 plugins
Wrong way
<plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-surefire-plugin</artifactid> </plugin>
Correct way
<plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-surefire-plugin</artifactid> <version>2.3</version> </plugin>
This matters because if you leave out the version, maven2 defaults to LATEST available.
So this can mean that
- the result of the plugin can become unpredictable if the implementation of the plugin has changed (new/removed feature, …)
- your project unknowingly became automatic beta testers for third party plugins if the latest version is a SNAPSHOT
Newer version fo m2clipse will this show as warnings in the console. See also the maven enforcer plugin
Minimize number of SNASPHOT dependencies
It’s strictly unrecommended to use SNAPSHOT version in your project dependencies since it’s never guaranteed that a SNAPSHOT version is available in any repository. This can lead to well-know build errors due to missing dependencies . So for projects that don’t belong to ‘you’… it’s preferable to use a real version.
how to detect if you have a snapshot version
Use dependency management section
Transitive dependencies is great feature… but in the end you want to be sure of the version you are using and shipping to production
Dependency Management allows to consolidate and centralize the management of dependency versions without adding dependencies which are inherited by all children. This is especially useful when you have a set of projects (i.e. more than one) that inherits a common parent.
Another extremely important use case of dependencyManagement is the control of versions of artifacts used in transitive dependencies. This is hard to explain without an example. Luckily, this is illustrated in the documentation.
<dependencyManagement> <dependencies> ...
Beware of relocation in maven repo
Relocating an artifact is changing the “maven id” (groupId:artifactId) of a project.
xstream:xstream com.thoughtworks.xstream:xstream
One common pitfall in maven relocation is having double jars even with correct usages of dependencyManagement.
Use the m2clipse and his dependency hierarchy views, to detect and exclude the undesired artifacts.
After a dependency modification, double check the produced artifacts
A good habit is to double check your war/ear produced after the addition of a new dependency or the upgrade of an existing one.
2 greats to tools to help you in this
Use and abuse of modules
the modules can be more “technical”
<modules> <module>mymodule_api</module> <!-- service interface, value objects, exception --> <module>mymodule_impl</module> <!-- service & dao implementation --> <module>mymodule_web</module> <!-- web components, controllers, templates.. --> </modules>
or
more business oriented
<modules> <module>contract_modules</module> <module>finance_modules</module> <module>time_modules</module> <module>agenda_modules</module> </modules>
Make the build maintainable
Prefer default directory layout
this will make the plugin configuration more easy :
src/main/java Application/Library sources src/main/resources Application/Library resources src/main/webapp Web application sources (for war packaging) src/test/java Test sources src/test/resources Test resources
Avoid duplication by moving common tags to parent pom
Do you really want to say that you compile for 1.5 jdk in all your projects ?
<!-- Use Java 1.5 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.1</version> <configuration> <fork>${javacFork}</fork> <executable>${javacExecutable}</executable> <verbose>${javacVerbose}</verbose> <compilerVersion>${jdk.version}</compilerVersion> <source>${jdk.version}</source> <target>${jdk.version}</target> </configuration> </plugin>
move this in a parent pom !
for example, in
corporate_base_pom -> app_basecom -> app_module1 -> app_sub_modules jdk 1.5 technical application enterprise repo dependencies dependencies (spring,...) (app_module2,app_module3,..)
Always specify a version of dependencies in a parent pom
prefer a central place for version definition.
Don’t specify version in a specific sub-modules
Use Properties Liberally
Grouping Dependencies with properties… to avoid copy/pasting the version everywhere.
and help upgrading easily to 3.0.5.RELEASE 😉
And move this to a parent pom : cfr Avoid duplication move to parent pom
<properties> <spring.version>3.0.0.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> </dependencies>
Minimize the number of Profiles
Profiles can help a lot… but will inevitability complexify the process.
So don’t use profiles if for example adding a new project with and assembly will do the trick. best practices
- The build must pass when no profile has been activated
- Use profiles to adapt to build-time context, not run-time context, and not (with rare exceptions) to produce alternative versions of your artifact
Remark: The support for profiles outside of the POM or the settings.xml has been removed in Maven 3.x.
Make the build portable
don’t commit eclipse and maven artifacts
to make the checkout easier… avoid commiting the following files and directory
.project .classpath .settings .wtpmodules target
these files are often :
- referencing local settings like JRE name/path/…
- specific to a version of plugins (wtp,…)
so let m2clipse handle this and maintain/generate the .project, .classpath,…
Don’t modify pom/artifacts in your “enterprise” repository
It’s always tempting to fix a pom or a jar in the central repository… don’t do this.
Identify it a special version in your repo ‘artefact-1.0.5.corporatepath’ or manage the correct exclusions.
see log4j example
Jenkins : diskspace requirement tips
Jenkins is great tool but I already wrote the default value don’t help to keep it running for a long time without terrabytes of disks. So let’s manage our diskspace requirement for maven builds using the various option of jenkins and the system groovy scripts.
Disable maven artefact archiving
This option will tell jenkins to collect pom,jars,wars,ears as they are produced by maven. This is rarely usefull when you use an enterprise repository. This option is enabled by default… so if you aren’t using it… disable it !
to do so you need to go in each job definition and check :
Build > Advanced > Disable automatic artifact archiving
As lazy programmer, you may be know that jenkins offer a jenkins script console.
So you can fix artefact archiving in a single batch with the following script :
String format ='%-45s | %-20s | %-10s | %-10s | %-30s' def readonly = false activeJobs = hudson.model.Hudson.instance.items.findAll {job -> job.isBuildable() && job instanceof hudson.maven.MavenModuleSet} def oneline= { str -> if (str==null) return ""; str.replaceAll("[\n\r]", " - ")} println String.format(format , "job", "scm trigger","last status"," logrot","archiving") println "-------------------------------------------------------------------------------------------------------------------------------" activeJobs.each{run -> println String.format(format ,run.name,oneline(run.getTrigger(hudson.triggers.Trigger.class)?.spec), run?.lastBuild?.result, run.logRotator.getDaysToKeep()+" "+run.logRotator.getNumToKeepStr(), ""+run.isArchivingDisabled()) ; if (!run.isArchivingDisabled() && !readonly ) { run.setIsArchivingDisabled(true); run.save() } }
adjust the readonly variable to true to fix them automatically 😉
job | scm trigger | last status | logrot | archiving ------------------------------------------------------------------------------- myproject_ci | 24 * * * * | SUCCESS | -1 10 | true
Discard Old Build
you can easily locate the jobs leaking logs
noLogRotation = hudson.model.Hudson.instance.items.findAll {job -> job.isBuildable() && job.logRotator==null} noLogRotation.each() {println it.name}
and fix them also by providing a logRotator
def jobs = hudson.model.Hudson.instance.items.findAll { !it.logRotator && !it.disabled } jobs.each { job -> // days to keep, num to keep, artifact days to keep, num to keep job.logRotator = new hudson.tasks.LogRotator ( 30, 40, 1, 1) println "$it.name fixed " }