Skip to content

Latest commit

 

History

History
666 lines (558 loc) · 32.2 KB

chapter-profiles.asciidoc

File metadata and controls

666 lines (558 loc) · 32.2 KB

Build Profiles

Profiles allow for the ability to customize a particular build for a particular environment. They enable portability between different build environments.

Two typical build environments would be "production" and "development". When you are working in a development environment, your system would probably be configured to use a development database instance, while in production, your system is configured to read from the production database. Maven allows you to define any number of build environments using build profiles, by means of which you can override any of the settings in the POM.

Profiles can be activated according to the environment and platform, among other variables. You can customize a build to run differently depending, for example, on the operating system or the installed JDK version. Before we talk about using and configuring Maven profiles, we need to define the concept of Build Portability.

Build Portability

A build’s portability is a measure of how easy it is to take a particular project and build it in different environments. A build which works without any custom configuration or customization is more portable than a build which requires a great deal of work to build from scratch. The most portable projects tend to be widely used open-source projects like Apache Commons or Spring Framework, which ship with Maven builds requiring little or no customization.

Put simply, the most portable project builds tend to just work out of the box, and the least portable builds require you to jump through hoops and configure platform specific paths to locate build tools. Before we show you how to achieve build portability, let’s survey the different kinds of portability we are talking about.

Non-Portable Builds

Lack of portability is exactly what all build tools are made to prevent. However, any tool can be configured to be non-portable (even Maven). A non-portable project is buildable only under a specific set of circumstances and criteria (e.g., your local machine). Unless you are working by yourself and have no plans of ever deploying your application to another machine, it is best to avoid non-portability entirely. A non-portable build only runs on a single machine. It is a "one-off".

Maven is designed to discourage non-portable builds by offering the ability to customize builds using profiles.

When a new developer gets the source for a non-portable project, they will not be able to build the project without rewriting large portions of a build script.

Environment Portability

A build exhibits environment portability if it has a mechanism for customizing behavior and configuration when targeting different environments. A project that contains a reference to a test database in a test environment, for example, and a production database in a production environment, is environmentally portable. It is likely that this build has a different set of properties for each environment. When you move to a different environment, one that is not defined and has no profile created for it, however, the project will not work. Hence it is only portable between defined environments.

When a new developer gets the source for an portable project, they will have to run the build within a defined environment, or have to create a custom environment to successfully build the project.

Organizational (In-House) Portability

The center of this level of portability is a project’s requirement that only a select few may access internal resources such as source control or an internally-maintained Maven repository. A project at a large corporation may depend on a database available only to in-house developers, or an open-source project might require a specific level of credentials to publish a web site and deploy the products of a build to a public repository.

If you attempt to build an in-house project from scratch outside of the in-house network (for example, outside a corporate firewall), the build will fail. It may fail because certain required custom plugins are unavailable; or because project dependencies cannot be found since you don’t have the appropriate credentials to retrieve dependencies from a custom remote repository. Such a project is portable only across environments in a single organization.

Wide (Universal) Portability

In a universally portable build, anyone can download the project’s source, compile and install it without customizing a build for a specific environment. This is the highest level of portability; anything less requires extra work for those who wish to build your project. This level of portability is especially important for open-source projects, which depend on the ability of would-be contributors to easily download and build from source.

Selecting an Appropriate Level of Portability

Clearly, you’ll want to avoid creating the worst-case scenario: the non-portable build. You may have had the misfortune to work at an organization that had critical applications with non-portable builds. In such organizations, you cannot deploy an application without the help of a specific individual on a specific machine. It would be also very difficult to introduce new project dependencies or changes without coordinating the change with people who maintain such a non-portable build. Non-portable builds tend to grow in highly political environments when one individual or group needs to exert control over how and when a project is built and deployed. "How do we build the system?" "Oh, we’ve got to call Jack and ask him to build it for us. No one else deploys to production". That’s a dangerous situation which is more common that you’d think. If you work for an organization like this, Maven profiles provide a way out of the mess.

On the opposite end of the portability spectrum are widely portable builds, which are generally the most difficult build portability level to attain. Universal builds restrict your dependencies to those projects and tools that may be freely distributed and are publicly available.

