Introduction to Build Profiles



 



Apache Maven 2.0 竭尽全力去保证构建是可移植的,这意味着允许构建配置在 POM 文件中,从而避免所有的文件系统引用。Maven 倾向于更重地依赖本地仓库来存储构建所需的元信息。



然而有时候可移植性不是完全可能的,比如下面这些情况:



  • 插件可能会被配置一些本地路径;
  • 一些依赖版本的变更也是不可避免的;
  • 工程的 artifact 名字也可能会被调整;
  • 你甚至可能需要在检测到某些特殊构建环境后需要包含整个构建插件。



 



为了实现上述效果,Maven 2.0 引入了 profile 概念。Profiles 可以指定使用 POM 中所有配置元素的一个子集,它们在构建时修改 POM,意在通过提供补充配置元素来实现针对不同环境提供不同的参数信息(比如在 debug、online 环境下链接不同的数据库);同样 profile 可以轻易地使团队不同成员构建出不同的结果。对 profiles 的适当使用可以你保证工程的可移植性,这会大大减少对 Maven -f 选项的使用(-f 选项可以指定从另外的地方加载 POM 文件)。



 



What are the different types of profiles? Where is each defined?



 



  • Per Project
  • 定义在 POM 文件里
  • Per User
  • 定义在 Maven-setting 里(%USER_HOME%/.m2/settings.xml)
  • Gloabal
  • 定义在全局 Maven-setting 里(%M2_HOME%/conf/settings.xml)
  • Profile descriptor
  • 在工程根目录下的一个本地配置文件(profiles.xml)

 



How can a profile be triggered? How does this vary according to the type of profile being used?



 



一个 profile 可以通过下面几种方式触发:



  • 显式触发
  • 通过 Maven 设置
  • 基于环境变量
  • 在于操作系统
  • 某个文件是否存在



 



Details on profile activation



显式触发是指通过命令行的 -P 选择指定 profile。这个选项可以指定使用多个 profile,各个 profile 之间用逗号分隔。启动了这个构建参数后,指定的 profile 连同默激活(通过在<activeProfiles>区中配置的 profile)的 profile 共同生效。



mvn groupId:artifactId:goal -P profile-1,profile-2
 
 
<settings>
...
<activeProfiles>
    <activeProfile>profile-1</activeProfile>
</activeProfiles>
...
</settings>


 



Profiles 可以通过检测构建环境来自动触发。这个触发器是通过在 profile 中配置 <activation> 来实现的。当前这种机制仅支持根据前缀来匹配 JDK 版本,JDK 版本信息是通过在 system property 中获取得到的。下面这个配置会在 JDK 版本以 '1.4' 开头的环境中触发:



<profiles>
<profile>
    <activation>
        <jdk>1.4</jdk>
    </activation>
...
</profile>
</profiles>



 当然也可以制定符合条件的 JDK 范围,下面的例子声明了在1.3、1.4、1.5 版本中触发:


<profiles>
<profile>
    <activation>
        <jdk>[1.3,1.6)</jdk>
    </activation>
...
</profile>
</profiles>



 下面是一个通过操作系统版本设置触发 profile 的例子,更多的操作系统值参考这个 文档

<profiles>
<profile>
    <activation>
        <os>
            <name>Windows XP</name>
            <family>Windows</family>
            <arch>x86</arch>
            <version>5.1.2600</version>
        </os>
    </activation>
...
</profile>
</profiles>
 
 
<profiles>
<profile>
    <activation>
        <property>
            <name>debug</name>
        </property>
    </activation>
...
</profile>
</profiles>
 
 
<profiles>
<profile>
    <activation>
        <property>
            <name>environment</name>
            <value>test</value>
        </property>
    </activation>
...
</profile>
</profiles>

 激活上述的 profile ,可以通过下面这个命令行来实现


mvn groupId:artifactId:goal -Denvironment=test


Note:环境变量会被正则化,比如 Foo 会被正则成为 env.Foo , 另外在 windows 中环境变量统一会被正则成全大写的形式。



下面这个 profile 会在 target/generated-sources/axistools/wsdl2java/org/apache/maven 这个文件丢失的时候触发。



<profiles>
<profile>
    <activation>
        <file>
            <missing>target/generated-sources/axistools/wsdl2java/org/apache/maven</missing>
        </file>
    </activation>
...
</profile>
</profiles>



Profiles 也可以通过配置 <activeByDefault> 激活,如下面的示例:



<profiles>
<profile>
    <id>profile-1</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
...
</profile>
</profiles>



Deactivating a profile



从 Maven 2.0.10 起,可以禁默一个或多个 profiles,方法是在命令行中在这些 profile id 前面加一个 '!' 或者 '-',如下例所示:

mvn groupId:artifactId:goal -P !profile-1,!profile-2

这个可以禁默通过  activeByDefault 设置激活的profile,也可以禁默被 activation 配置激活的 profile。


 



Which areas of a POM can be customized by each type of profile? Why?



 



