Apache Maven is simple, yet quite powerful; with a few tricks here and there, you can greatly simplify and optimize your dev experience.
Working on multiple, non-colocated modules
Say you have two utility modules foo
and bar
from one master project A
, and another project B
which pulls in foo
and bar
.
While working on B
, you realize that you need some occasional tweaks to be done on foo
and bar
as well; however, since they are on a different project, you would usually need to
- switch to
A
- make the change
mvn install
- switch back to
B
- and "refresh" dependencies (if you're on an IDE).
Every time there's a need to make an upstream change.
With Maven, instead you can temporarily "merge" the three pieces with a mock master POM that defines foo
, bar
and B
as child modules:
<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> <!-- blah blah blah --> <modules> <module>foo</module> <module>bar</module> <module>B</module> </modules>
What's in it for me?
IDEs (like IntelliJ IDEA) will identify the root as a multi-module project; which means you'll be able to:
- browse seamlessly from one module to other. No more decompilation, bytecode incompatibilities or source maps; handy when you're searching for usages of some class or method - or refactoring one - across your "composite" project's scope.
- modify sources and resources of each module on demand, within the same project window. The IDE will automatically recompile the changes and add everything to the runtime classpath; handy for in-IDE testing and debugging with hot reload.
- version seamlessly. If the three modules are under different VCS roots, IDEs like IDEA will track each repo individually; if you commit one set of changes, each repo will have a new commit reflecting its own part of the change; all with the same message!
Meanwhile, plain Maven will build foo
/bar
(as required) and B
in proper sequence, if the root module is built - exactly what we would have wanted.
Relative paths, FTW!
Even if the modules are scattered all over the filesystem, Maven can resolve them easily via relative paths:
<modules> <module>../foo</module> <module>grandpa/papa/bar</module> <module>../../../../../media/disk2/repos/top-secret/B</module> </modules>
Drop that clean
Perhaps the most used (hence the most misused) Maven command is:
mvn clean install
The de-facto that gets run, right after you make some change to your project.
And, for most scenarios, it's grossly overkill.
From scratch?
The command combines two lifecycle phases - "stages" of a Maven build process. Phases have a definite sequence; so if you request some phase to run, all previous phases in its lifecycle will run before it. But Maven plugins are smart enough to skip their work if they detect that they don't have anything to do; e.g. no compilation will happen when compiled classes are up-to-date.
Now, clean
is not part of the default lifecycle; rather it is used to start from scratch by removing the entire target
directory. On the other hand, install
is almost the end of the line (just before deploy
in the default lifecycle).
mvn clean install
will run both these phases; and, thanks to clean
, everything in between as well.
It's handy when you want to clean up everything, and end up with the latest artifacts installed into your local Maven repo. But in most cases, you don't need all of that.
Besides, install
will eventually clutter your local Maven cache; especially if you do frequent snapshots/releases with MB- or GB-sized bundles.
Be lazy; do only what's necessary!
If you updated one source file, and want to propagate it to the target/classes
dir:
mvn compile
where Maven will auto-detect any changes - and skip compilation entirely if there are none.
If the change was in a test class or resource:
mvn test-compile
will get it into target/test-classes
.
Just to run the tests (which will automatically compile any dirty source/test classes):
mvn test
To get a copy of the final bundle in target
:
mvn package
As you might often want to start with a clean slate before doing the packaging:
mvn clean package
Likewise, just specify the end phase; or both start and end goals, if you want to go clean to some extent. You will save a whole lot of time, processing power, and temper.
Meanwhile in production...
If your current build would go into production, just forget most of the above ;)
mvn clean package
While any of the "sub-commands" should theoretically do the same thing, you don't want to take chances ;)
While I use package
above, in a sense install
could be better as well; because then you'll have a copy of the production artifact in your .m2/repository
- could be a lifesaver if you lose the delivered/deployed copy.
More skips...
--no-snapshot-updates
If you have watched closely, a build that involves snapshot dependencies, you'd have noticed it taking several seconds to search for Maven metadata files for the snapshots (and failing in the end with warnings; unless you have a habit of publishing snapshot artifacts to remote).
This is usually useless if you're also builidng the snapshot dependencies locally, so you can disable the metadata check (and snapshot sync attempt) via the --no-snapshot-updates
or -nsu
parameter.
Of course -o
would prevent all remote syncs; but you can't use it if you actually want to pull some of the dependencies, in which case -nsu
would help.
You can skip compile!
Like the (in)famous -Dmaven.test.skip
- or -DskipTests
, you can skip the compilation step (even if there are code changes) via -Dmaven.main.skip
. Handy when you just want to run tests, without going through the compilation overhead; if you know the stuff is already compiled, of course. Just like -DskipTests
- but the other way around!
(Kudos to this SO post)
Continuation: -rf
You might already know that, if a module fails in the middle of a build, you can resume the build from that point via the -rf :module-name
parameter.
This parameter also works out of the blue; it's not limited to failure scenarios. If you have 30 modules but you just want to build the last 5, just run with -rf :name-of-26th-module
.
Tasty testing tricks
Inheriting tests
Generally Maven artifacts don't include test classes/resources. But there are cases where you want to inherit some base test classes into child modules.
With the
specifier, you can inherit an artifact that only contains test classes and resources:
<dependency> <groupId>com.acme</groupId> <artifactId>foo</artifactId> <version>3.2.1</version> <type>test-jar</type> <scope>test</scope> </dependency>
The corresponding build configuration on the "depended" module would be like:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <executions> <execution> <goals> <goal>test-jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
One caveat is that transitive test dependencies are not inherited in this process, and have to be manually specified again at each usage of the test JAR. (At least I have't come across a better alternative.)
If you're working on one test case, don't run the whole bunch.
-Dtest=com.acme.my.pkg.Test
can single-out your WIP test, so you can save plenty of time.
Depending on your test runner, -Dtest
may support wildcard selectors as well.
Of course you can temporarily modify the
or
array of your test plugin config (e.g. SureFire) to limit the set of runnable tests.
Debuggin' it
Debug a Maven test?
If your test runner (e.g. SureFire) allows you to customize the command line or JVM args used for the test, you can easily configure the forked JVM to wait for a debugger before the test starts executing:
<build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <!-- ... --> <configuration> <argLine>-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,address=8000</argLine>
Debug Maven itself?!
If you're writing or troubleshooting a Maven plugin or extension it would be handy to run Maven itself in debug mode.
Maven is ultimately Java, so you can simply grab the ultimate command that gets run when you invoke, and re-run it with the -Xdebug
... params.
But Maven already has a way cooler mvnDebug
command that does this automatically for you. It has the same syntax as mvn
so is pretty easy to get used to.
Once invoked, it will by default listen on port 8000 for a debugger, and start executing as soon as one gets attached; and stop at breakpoints, reveal internal states, allow expression evaluations, etc.
Look at the logs!!!
This deserves its own section, because we are very good at ignoring things - right in front of our eyes.
Right at the start
I bet there's 95% chance that Maven will be spewing off at least one [WARNING]
at the start of your build. While we almost always ignore or "postpone" them, they will bite back at some point in the future.
Right before the end
If there's a compile, test or other failure, Maven will try to help by dumping the context (stacktraces, [ERROR]
s etc.). Sometimes you'd need to scroll back a page or two to find the actual content, so don't give up and smack your computer in the face, at the first attempt itself.
Recently I spent almost an hour trying to figure out why a -rf :
'd build was failing; while the same thing was succeeding when starting from scratch. In the end it boiled down to two little [WARNING]
lines about a systemPath
dependency resolution error. Right in front of my eyes, yet so invisible.
Desperate times, -X
measures
In some cases, where the standard Maven output is incapable of pinpointing the issue, running Maven in trace mode (-X
) is the best course of action. While its output can be daunting, it includes everything Maven (and you) needs to know during the build; plugin versions, compile/test dependency trees, external command invocations (e.g. tests); so you can dig deep and find the culprit.
As always, patience is a virtue.
Final words
As with anything else, when using Maven,
- know what you're doing.
- know what you really want to do.
- believe Maven can do it, until proven otherwise ;)
No comments:
Post a Comment