Many commercial software packages might be excluded from the most portable builds because they cannot be downloaded before you have accepted a certain license. Wide portability also restricts dependencies to those pieces of software that may be distributed as Maven artifacts. For example, if you depend upon Oracle JDBC drivers, your users will have to download and install them manually. This is not widely portable, as you will have to distribute a set of environment setup instructions for people interested in building your application. On the other hand, you could use a database with JDBC drivers available from public Maven repositories, like MySQL or HSQLDB drivers.

As stated previously, open-source projects benefit from having the most widely portable build possible. Widely portable builds reduce inefficiencies associated with contributing to a project. In an open-source project (such as Maven) there are two distinct groups: end-users and developers. When an end-user uses a project like Maven and decides to contribute a patch, they have to make the transition from using the output of a build to running a build. They have to first become a developer, and if it’s difficult to learn how to build the project, this end-user has a disincentive to take the time to contribute to a project.

In a widely portable project, end-users don’t have to follow a set of arcane build instructions to start becoming a developer. They can download the source, modify it, build it, and submit a contribution without asking someone to help them set up a build environment. When the cost of contributing source back to an open-source project is lower, you’ll see an increase in code contributions, especially casual contributions. This can make the difference between a project’s success and its failure. One side effect of Maven’s adoption across a wide group of open-source projects is that it has made it easier for developers to contribute code.

Portability through Maven Profiles

A profile in Maven is an alternative set of configuration values which set or override default values. Using a profile, you can customize a build for different environments. Profiles are configured in the POM and are given an identifier. Then you can run Maven with a command-line flag that tells Maven to execute goals in a specific profile. The following POM uses a profile named production to override the default settings of the Compiler plugin.

Using a Maven Profile to Override Compiler Settings
<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/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.sonatype.mavenbook</groupId>
    <artifactId>simple</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple</name>
    <url>http://maven.apache.org</url>

    <dependencies>
      ...
    </dependencies>
    ...
    <profiles> <!--(1)-->
      <profile>
      <id>production</id> <!--(2)-->
          <build> <!--(3)-->
             <plugins>
                <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-compiler-plugin</artifactId>
                   <configuration>
                   <debug>false</debug> <!--(4)-->
                      <optimize>true</optimize>
                   </configuration>
                </plugin>
              </plugins>
          </build>
      </profile>
  </profiles>
</project>

In this profile, we override the default configuration of the Maven Compiler plugin. Let’s examine the syntax in detail.

  1. The profiles element in the POM contains one or more profile elements. Since profiles override the default settings in a POM, the profiles element is usually listed as the last element in a `pom.xml'.

  2. Each profile has to have an id element, which contains the name used to invoke the profile from the command-line. A profile is invoked by passing the -P<profile_id> command-line argument to Maven.

  3. A profile element can contain many of the elements which can appear under the project element of a POM. In this example, we’re overriding the behavior of the Compiler plugin and we override the plugin configuration which is normally enclosed in a build and a plugins element.

  4. We’re overriding the configuration of the Maven Compiler plugin. We’re making sure that the bytecode produced by the production profile doesn’t contain debug information, and that the bytecode has gone through the compiler’s optimization routines.

To execute mvn install under the production profile, you need to pass the -Pproduction argument on the command-line. To verify that the production profile overrides the default Compiler plugin configuration, execute Maven with debug output enabled (-X) as follows:

~/examples/profile $ mvn clean install -Pproduction -X

... (omitting debugging output) ...

[DEBUG] Configuring mojo `o.a.m.plugins:maven-compiler-plugin:2.0.2:testCompile'
[DEBUG]   (f) basedir = ~\examples\profile
[DEBUG]   (f) buildDirectory = ~\examples\profile\target
...
[DEBUG]   (f) compilerId = javac
[DEBUG]   (f) *debug = false*
[DEBUG]   (f) failOnError = true
[DEBUG]   (f) fork = false
[DEBUG]   (f) *optimize = true*
[DEBUG]   (f) outputDirectory = ~\svnw\sonatype\examples\profile\target\test-classes
[DEBUG]   (f) outputFileName = simple-1.0-SNAPSHOT
[DEBUG]   (f) showDeprecation = false
[DEBUG]   (f) showWarnings = false
[DEBUG]   (f) staleMillis = 0
[DEBUG]   (f) verbose = false
[DEBUG] -- end configuration --

... (omitting debugging output) ...

This excerpt from the debug output of Maven shows the configuration of the Compiler plugin under the production profile. As shown in the output, debug is set to false and optimize is set to true.

Overriding a Project Object Model