我们在这里再讨论一下 profile 可以用来指定什么东西,这个问题像 profile 配置的其他方面一下,答案不是很直观。



根据你选择在哪里配置你的 profile,你将会接触到各种 POM 配置选项。



 



  • Profiles in external files



在外部文件(比如在 settings.xml 或者 profiles.xml)中指定的 profile 在严格意义上说是不可移植的。任何看起来很大可能性改变构建结果的配置都应该严格放在 POM 文件里。像一些仓库列表这样的信息可以放到外部文件中。因此你仅能修改<repositories> 和 <pluginRepositories> 这样的配置,再加上额外的 <properties> 配置。



<properties> 配置允许你以 key-value 的形式指定哪些信息在处理 POM 的时候会被篡改。这使你可以通过 ${profile.provided.path} 的方式指定一个插件的配置。



 



  • Profiles in POMs



另一方面,如果你的 profiles 可以合理地在 POM 中指定,那你的选择余地就大的多。权衡的标准是你可以仅仅修改对应的工程和它的子模块。这些 profiles 是内联指定的,所以能更好地保证程序的可移植性,因为其他人可以毫无风险地应用你的修改。



在 POM 文件里的 profile 可以修改 POM 的下列元素:


• <repositories>
• <pluginRepositories>
• <dependencies>
• <plugins>
• <properties>
• <modules>
• <reporting>
• <dependencyManagement>
• <distributionManagement>


<build> 下面的元素,主要是:


• <defaultGoal>
• <resources>
• <testResources>
• <finalName>

 



Profile Pitfalls



我们前面已经提到了添加 profile 到你的构建中有可能破坏工程的可移植性,那么我们就尽可能多地强调一下哪些环境中对 profile 的使用会导致工程不可移植的问题。



在使用 profile 的时候,两个主要的问题要牢记于心:一个是外部属性(通常用在插件配置中);另一个是 profile 自然集的不完全指定。



 


External Properties


 



失败。考虑到一个 web 应用工程的 pom 片段如下:



<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>


  然后你再本地 的 ~/.m2/settings.xml 中有如下配置片段:


<settings>
...
<profiles>
    <profile>
        <id>appserverConfig</id>
        <properties>
            <appserver.home>/path/to/appserver</appserver.home>
        </properties>
    </profile>
</profiles>

<activeProfiles>
    <activeProfile>appserverConfig</activeProfile>
</activeProfiles>
...
</settings>


  当你构建   integration-test 

的时候,你的集成测试通过,因为你提供给测试插件的路径是有效的。


但是当你的同事试图构建 integration-test 的时候缺发现构建失败,原因是无法解析 <appserverHome> 元素。



 



Incomplete Specification of a Natural Profile Set



除了上述问题外,经常出错的是在 profile 中没有覆盖到所有的情况。我们来看看下面这个 pom.xml 例子:



<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>


现在我们考虑下面这个在 pom.xml 中的 profile:


<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>


  这个 profile 看起来跟上面那个例子很像,只有一点不同:这里又配置了一个   appserverConfig-dev-2   这个profile,这个 profile 是通过 env=dev-2 来激活的。


这样,执行:



mvn -Denv=dev-2 integration-test

 

可以构建成功,应用的属性是由  appserverConfig-dev-2 提供的。当我们执行:

mvn -Denv=dev integration-test


  也可以构建成功, 应用的属性是由  appserverConfig-dev 提供的。但是当我们执行:


mvn -Denv=production integration-test

  将会构建失败。原因是上述两个 profile 中的任何一个都没有被激活,导致 appserverHome 这个参数找不到。

 



How can I tell which profiles are in effect during a build?



 



我们可以用 Maven Help Plugin 来确定构建期间那个 profile 在起作用。



mvn help:active-profiles

 

这里我们一起来看几个例子,来更好地理解一下 active-profiles。在上面那个 profile 的配置情况下,输入下面的 maven 命令


mvn help:active-profiles -Denv=dev


  会得到这样的结果:


The following profiles are active:

- appserverConfig-dev (source: pom)
 
 
mvn help:effective-pom -P appserverConfig-dev -Doutput=effective-pom.xml


  Naming Conventions



 



现在我们知道了 profile 是解决多环境下构建差异化问题的很自然的解决方法。我们称 profile 中解决差异化问题的概念是'自然集',对这个集合的思考是非常重要的。



关于如何组织和管理自然集的变更是非常重要的。就像优秀的开发人员会写出自解释的代码一样,profile 的 id 也需要对他的意图有比较好的暗示。一个很好的实践是使用公共系统属性触发器作为 profile 名字的一部分。比如用 env-dev,env-test,env-prod 作为 profile 的名字,这些 profile 都是被 env 这个系统属性触发的。这样的系统在特殊环境的构建时具有很好的直觉暗示性。这样你要是想激活 env-test 可以输入下面这样的 maven 命令:


mvn -Denv=test <phase>