====== Introduction ====== Maven is yet another build automation tool, comparable to e.g. ant; check the [[http://maven.apache.org/|project homepage]] and the [[http://en.wikipedia.org/wiki/Apache_Maven|wikipedia]] page. ===== Characteristics ===== * convention over configuration * tries to enforce best practices and standards * tries to enforce clean dependency management. Directory layout of a maven project ├── pom.xml ├── src │   ├── main │   │   ├── java │   │   │   └── com.company.project.* │   │   └── resources │   └── test │   ├── java │      │   └── com.company.project.* │   └── resources └── target ===== Project Object Model (POM) ===== The POM is the heart of all Maven projects. It is an XML file containing all the information and configuration details required for a project. Example for a minimal pom.xml 4.0.0 com.mycompany.app my-app 1.0.0 The default packaing type is jar. To build a war, just add: war For more details go [[http://maven.apache.org/pom.html#Quick_Overview|over there]] [[http://maven.apache.org/guides/introduction/introduction-to-the-pom.html|or there]]. ====== Basics ====== ===== Creating a project ===== mvn archetype:generate # pick type and answer basic questions This creates the pom.xml and a basic directory structure. If you want to create it by hand instead: mkdir -p src/main/{java,resources} mkdir -p src/test/{java,resources} touch pom.xml ===== Building ===== mvn verify # build, test, package, integration-test and verify see http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html ===== Local Install ===== Install your project to the **local repository** mvn install ===== Deploy to Maven repository ===== **Configure the target repository** in pom.xml: internal.repo Internal Releases http://192.168.1.10:8081/nexus/content/repositories/releases/ internal.repo Internal Snapshots http://192.168.1.10:8081/nexus/content/repositories/snapshots/ Dont forget to **set your credentials** in ~/.m2/settings.xml : internal.repo deployment deployment123 Deploy to the repository: mvn deploy ===== Deploy to JBoss ===== Note: replace /opt/jboss with /your/path/to/jboss and also make sure to use the correct server IP. ==== JBoss / WildFly ==== This works about the same since JBoss 7 up to (currently) WildFly 10 === Server Preparations === If you only want to deploy to localhost no preparation is required. Just start the server. To connect to a remove server the management interface must be bound to an interface. This can be achieved with the startup parameter -bmanagement. In the example webservices are bound 0.0.0.0, which means everywhere (the opposite of the default, which is localhost only). /opt/jboss/bin/standalone.sh -b 0.0.0.0 -bmanagement your.server.ip.address Additionally you should create a management user by using this script (changes /opt/jboss/standalone|domain/configuration/mgmt-users.properties): /opt/jboss/bin/add-user.sh === JBoss 7: jboss-as-maven-plugin === Add the [[jenkins&#continuous_delivery_with_jenkins|jboss-as-maven-plugin]] to your pom.xml and also configure the management user you just created. org.jboss.as.plugins jboss-as-maven-plugin 7.3.Final user password Then you are ready to rumble: mvn clean package # local deployment mvn jboss-as:deploy # remote deployment # this seems to be buggy: if a hostname is specified in pom.xml it will be ignored at the command line! mvn -Djboss-as.hostname=your.server.ip.address jboss-as:deploy # undeploy mvn jboss-as:undeploy Note, that you will not find the deployment as expected in ''/opt/jboss/standalone/deployments'', but it is hidden away in a zip archive named with a sha1-hash in ''/opt/jboss/standalone/data/content'' - to avoid manual tampering. You can see and manage (disable / undeploy) all deployments in the management console on port 9990 though (e.g. http://localhost:9990). Actually an entry is added to the end of ''/opt/jboss/standalone/configuration/standalone.xml'' for each deployment: Full documentation: https://docs.jboss.org/jbossas/7/plugins/maven/latest/index.html === WildFly 8 and higher: wildfly-maven-plugin === Basically this works the same as the old jboss plugin. Documentation: https://docs.jboss.org/wildfly/plugins/maven/latest/ ==== Older JBoss Versions ==== To deploy via JMX over HTTP mvn jboss:deploy To copy the file directly to the deploy directory of a local server: export JBOSS_HOME=/opt/jboss mvn jboss:hard-deploy ====== Maven + Eclipse ====== * Don't check in the Eclipse-specific config files. Maven can generate them for you! * ''svn propedit svn:ignore .'' * add one line for .project .classpath and .settings each ===== Eclipse plugins ===== * [[http://eclipse.org/m2e/ | m2e Plugin]] ===== Checking out a Maven project from SCM ===== ==== Using m2e Import ==== With m2e installed >File --> Import.. --> Maven --> Project from SCM //should// work. But the Plugin does not work very well with the Eclipse Team provider (in Eclipse Juno): http://stackoverflow.com/questions/6729511/checkout-maven-project-from-scm-no-connectors === Workaround === With the m2e Plugin installed. 1. Check out the project using normal Eclipse Team provider. The project will not function as java project just yet. 2. Close the project. Go to console and enter mvn eclipse:clean mvn eclipse:eclipse 3. Re-open the project. Right click the project: > Configure.. --> Convert to Maven project (ignore the errors). 4. Close the project. Go to console and rm .classpath 5. Re-open the project. Right click the project: >Maven --> Update Project ===== Convert Eclipse project to Maven ===== Remove Eclipse artefacts * .classpath * .settings * .project Close the project. Go to console and enter mvn eclipse:clean mvn eclipse:eclipse Re-open the project. Right click the project: > Configure.. --> Convert to Maven project (ignore the errors). Close the project. Go to console and rm .classpath Re-open the project. Right click the project: >Maven --> Update Project Be sure to set all Maven dependencies for your project. ===== Create new JBoss Service ===== Create a pom.xml by adapting an already existing one (by hand) Create the src folder hierarchy by hand: mkdir -p src/main/java mkdir -p src/main/resources mkdir -p src/test/java mkdir -p src/test/resources Then import the pom.xml into Eclipse as described above. If you Eclipse has a full installation of JBoss Tools (tested with Eclipse Indigo) the created project will be a web project and can be dragged and dropped to servers within Eclipse. ====== Testing ====== ===== Integration Tests ===== Often you have (hopefully) fast running unit tests and slower running integration tests. Maven provides two different goals to cleanly seperate these two: * Unit Tests * are fast * are run using the maven-surefire-plugin during the goal "test" * Test classes need to be located in src/test/java/ * Class names need to end in "Test" (eg. RouterTest.java ) * run with //mvn package// * Integration Tests * may be slow * are run by the maven-failsafe-plugin during the goal "integration-test" * Test classes also need to be located in src/test/java/ * Class names need to end in "IT" (eg. RouterIT.java) * run with //mvn verify// ==== Setting up Integration Tests ==== * Write a JUnit Integration Test (class name must end in "IT") * Configure the maven-failsafe-plugin in pom.xml org.apache.maven.plugins maven-failsafe-plugin 2.12.3 integration-test verify * Run integration tests with //mvn verify// ====== Building an executable jar ====== Use the [[https://maven.apache.org/plugins/maven-shade-plugin/usage.html|maven-shade-plugin]] to build an uber-jar with ''mvn package''. Note, that you have to care about shadowing problems yourself and handle them with transformers, e.g. use an AppendingTransformer to prevent SQLite and PostgreSQL overwriting META-INF/services/java.sql.Driver. org.apache.maven.plugins maven-shade-plugin 2.4.1 package shade my.package.Main META-INF/services/java.sql.Driver *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA The hint on dealing with signed jars is from http://zhentao-li.blogspot.co.at/2012/06/maven-shade-plugin-invalid-signature.html. The deprecated way is to use the maven-assembly-plugin: org.apache.maven.plugins maven-assembly-plugin 2.2.1 true jar-with-dependencies com.company.my.MainClass package-jar-with-dependencies package single If the execution goal is not enabled, the executable jar can be built manually using mvn clean verify assembly:single # must be all in one command. otherwise it does not work. no idea why. ====== Dependency Management ====== ===== Declaring Dependencies ===== In your **pom.xml** junit junit 4.10 test ===== Dependencies in an internal repository ===== Assuming your company builds a library which is available from an internal maven repository you can specify that dependency same as above, but you also have to tell maven about your local repo. There are two ways to do this * Enter the repository in your pom.xml local-maven-repo http://192.168.1.10:8081/nexus/content/groups/public/ always always * enter the repository in your ~/.m2/settings.xml default true local-maven-repo Local Repo always always http://192.168.1.10:8081/nexus/content/groups/public/; ==== Version ==== When declaring a "normal" version such as 4.10 for JUnit, internally this is represented as "allow anything, but prefer 4.10" === Version Ranges === You can specify a range of versions that would satisfy a given dependency: ^ Range ^ Meaning ^ |(,1.0] | x < = 1.0 | |1.0 | "Soft" requirement on 1.0 (just a recommendation - helps select the correct version if it matches all ranges) | |[1.0] | Hard requirement on 1.0 | |[1.2,1.3] | 1.2 < = x < = 1.3 | |[1.0,2.0) | 1.0 < = x < 2.0 | |[1.5,) | x > = 1.5 | |(,1.0],[1.2,) | x < = 1.0 or x > = 1.2. Multiple sets are comma-separated | |(,1.1),(1.1,) |This excludes 1.1 if it is known not to work in combination with this library | Default strategy: Of the overlapping ranges, the highest soft requirement is the version to be used. If there are no soft requirements inside the prescribed ranges, the most recent version is used. If that does not fit the described ranges, then the most recent version number in the prescribed ranges is used. If the ranges exclude all versions, an error occurs. * http://docs.codehaus.org/display/MAVEN/Dependency+Mediation+and+Conflict+Resolution#DependencyMediationandConflictResolution-DependencyVersionRanges ==== Scope ==== Valid scopes are * **compile** - the default. Makes the dependency available in all classpaths of a project. Propagated to dependent projects. * **provided** - indicates you expect the JDK or a container (eg. JBoss) to provide the dependency at runtime. --> Dependency is only available on the compilation and test classpath, and is not transitive. * **runtime** - dependency is not required for compilation, but is for execution. --> Dependency is in the runtime and test classpaths, but not the compile classpath. * **test** - dependency is not required for normal use of the application, and is only available for the test compilation and execution phases. * **system** - This scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository. * **import** - used on a dependency of type pom in the section. It indicates that the specified POM should be replaced with the dependencies in that POM's section. ===== Transitive Dependencies ===== Transitive dependencies are a new feature in Maven 2.0. This allows you to avoid needing to discover and specify the libraries that your own dependencies require, and including them automatically. This feature is facilitated by reading the project files of your dependencies from the remote repositories specified. In general, all dependencies of those projects are used in your project, as are any that the project inherits from its parents, or from its dependencies. ===== Unmanaged Dependencies ===== A clean way to handle unamanaged dependencies is described here: https://devcenter.heroku.com/articles/local-maven-dependencies This is also useful in the case not all people working with a maven project can resolve all dependences (because some dependencies are only available on internal repositories). The internal-only dependencies can be published with this method. * Use ''mvn'' to deploy the library to a local repository. This will create the correct directory structure plus meta files. mvn deploy:deploy-file \ -Durl=file:my_repository \ -Dfile=mylib-1.0.jar \ -DgroupId=com.example \ -DartifactId=mylib \ -Dpackaging=jar \ -Dversion=1.0 * Add the local repository to you pom.xml project.local project file:${project.basedir}/my_repository always always * Add the dependency like you normally would: com.example mylib 1.0 ===== Useful Links ===== * http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html ====== Release Management ====== **Use Case:** you are developing on a version like "2.1.5-SNAPSHOT". Once the version is stable you want to make a 2.1.5 release, tag it in SVN and start working on the next version (2.1.6-SNAPSHOT or something like that). The [[http://maven.apache.org/plugins/maven-release-plugin/ | Maven Release Plugin]] supports this. ===== Set-Up ===== ==== Configure SCM ==== Make sure your pom.xml contains a valid SCM section (more info [[http://maven.apache.org/pom.html#SCM]] === SVN === The typical directory hierarchy with trunk, tags and branches must be used and created manually. If e.g. the directory tags is missing release:perform will fail. scm:svn:http://svn.my.company.com/repository scm:svn:http://svn.my.company.com/repository/trunk/my-project scm:svn:http://svn.my.company.com/repository/trunk/my-project === Mercurial === The whole project just resides in one directory. Separate directories for trunk/tags/branches are not required, this is done automatically via the tagging and branching functionality of mercurial. The urls are therefore always the same (and must not be a subdirectory of the project) scm:hg:https://hg.my.company.com/repository scm:hg:https://hg.my.company.com/repository scm:hg:https://hg.my.company.com/repository Also the -part of your pom.xml should add a dependency to the maven-scm-provider-hg. (Probably that's optional .. test this in the future) maven-release-plugin 2.4.1 org.apache.maven.scm maven-scm-provider-hg 1.8.1 ==== Optional: Specify a custom location for the tags ==== org.apache.maven.plugins maven-release-plugin http://svn.my.company.com/repository/tags ==== Optional: Specify deployment location ==== see [[maven#deploy_to_maven_repository]] ==== Handling SCM credentials ==== This should work both for SVN and mercurial: Either handle them externally: ensure you DON't have to enter your SCM login each time (i.e. your login information is stored somewhere by SVN) **OR** edit ~/.m2/settings.xml and add a section server-hostname-or-IP username mellon **OR** add the credentials explicitly mvn release:perform -Dusername=user -Dpassword=password ===== Release Workflow ===== **Commit changes & check Javadoc** * Make sure everything is committed to your VCS * Check for javadoc errors with mvn site (mvn javadoc:javadoc does not seem to build everything) - this avoids annoying problems at the very end (mvn release:perform)) * In case your site build takes ages due to errors like "[ERROR] Unable to determine if resource batik:batik-util:jar:1.6-1:compile exists in https://repository.jboss.org/nexus/content/groups/public/" simply run mvn site -Ddependency.locations.enabled=false ([[https://whatiscomingtomyhead.wordpress.com/2011/04/20/if-your-maven-site-build-is-too-slow/|source]]) **Verify the build** * Verify all files in your project are committed * Verify your project has no SNAPSHOT dependencies * Verify of your project's pom.xml is a SNAPSHOT version * Verify the project mvn verify **Prepare the release information** * Confirm what the project's next RELEASE version will be * Confirm what the project's SVN tag for the next release version will be * Confirm what the project's next SNAPSHOT version will be ** Dry run ** Since the Release Plugin performs a number of operations that change the project, it may be wise to do a dry run before a big release or on a new project. To do this, commit all of your files as if you were about to run a full release and run: mvn release:prepare -DdryRun=true # then check the generated files mvn release:clean # Removes all of the files created above, and the project will be ready to execute the proper release ** Prepare the release ** mvn release:prepare # answer the questions maven asks ** Check ** the generated release.properties. If something is not right the release can be rolled back with: //"mvn release:rollback"// **CAUTION**: rollback [[http://maven.apache.org/maven-release/maven-release-plugin/examples/rollback-release.html|currently does NOT remove already created tags in your SCM]]. You have to delete the tag yourself. **Perform the release ** if everything looks alright: mvn release:perform **Note**: When deploying a new **release** to a Nexus repository fails, make sure that there's **not already an artifact of the same name & version there**. If you are deploying to a release Nexus repository, you cannot overwrite an existing file. ===== Staging a release ===== tbd ===== Deploy release to production JBoss ===== To make production deployments really easy, here is a script that does * get the war file that shall be deployed * starting the jboss-cli * deploy my.war #!/usr/bin/env python # # Usage: ./deploy.py # Example: ./deploy.py 0.0.1 # import os import sys import logging logging.basicConfig(filename="deploy.log", level=20, format="%(asctime)-15s %(levelname)s\t(%(filename)s:%(lineno)d) - %(message)s") artifact_id = "fancy-services" warname = "fancy.war" nexus_repo="http://nexus:port/nexus/content/repositories/releases/path/to/artifact/" +artifact_id+ "/" jboss_base_cmd = """/opt/jboss/bin/jboss-cli.sh --connect --controller=127.0.0.1:9999 --user=elder --password=secret """ def jboss(command_text, raise_on_error=True): cmd = jboss_base_cmd + """ --command="%s" """ % command_text returncode=os.system(cmd) if raise_on_error and returncode != 0: raise Exception("command %s failed" % command_text) def downloadArtifact(): url = nexus_repo + release_version + "/" + versioned_artifact os.system("rm {0}".format(warname)) logging.debug("Getting artifact from " + url) returncode = os.system("wget {url} -O {war}".format(url=url, war=warname)) if returncode!=0: raise Exception("Download Failed: " + url) def showDeployments(): print "Currently deployed on host:\n" jboss("deploy -l", raise_on_error=False) # list deployments release_version = sys.argv[1] versioned_artifact = artifact_id+"-"+release_version + ".war" logging.info("Attempting to deploy " + versioned_artifact) try: downloadArtifact() jboss("undeploy {0}".format(warname), raise_on_error=False ) # undeploy old version jboss("deploy {0}".format(warname), raise_on_error=True) # deploy new version logging.info("SUCCESS: " + versioned_artifact + " deployed.") print "\n\nSUCCESS\n" showDeployments() except Exception, e: logging.exception("ERROR: Deployment of %s failed " % versioned_artifact) print "\n\nERROR: Deployment failed\n\n" print e ====== Misc ====== ===== Create archetypes from existing projects ===== Using an existing project (preferrably a stable release) you can simply create and locally install an archetype: # cd mvn archetype:create-from-project cd target/generated-sources/archetype/ mvn install Afterwards the newly created archetype will be listed here and can be used to create a new project mvn archetype:generate Or only list locally installed archetypes: mvn archetype:generate -DarchetypeCatalog=local See also: [[http://maven.apache.org/archetype/maven-archetype-plugin/advanced-usage.html|official documentation]] ===== Export source code of dependencies ===== To get a directory with xxx-src.jar dependencies of your project do mvn dependency:copy-dependencies -Dclassifier=sources -DincludeArtifactIds=jmapmatcher-core,ariadne-core # ls target/dependency see [[http://maven.apache.org/plugins/maven-dependency-plugin/copy-dependencies-mojo.html]] ===== Maven and J2EE ===== there are some useful Maven plugins for J2EE (and JBoss). Have a look at [[Maven J2EE]] ====== Useful Links ====== * http://www.java-tutorial.ch/maven/maven-release * http://maven.apache.org/plugins/maven-release-plugin/index.html