While the previous example showed you how to override the default configuration properties of a single Maven plugin, you still don’t know exactly what a Maven profile is allowed to override. The short answer is that a profile can override almost everything that you could have in a POM. Each profile must have an id, and other than that, it can contain almost any of the elements one would expect to see under the project element. The following XML document shows all of the elements a profile is allowed to override.

Elements Allowed in a Profile
<project>
    <profiles>
        <profile>
            <build>
                <defaultGoal>...</defaultGoal>
                <finalName>...</finalName>
                <resources>...</resources>
                <testResources>...</testResources>
                <plugins>...</plugins>
            </build>
            <reporting>...</reporting>
            <modules>...</modules>
            <dependencies>...</dependencies>
            <dependencyManagement>...</dependencyManagement>
            <distributionManagement>...</distributionManagement>
            <repositories>...</repositories>
            <pluginRepositories>...</pluginRepositories>
            <properties>...</properties>
        </profile>
    </profiles>
</project>

A profile can override an element shown with ellipses above. It can override the final name of a project’s artifact in a profile, the dependencies, and the behavior of a project’s build via plugin configuration. A profile can also override the configuration of distribution settings. For example, if you need to publish an artifact to a staging server, you would create a staging profile which overrides the distributionManagement element.

Profile Activation

We’ve shown how to create a profile that overrides default behavior for a specific target environment, where the default build was designed for development and the production profile provides configuration for a production environment. What happens when you need to provide customizations based on variables like operating systems or JDK version? Maven provides a way to "turn on" a profile for different environmental parameters, this is called profile activation.

Take the following example, assume that we have a Java library that has a specific feature only available since the Java 6 release: the Scripting Engine as defined in JSR-223. You’ve separated the portion of the library that deals with the scripting library into a separate Maven project, and you want people running Java 5 or earlier to be able to build the project without attempting to build the new specific library extension. You can do this by using a Maven profile that adds the scripting extension module to the build only when the build is running with Java 6 or later. First let’s take a look at our project’s directory layout and how we want developers to build the system.

When someone runs mvn install with Java 6+, you want the build to include the simple-script project’s build. When they are running in Java 5, you’d like to skip the simple-script project build. If you failed to skip that in Java 5, your build would fail because Java 5 does not have the ScriptEngine on the classpath. Let’s take a look at the library project’s POM:

Dynamic Inclusion of Submodules Using Profile Activation
<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/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.sonatype.mavenbook</groupId>
    <artifactId>simple</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple</name>
    <url>http://maven.apache.org</url>

    <dependencies>
      ...
    </dependencies>

    <profiles>
        <profile>
            <id>jdk16</id>
            <activation>   <!--(1)-->
                <jdk>1.6</jdk>
            </activation>
            <modules>      <!--(2)-->
                <module>simple-script</module>
            </modules>
        </profile>
    </profiles>
</project>

If you run mvn install under Java 6, you will see Maven descending into the simple-script subdirectory to build the simple-script project. If you are running mvn install in Java 5 or earlier, the build will not try to build the simple-script submodule. Let’s explore this activation configuration in more detail:

  1. The activation element lists the conditions for profile activation. In this example, we’ve specified that this profile will be activated by Java versions that begin with "1.6". This would include "1.6.0_03", "1.6.0_02", or any other string that began with "1.6". Activation parameters are not limited to the Java version; for a full list of activation parameters see Activation Configuration.

  2. In this profile we are adding the module simple-script. Adding this module will cause Maven to look in the simple-script/ subdirectory for a pom.xml.

Activation Configuration

Activations can contain one or more selectors, including JDK versions, operating system parameters, files, and properties. A profile is activated when all activation criteria have been satisfied. For example, a profile could list an operating system family of Windows, and a JDK version 9. This profile will only be activated when the build is executed on a Windows machine of the given family and running Java 9. If the profile is active, all its elements will override the corresponding project-level elements, as if the profile were included with the -P command-line argument.

The following example POM defines a profile which is activated by a very specific combination of operating system parameters, properties, and JDK version.

Profile Activation Parameters: JDK Version, OS Parameters, and Properties
<project>
    ...
    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>false</activeByDefault> <!--(1)-->
                <jdk>9</jdk> <!--(2)-->
                <os>
                    <name>windows 10</name> <!--(3)-->
                    <family>Windows</family>
                    <arch>amd64</arch>
                    <version>10.0</version>
                </os>
                <file>
                    <exists>file2.properties</exists> <!--(4)-->
                    <missing>file1.properties</missing>
                </file>
            </activation>
            ...
        </profile>
    </profiles>
