blob: d6fdc3bdfc2a786da4513b499e317843e5f923cb [file] [log] [blame]
---
Introduction to build profiles
---
John Casey/Allan Ramirez
---
2008-01-01
---
~~ Licensed to the Apache Software Foundation (ASF) under one
~~ or more contributor license agreements. See the NOTICE file
~~ distributed with this work for additional information
~~ regarding copyright ownership. The ASF licenses this file
~~ to you under the Apache License, Version 2.0 (the
~~ "License"); you may not use this file except in compliance
~~ with the License. You may obtain a copy of the License at
~~
~~ http://www.apache.org/licenses/LICENSE-2.0
~~
~~ Unless required by applicable law or agreed to in writing,
~~ software distributed under the License is distributed on an
~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~~ KIND, either express or implied. See the License for the
~~ specific language governing permissions and limitations
~~ under the License.
~~ NOTE: For help with the syntax of this file, see:
~~ http://maven.apache.org/doxia/references/apt-format.html
Introduction to Build Profiles
Apache Maven 2.0 goes to great lengths to ensure that builds are portable. Among
other things, this means allowing build configuration inside the POM,
avoiding <<all>> filesystem references (in inheritance, dependencies, and
other places), and leaning much more heavily on the local repository to
store the metadata needed to make this possible.
However, sometimes portability is not entirely possible. Under certain
conditions, plugins may need to be configured with local filesystem paths.
Under other circumstances, a slightly different dependency set will be
required, and the project's artifact name may need to be adjusted slightly.
And at still other times, you may even need to include a whole plugin in the
build lifecycle depending on the detected build environment.
To address these circumstances, Maven 2.0 introduces the concept of a build
profile. Profiles are specified using a subset of the elements available in
the POM itself (plus one extra section), and are triggered in any of a
variety of ways. They modify the POM at build time, and are meant to be used
in complementary sets to give equivalent-but-different parameters for a set
of target environments (providing, for example, the path of the appserver root
in the development, testing, and production environments). As such, profiles
can easily lead to differing build results from different members of your team.
However, used properly, profiles can be used while still preserving
project portability. This will also minimize the use of <<<-f>>> option of
maven which allows user to create another POM with different parameters or
configuration to build which makes it more maintainable since it is runnning
with one POM only.
* What are the different types of profile? Where is each defined?
* Per Project
- Defined in the POM itself <<<(pom.xml)>>>.
* Per User
- Defined in the {{{/ref/current/maven-settings/settings.html} Maven-settings}}
<<<(%USER_HOME%/.m2/settings.xml)>>>.
* Global
- Defined in the {{{/ref/current/maven-settings/settings.html} global Maven-settings}}
<<<($\{maven.home\}/conf/settings.xml)>>>.
* Profile descriptor
- a descriptor located in
{{{/ref/2.2.1/maven-profile/profiles.html}project basedir <<<(profiles.xml)>>>}}
(unsupported in Maven 3.0: see
{{{https://cwiki.apache.org/confluence/display/MAVEN/Maven+3.x+Compatibility+Notes#Maven3.xCompatibilityNotes-profiles.xml} Maven 3 compatibility notes}})
* How can a profile be triggered? How does this vary according to the type of profile being used?
A profile can be triggered/activated in several ways:
* Explicitly
* Through Maven settings
* Based on environment variables
* OS settings
* Present or missing files
** Details on profile activation
Profiles can be explicitly specified using the <<<-P>>> CLI option.
This option takes an argument that is a comma-delimited list of
profile-ids to use. When this option is specified, the profile(s) specified in the option
argument will be activated in addition to any profiles which are activated by their activation
configuration or the <<<\<activeProfiles\>>>> section in <<<settings.xml>>>.
+---+
mvn groupId:artifactId:goal -P profile-1,profile-2
+---+
Profiles can be activated in the Maven settings, via the <<<\<activeProfiles\>>>>
section. This section takes a list of <<<\<activeProfile\>>>> elements, each
containing a profile-id inside.
+---+
<settings>
...
<activeProfiles>
<activeProfile>profile-1</activeProfile>
</activeProfiles>
...
</settings>
+---+
Profiles listed in the <<<\<activeProfiles\>>>> tag would be activated by default
every time a project use it.
Profiles can be automatically triggered based on the detected state of
the build environment. These triggers are specified via an <<<\<activation\>>>>
section in the profile itself. Currently, this detection is limited to
prefix-matching of the JDK version, the presence of a system property or
the value of a system property. Here are some examples.
The following configuration will trigger the profile when the JDK's
version starts with "1.4" (eg. "1.4.0_08", "1.4.2_07", "1.4"):
+---+
<profiles>
<profile>
<activation>
<jdk>1.4</jdk>
</activation>
...
</profile>
</profiles>
+---+
Ranges can also be used as of Maven 2.1 (refer to the {{{/enforcer/enforcer-rules/versionRanges.html} Enforcer Version Range Syntax}} for more information).
The following honours versions 1.3, 1.4 and 1.5.
+---+
<profiles>
<profile>
<activation>
<jdk>[1.3,1.6)</jdk>
</activation>
...
</profile>
</profiles>
+---+
<Note:> an upper bound such as <<<,1.5]>>> is likely not to include most releases of 1.5, since they will have an additional "patch" release such as <<<_05>>> that is not taken into consideration in the above range.
This next one will activate based on OS settings. See the {{{/enforcer/enforcer-rules/requireOS.html}Maven Enforcer Plugin}} for more details about OS values.
+---+
<profiles>
<profile>
<activation>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
</activation>
...
</profile>
</profiles>
+---+
The profile below will be activated when the system property "debug" is specified
with any value:
+---+
<profiles>
<profile>
<activation>
<property>
<name>debug</name>
</property>
</activation>
...
</profile>
</profiles>
+---+
The following profile will be activated when the system property "debug" is not defined
at all:
+---+
<profiles>
<profile>
<activation>
<property>
<name>!debug</name>
</property>
</activation>
...
</profile>
</profiles>
+---+
The following profile will be activated when the system property "debug" is not defined,
or is defined with a value which is not "true".
+---+
<profiles>
<profile>
<activation>
<property>
<name>debug</name>
<value>!true</value>
</property>
</activation>
...
</profile>
</profiles>
+---+
To activate this you would type one of those on the command line:
+---+
mvn groupId:artifactId:goal
mvn groupId:artifactId:goal -Ddebug=false
+---+
The next example will trigger the profile when the system property
"environment" is specified with the value "test":
+---+
<profiles>
<profile>
<activation>
<property>
<name>environment</name>
<value>test</value>
</property>
</activation>
...
</profile>
</profiles>
+---+
To activate this you would type this on the command line:
+---+
mvn groupId:artifactId:goal -Denvironment=test
+---+
As of Maven 3.0, profiles in the POM can also be activated based on properties from active profiles from the
<<<settings.xml>>>.
<<Note>>: Environment variables like <<<FOO>>> are available as properties of the form <<<env.FOO>>>. Further note
that environment variable names are normalized to all upper-case on Windows.
This example will trigger the profile when the generated file
<<<target/generated-sources/axistools/wsdl2java/org/apache/maven>>> is missing.
+---+
<profiles>
<profile>
<activation>
<file>
<missing>target/generated-sources/axistools/wsdl2java/org/apache/maven</missing>
</file>
</activation>
...
</profile>
</profiles>
+---+
As of Maven 2.0.9, the tags <<<\<exists\>>>> and <<<\<missing\>>>> could be interpolated. Supported variables are
system properties like <<<$\{user.home\}>>> and environment variables like <<<$\{env.HOME\}>>>. Please note that
properties and values defined in the POM itself are not available for interpolation here, e.g. the above example
activator cannot use <<<$\{project.build.directory\}>>> but needs to hard-code the path <<<target>>>.
Profiles can also be active by default using a configuration like the following:
+---+
<profiles>
<profile>
<id>profile-1</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
...
</profile>
</profiles>
+---+
This profile will automatically be active for all builds unless another profile in the same POM
is activated using one of the previously described methods. All profiles that are active by
default are automatically deactivated when a profile in the POM is activated on the command line
or through its activation config.
** Deactivating a profile
Starting with Maven 2.0.10, one or more profiles can be deactivated using the command line by prefixing their
identifier with either the character '!' or '-' as shown below:
+---+
mvn groupId:artifactId:goal -P !profile-1,!profile-2
+---+
This can be used to deactivate profiles marked as activeByDefault or profiles that would
otherwise be activated through their activation config.
* Which areas of a POM can be customized by each type of profile? Why?
Now that we've talked about where to specify profiles, and how to activate them,
it will be useful to talk about <what> you can specify in a profile. As with
the other aspects of profile configuration, this answer is not straightforward.
Depending on where you choose to configure your profile, you will have access
to varying POM configuration options.
** Profiles in external files
Profiles specified in external files
(i.e in <<<settings.xml>>> or <<<profiles.xml>>>) are not portable in the
strictest sense. Anything that seems to stand a high chance of changing the result
of the build is restricted to the inline profiles in the POM. Things like
repository lists could simply be a proprietary repository of approved
artifacts, and won't change the outcome of the build. Therefore, you will
only be able to modify the <<<\<repositories\>>>> and <<<\<pluginRepositories\>>>>
sections, plus an extra <<<\<properties\>>>> section.
The <<<\<properties\>>>> section allows you to specify free-form key-value pairs
which will be included in the interpolation process for the POM. This allows
you to specify a plugin configuration in the form of <<<$\{profile.provided.path\}>>>.
** Profiles in POMs
On the other hand, if your profiles can be reasonably specified <inside> the
POM, you have many more options. The trade-off, of course, is that you can
only modify <that> project and it's sub-modules. Since these profiles are
specified inline, and therefore have a better chance of preserving portability,
it's reasonable to say you can add more information to them without the risk
of that information being unavailable to other users.
Profiles specified in the POM can modify the following POM elements:
* <<<\<repositories\>>>>
* <<<\<pluginRepositories\>>>>
* <<<\<dependencies\>>>>
* <<<\<plugins\>>>>
* <<<\<properties\>>>> (not actually available in the main POM, but used behind the
scenes)
* <<<\<modules\>>>>
* <<<\<reporting\>>>>
* <<<\<dependencyManagement\>>>>
* <<<\<distributionManagement\>>>>
* a subset of the <<<\<build\>>>> element, which consists of:
* <<<\<defaultGoal\>>>>
* <<<\<resources\>>>>
* <<<\<testResources\>>>>
* <<<\<finalName\>>>>
[]
** POM elements outside \<profiles\>
We don't allow modification of some POM elements outside of POM-profiles
because these runtime modifications will not be distributed when the POM
is deployed to the repository system, making that person's build of that
project completely unique from others. While you can do this to some extent
with the options given for external profiles, the danger is limited.
Another reason is that this POM info is sometimes being reused from the
parent POM.
External files such as <<<settings.xml>>> and <<<profiles.xml>>> also does not support elements
outside the POM-profiles. Let us take this scenario for elaboration. When the
effective POM get deployed to a remote repository, any person can pickup
its info out of the repository and use it to build a Maven project directly.
Now, imagine that if we can set profiles in dependencies, which is very
important to a build, or in any other elements outside POM-profiles in
<<<settings.xml>>> then most probably we cannot expect someone else to use that
POM from the repository and be able to build it. And we have to also think about
how to share the <<<settings.xml>>> with others. Note that too many files to
configure is very confusing and very hard to maintain.
Bottom line is that since this is build data, it should be in the POM.
One of the goals in Maven 2 is to consolidate all the information needed to
run a build into a single file, or file hierarchy which is the POM.
* Profile Pitfalls
We've already mentioned the fact that adding profiles to your build has the
potential to break portability for your project. We've even gone so far as to
highlight circumstances where profiles are likely to break project portability.
However, it's worth reiterating those points as part of a more coherent
discussion about some pitfalls to avoid when using profiles.
There are two main problem areas to keep in mind when using profiles.
First are external properties, usually used in plugin configurations. These pose
the risk of breaking portability in your project. The other, more subtle area
is the incomplete specification of a natural set of profiles.
** External Properties
External property definition concerns any property value defined outside
the <<<pom.xml>>> but not defined in a corresponding profile inside it.
The most obvious usage of properties in the POM is in plugin configuration.
While it is certainly possible to break project portability without properties,
these critters can have subtle effects that cause builds to fail. For example,
specifying appserver paths in a profile that is specified in the
<<<settings.xml>>> may cause your integration test plugin to fail when another
user on the team attempts to build without a similar <<<settings.xml>>>.
Consider the following <<<pom.xml>>> snippet for a web application project:
+---+
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.myco.plugins</groupId>
<artifactId>spiffy-integrationTest-plugin</artifactId>
<version>1.0</version>
<configuration>
<appserverHome>${appserver.home}</appserverHome>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
+---+
Now, in your local <<<$\{user.home\}/.m2/settings.xml>>>, you have:
+---+
<settings>
...
<profiles>
<profile>
<id>appserverConfig</id>
<properties>
<appserver.home>/path/to/appserver</appserver.home>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>appserverConfig</activeProfile>
</activeProfiles>
...
</settings>
+---+
When you build the <<integration-test>> lifecycle phase, your integration
tests pass, since the path you've provided allows the test plugin to install
and test this web application.
<However>, when your colleague attempts to build to <<integration-test>>,
his build fails spectacularly, complaining that it cannot resolve the plugin
configuration parameter <<<\<appserverHome\>>>>, or worse, that the value of that
parameter - literally <<<$\{appserver.home\}>>> - is invalid (if it warns you at all).
Congratulations, your project is now non-portable. Inlining this profile in
your <<<pom.xml>>> can help alleviate this, with the obvious drawback that
each project hierarchy (allowing for the effects of inheritance) now have to
specify this information. Since Maven provides good support for project
inheritance, it's possible to stick this sort of configuration in the
<<<\<pluginManagement\>>>> section of a team-level POM or similar, and simply
inherit the paths.
Another, less attractive answer might be standardization of development
environments. However, this will tend to compromise the productivity
gain that Maven is capable of providing.
** Incomplete Specification of a Natural Profile Set
In addition to the above portability-breaker, it's easy to fail to cover all
cases with your profiles. When you do this, you're usually leaving one of
your target environments high and dry. Let's take the example <<<pom.xml>>>
snippet from above one more time:
+---+
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.myco.plugins</groupId>
<artifactId>spiffy-integrationTest-plugin</artifactId>
<version>1.0</version>
<configuration>
<appserverHome>${appserver.home}</appserverHome>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
+---+
Now, consider the following profile, which would be specified inline in the
<<<pom.xml>>>:
+---+
<project>
...
<profiles>
<profile>
<id>appserverConfig-dev</id>
<activation>
<property>
<name>env</name>
<value>dev</value>
</property>
</activation>
<properties>
<appserver.home>/path/to/dev/appserver</appserver.home>
</properties>
</profile>
<profile>
<id>appserverConfig-dev-2</id>
<activation>
<property>
<name>env</name>
<value>dev-2</value>
</property>
</activation>
<properties>
<appserver.home>/path/to/another/dev/appserver2</appserver.home>
</properties>
</profile>
</profiles>
..
</project>
+---+
This profile looks quite similar to the one from the last example, with a few
important exceptions: it's plainly geared toward a development environment,
a new profile named <<<appserverConfig-dev-2>>> is added and it has an
activation section that will trigger its inclusion when the system
properties contain "env=dev" for a profile named <<<appserverConfig-dev>>> and
"env=dev-2" for a profile named <<<appserverConfig-dev-2>>>. So, executing:
+---+
mvn -Denv=dev-2 integration-test
+---+
will result in a successful build, applying the properties given
by profile named <<<appserverConfig-dev-2>>>. And when we execute
+---+
mvn -Denv=dev integration-test
+---+
it will result in a successful build applying the properties given
by the profile named <<<appserverConfig-dev>>>. However, executing:
+---+
mvn -Denv=production integration-test
+---+
will not do a successful build. Why? Because, the resulting non-interpolated
literal value of <<<$\{appserver.home\}>>> will not be a valid path for deploying
and testing your web application. We haven't considered the case for the
production environment when writing our profiles. The "production"
environment (env=production), along with "test" and possibly even "local"
constitute a natural set of target environments for which we may want to
build the integration-test lifecycle phase. The incomplete specification of
this natural set means we have effectively limited our valid target environments
to the development environment. Your teammates - and probably your manager -
will not see the humor in this. When you construct profiles to handle cases
such as these, be sure to address the entire set of target permutations.
As a quick aside, it's possible for user-specific profiles to act in a similar
way. This means that profiles for handling different environments which are
keyed to the user can act up when the team adds a new developer. While I
suppose this <could> act as useful training for the newbie, it just wouldn't
be nice to throw them to the wolves in this way. Again, be sure to think of the
<whole> set of profiles.
* How can I tell which profiles are in effect during a build?
Determining active profiles will help the user to know what particular
profiles has been executed during a build. We can use the {{{/plugins/maven-help-plugin/}Maven Help Plugin}}
to tell what profiles are in effect during a build.
+---+
mvn help:active-profiles
+---+
Let us have some small samples that will help us to understand more on the
<active-profiles> goal of that plugin.
From the last example of profiles in the <<<pom.xml>>>, you'll notice that there are
two profiles named <<<appserverConfig-dev>>> and <<<appserverConfig-dev-2>>>
which has been given different values for properties. If we go ahead
and execute:
+---+
mvn help:active-profiles -Denv=dev
+---+
The result will be a bulleted list of the id of the profile with an
activation property of "env=dev" together with the source where it was
declared. See sample below.
+---+
The following profiles are active:
- appserverConfig-dev (source: pom)
+---+
Now if we have a profile declared in <<<settings.xml>>> (refer to the sample of profile
in <<<settings.xml>>>) and that have been set to be an active profile and execute:
+---+
mvn help:active-profiles
+---+
The result should be something like this
+---+
The following profiles are active:
- appserverConfig (source: settings.xml)
+---+
Even though we don't have an activation property, a profile has been listed as active.
Why? Like we mentioned before, a profile that has been set as an active profile
in the <<<settings.xml>>> is automatically activated.
Now if we have something like a profile in the <<<settings.xml>>> that has been set
as an active profile and also triggered a profile in the POM. Which profile do
you think will have an effect on the build?
+---+
mvn help:active-profiles -P appserverConfig-dev
+---+
This will list the activated profiles:
+---+
The following profiles are active:
- appserverConfig-dev (source: pom)
- appserverConfig (source: settings.xml)
+---+
Even though it listed the two active profiles, we are not sure which one
of them has been applied. To see the effect on the build execute:
+---+
mvn help:effective-pom -P appserverConfig-dev
+---+
This will print the effective POM for this build configuration out to the
console. Take note that profiles in the <<<settings.xml>>> takes higher priority
than profiles in the POM. So the profile that has been applied here is
<<<appserverConfig>>> not <<<appserverConfig-dev>>>.
If you want to redirect the output from the plugin to a file called <<<effective-pom.xml>>>,
use the command-line option <<<-Doutput=effective-pom.xml>>>.
* Naming Conventions
By now you've noticed that profiles are a natural way of addressing the problem of
different build configuration requirements for different target environments. Above,
we discussed the concept of a "natural set" of profiles to address this situation,
and the importance of considering the whole set of profiles that will be required.
However, the question of how to organize and manage the evolution of that set is
non-trivial as well. Just as a good developer strives to write self-documenting
code, it's important that your profile id's give a hint to their intended use.
One good way to do this is to use the common system property trigger as part of
the name for the profile. This might result in names like <<env-dev>>, <<env-test>>,
and <<env-prod>> for profiles that are triggered by the system property <<env>>.
Such a system leaves a highly intuitive hint on how to activate a build targeted
at a particular environment. Thus, to activate a build for the test environment,
you need to activate <<env-test>> by issuing:
+---+
mvn -Denv=test <phase>
+---+
The right command-line option can be had by simply substituting "=" for "-" in
the profile id.