</project>

This previous example defines a narrow set of activation parameters. Let’s examine each activation criterion in detail:

  1. The activeByDefault element controls whether this profile is considered active by default.

  2. This profile will only be active for JDK versions that begin with "9". This includes "9.0", "9.0.1", etc.

  3. The profile targets a specific version of Windows, on a 64-bit platform. If your project uses the Native plugin to build a C program, you might find yourself writing projects for specific platforms.

  4. The file element allows you to activate a profile based on the presence (or absence) of files. In this example, the profile will be activated if a file named file2.properties exists in the base directory of the project, and if there is no file named file1.properties in the project’s base directory.

Property-based Activation

You can also activate a profile based on the value of a property like environment.type. For example, you can activate a development profile if environment.type equals development, or a production profile if environment.type equals production. You can also activate a profile based on the absence of a property. The following configuration activates a profile if the property environment.type is not present during Maven execution.

Activating a Profile in the Absence of a Property
<project>
    ...
    <profiles>
        <profile>
            <id>development</id>
            <activation>
                <property>
                    <name>!environment.type</name>
                </property>
            </activation>
        </profile>
    </profiles>
</project>

Note the exclamation point prefixing the property name. This is often referred to as the "bang" character and signifies "not". This profile is activated when no ${environment.type} property is set.

Listing Active Profiles

Maven profiles can be defined in either the POM itself, in a profiles.xml file, in ~/.m2/settings.xml, or in ${M2_HOME}/conf/settings.xml. With these four "levels", there’s no good way of keeping track of profiles available to a particular project without remembering which profiles are defined in these files. To make it easier to keep track of which profiles are available and where they’ve been defined, the Maven Help plugin includes the goal active-profiles, which lists all active profiles and where they have been defined. You can run it as follows:

$ mvn help:active-profiles

Active Profiles for Project `My Project':

The following profiles are active:
- my-settings-profile (source: settings.xml)
- my-external-profile (source: profiles.xml)
- my-internal-profile (source: pom.xml)

Tips and Tricks

If your build needs subtle customizations to work on different platforms, or if you need your build to produce different results for different target platforms, project profiles increase build portability. Settings profiles generally decrease build portability by adding extra-project information that must be communicated from developer to developer. The following sections provide some guidelines and some ideas for applying Maven profiles to your project.

Common Environments

One of the core motivations for Maven project profiles is to provide for environment-specific configuration settings. You can define profiles activated by environment properties and use these common environment properties across all your projects.

For example, if every project had a "development" profile activated by a property named environment.type having a value of dev, and if those same projects had a production profile activated by a property named environment.type having a value of prod, you could simply pass in the appropriate property value on the command-line to ensure that your builds target the correct environment.

Let’s take a look at how a project’s POM would define a profile activated by environment.type having the value dev.

Project Profile Activated by setting environment.type
<project>
    ...
    <profiles>
        <profile>
            <id>development</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <property>
                    <name>environment.type</name>
                    <value>dev</value>
                </property>
            </activation>

            <properties>
                <database.driverClassName>
                    com.mysql.jdbc.Driver
                </database.driverClassName>
                <database.url>
                    jdbc:mysql://localhost:3306/app_dev
                </database.url>
                <database.user>
                    development_user
                </database.user>
                <database.password>
                    development_password
                </database.password>
            </properties>
        </profile>

        <profile>
            <id>production</id>
            <activation>
                <property>
                    <name>environment.type</name>
                    <value>prod</value>
                </property>
            </activation>

            <properties>
                <database.driverClassName>
                    com.mysql.jdbc.Driver
                </database.driverClassName>
                <database.url>
                    jdbc:mysql://master01:3306,slave01:3306/app_prod
                </database.url>
                <database.user>
                    prod_user
                </database.user>
            </properties>
        </profile>
    </profiles>
</project>

Note that this project defines some properties like database.url and database.user which might be used to configure another Maven plugin configured in the POM. (There are plugins available that can manipulate the database and run SQL, as well as plugins which can generate annotated model objects for use in persistence frameworks.) Now, if we need to target the development environment, we can just run the following command:

mvn install

Because the development profile is active by default, and because there are no other profiles activated, running mvn help:active-profiles will show that the development profile is active. The activeByDefault option will only work if no other profiles are active, however. If you wanted to be sure the development profile would be active for a given build, you could explicitly pass in the environment.type variable as follows:

mvn install -Denvironment.type=dev

Alternatively, if we need to activate the production profile, we could run Maven with:

mvn install -Denvironment.type=prod

To test which profiles are active for a given build, use mvn help:active-profiles.

Protecting Secrets

In Project Profile Activated by setting environment.type above, the production profile doesn’t contain the database.password property. We’ve done this on purpose, to illustrate the concept of putting secrets in you user-specific settings.xml. If you were developing an application at a large organization which values security, it’s likely that the majority of the development group will not know the password to the production database.

In this scenario, the production environment build may only be carried out by a lead developer or by a member of the production operations group. When they run this build using the prod environment.type, they will need to define this variable in their settings.xml as follows:

Storing Secrets in a User-specific Settings Profile
<settings>
    <profiles>
    <profile>
        <activeByDefault>true</activeByDefault>
        <properties>
        <environment.type>prod</environment.type>
        <database.password>production_password</database.password>
        </properties>
    </profile>
    </profiles>
</settings>

This user has defined a default profile which sets the environment.type to prod and which also sets the production password. When the project is built, the production profile is activated by the environment.type property and the database.password property is populated. This way, you can put all of the production-specific configuration into a project’s POM, and leave out only the single secret necessary to access the production database.

Platform Classifiers

Let’s assume you have a library or project that produces platform-specific customizations. Even though Java is platform-neutral, there are times when you might need to write code that invokes platform-specific native libraries or tools. Another possibility is that you’ve written some C code which is compiled by the Maven Native plugin, and you want to produce a qualified artifact depending on the build platform. You can set a classifier with the Maven Assembly plugin or with the Maven Jar plugin.

The following POM produces a qualified artifact using profiles which are activated by operating system parameters. For more information about the Maven Assembly plugin, see [assemblies].

Qualifying Artifacts with Platform-Activated Project Profiles
<project>
    ...
    <profiles>
        <profile>
            <id>windows</id>
            <activation>
                <os>
                    <family>windows</family>
                </os>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-jar-plugin</artifactId>
                        <configuration>
                            <classifier>win</classifier>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

        <profile>
            <id>linux</id>
            <activation>
                <os>
                    <family>unix</family>
                </os>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-jar-plugin</artifactId>
                        <configuration>
                            <classifier>linux</classifier>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

If the Operating System is in the Windows family, this POM qualifies the JAR artifact with "-win". If the Operating System is in the Unix family, the artifact is qualified with "-linux". This POM successfully adds the qualifiers to the artifacts, but it is more verbose than needed, due to the redundant configuration of the Maven Jar plugin in both profiles. This example could be rewritten to use variable substitution to minimize redundancy as follows:

Qualifying Artifacts with Platform-Activated Profiles and Variable Substitution
<project>
    ...
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <classifier>${envClassifier}</classifier>
                </configuration>
            </plugin>
        </plugins>
    </build>
    ...
    <profiles>
        <profile>
            <id>windows</id>
            <activation>
                <os>
                    <family>windows</family>
                </os>
            </activation>
            <properties>
                <envClassifier>win</envClassifier>
            </properties>
        </profile>

        <profile>
            <id>linux</id>
            <activation>
                <os>
                    <family>unix</family>
                </os>
            </activation>
            <properties>
                <envClassifier>linux</envClassifier>
            </properties>
        </profile>
    </profiles>
</project>

In this POM, each profile doesn’t need to include a build element to configure the Jar plugin. Instead, it’s activated by the operating system family and sets the envClassifier property to either win or linux. This envClassifier is then referenced in the default POM build element to add a classifier to the project’s JAR artifact. The JAR artifact will be named ${finalName}-${envClassifier}.jar and included as a dependency using the following dependency syntax:

Depending on a Qualified Artifact
<dependency>
    <groupId>com.mycompany</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0</version>
    <classifier>linux</classifier>
</dependency>

Summary

When used judiciously, profiles can make it easy to customize a build for different platforms. If something in your build needs to define a platform-specific path for something like an application server, you can put these configuration points in a profile which is activated by an operating system parameter.

If you have a project which needs to produce different artifacts for different environments, you can customize the build behavior for different environments and platforms via profile-specific plugin behavior. Using profiles, builds can become portable. There is no need to rewrite your build logic to support a new environment. Just override the configuration that needs to change and share the configuration points which can be shared.