【问题标题】:How to import properties and targets from ant build file properly?如何从 ant 构建文件中正确导入属性和目标?
【发布时间】:2014-08-04 08:39:46
【问题描述】:

我想用两个模块制作项目。应用程序和服务器。服务器取决于应用程序。当我编译服务器时,我想将应用程序中的类文件包含到构建中。但是由于导入问题,它相对于服务器而不是应用程序解决了类路径。如何让蚂蚁相对于应用程序和相对于服务器的服务器位置解析应用程序位置。我不明白它是如何在 ant 文档中完成的。你能用更简单的方式解释一下吗?代码 sn-p 来澄清一点问题。

应用构建.xml:

<project name="app">
<property name="app.build.dir" location="build"/>

<target name="compile">
    <echo message="Compiling app to ${app.build.dir}"/>
</target>
</project>

服务器构建.xml:

<project name="server">
<property name="server.build.dir" location="build"/>

<include file="../app/build.xml"/>

<target name="compile" depends="app.compile">
    <echo message="Compiling server to ${server.build.dir} using classpath: ${app.build.dir}"/>
</target>
</project>

输出:

Buildfile: D:\work\test\ant-test2\server\build.xml

app.compile:
 [echo] Compiling to D:\work\test\ant-test2\server\build

compile:
 [echo] Compiling server to D:\work\test\ant-test2\server\build using classpath:  D:\work\test\ant-test2\server\build

BUILD SUCCESSFUL
Total time: 0 seconds

期望的输出:

Buildfile: D:\work\test\ant-test2\server\build.xml

app.compile:
 [echo] Compiling to D:\work\test\ant-test2\app\build

compile:
 [echo] Compiling server to D:\work\test\ant-test2\server\build using classpath:  D:\work\test\ant-test2\app\build

BUILD SUCCESSFUL
Total time: 0 seconds

【问题讨论】:

    标签: ant build multi-module


    【解决方案1】:

    多模块构建很困难,因为没有标准,每个构建作者都有自己的方法来解决这个问题。

    我个人的偏好是模仿 Maven 的做法。每个模块创建一个 jar 文件并将其发布到“本地”存储库。然后,此 jar 文件是使用其类的其他模块的依赖项。这种方法在模块之间创建了清晰的分离,意味着您在处理一个子模块时不需要构建整个项目。

    那么这是如何使用 ANT 完成的呢?那么你需要接受另一个 Maven 概念,依赖管理。 ivy plugin 为 ANT 提供了此功能。

    示例

    我的虚拟项目。一个名为“app”的模块,它是“server”模块的依赖项

    ├── build.xml         <-- Builds all modules in correct order
    ├── app
    │   ├── build.xml
    │   ├── ivy.xml       <-- Describes module dependencies
    │   └── src
    |         ..
    └── server
        ├── build.xml
        ├── ivy.xml       <-- Dependency on the "app" module
        └── src
              ..
    

    除非您自定义位置,否则 ivy 使用以下目录来存储文件:

    ~/.ivy2/cache   <-- Downloaded 3rd party dependencies go here
    ~/.ivy2/local   <-- Repository which is private to the user. 
    

    创建替代存储位置和利用 Maven 存储库管理器超出了此问题的范围。

    通过构建运行此示例后,会生成以下显式版本文件:

    ~/.ivy2/local/com.myspotontheweb/demo-app/1.0.0/jars/demo-app.jar
    ~/.ivy2/local/com.myspotontheweb/demo-server/1.0.0/wars/demo-server.war
    

    build.xml

    以正确的顺序构建所有模块。这是由每个模块的 ivy.xml 文件中记录的模块依赖关系决定的(参见 ivy buildlist 任务)。当您有大量相互依赖的模块时,这是一个非常有用的功能。

    <project name="demo" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
    
      <available classname="org.apache.ivy.Main" property="ivy.installed"/> 
    
      <target name="install-ivy" unless="ivy.installed">
        <mkdir dir="${user.home}/.ant/lib"/>
        <get dest="${user.home}/.ant/lib/ivy.jar" src="http://search.maven.org/remotecontent?filepath=org/apache/ivy/ivy/2.3.0/ivy-2.3.0.jar"/>
        <fail message="Ivy has been installed. Run the build again"/>
      </target>
    
      <target name="build-list" depends="install-ivy">
        <ivy:buildlist reference="build-path">
          <fileset dir="." includes="**/build.xml" excludes="build.xml"/>
        </ivy:buildlist>
      </target>
    
      <target name="build" depends="build-list">
        <subant buildpathref="build-path">
          <target name="clean"/>
          <target name="publish"/>
        </subant>
      </target>
    
      <target name="clean" depends="build-list">
        <subant buildpathref="build-path">
          <target name="clean"/>
        </subant>
      </target>
    
      <target name="clean-all" depends="clean">
        <ivy:cleancache/>
      </target>
    
    </project>
    

    注意事项:

    • 包含确保 ivy jar 依赖项在缺失时安装的逻辑
    • Ivy 将缓存下载的第 3 方依赖项。 “clean-all”任务对于确保构建干净整洁很有用:-)

    app/ivy.xml

    列出模块具有的第 3 方依赖项。这是一个非常有用的 Maven 特性。依赖项会从 Maven Central 自动下载。无需将它们提交到您的源代码存储库中。

    <ivy-module version="2.0">
      <info organisation="com.myspotontheweb" module="demo-app"/>
    
      <configurations>
        <conf name="compile" description="Required to compile application"/>
        <conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
        <conf name="test"    description="Required for test only" extends="runtime"/>
      </configurations>
    
      <publications>
        <artifact name="demo-app"/>
      </publications>
    
      <dependencies>
        <!-- compile dependencies -->
        <dependency org="org.slf4j" name="slf4j-api" rev="1.7.5" conf="compile->default"/>
    
        <!-- runtime dependencies -->
        <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.7.5" conf="runtime->default"/>
    
        <!-- test dependencies -->
        <dependency org="junit" name="junit" rev="4.11" conf="test->default"/>
      </dependencies>
    
    </ivy-module>
    

    注意:

    • Ivy 配置用于对依赖项进行分类和分组。稍后用于填充类路径

    app/build.xml

    相当标准的构建过程。代码经过编译测试和打包。请注意如何使用 ivy 配置来控制类路径。

    “发布”目标值得特别注意,它将构建的 jar 推送到 local location 中,其他模块构建可以获取它。

    <project name="demo-app" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
    
        <!--
        ================
        Build properties
        ================
        -->
        <property name="src.dir"          location="src/main/java"/>
        <property name="resources.dir"    location="src/main/resources"/>
        <property name="test.src.dir"     location="src/test/java"/>
        <property name="build.dir"        location="target"/>
        <property name="dist.dir"         location="${build.dir}/dist"/>
    
        <property name="jar.main.class" value="org.demo.App"/>
        <property name="jar.file"       value="${dist.dir}/${ant.project.name}.jar"/>
    
        <property name="pub.revision" value="1.0"/>
        <property name="pub.resolver" value="local"/>
    
        <!--
        ===========
        Build setup
        ===========
        -->
        <target name="resolve" description="Use ivy to resolve classpaths">
            <ivy:resolve/>
    
            <ivy:report todir='${build.dir}/ivy-reports' graph='false' xml='false'/>
    
            <ivy:cachepath pathid="compile.path" conf="compile"/>
            <ivy:cachepath pathid="test.path"    conf="test"/>
        </target>
    
        <!--
        ===============
        Compile targets
        ===============
        -->
        <target name="resources" description="Copy resources into classpath">
            <copy todir="${build.dir}/classes">
                <fileset dir="${resources.dir}"/>
            </copy>
        </target>
    
        <target name="compile" depends="resolve,resources" description="Compile code">
            <mkdir dir="${build.dir}/classes"/>
            <javac srcdir="${src.dir}" destdir="${build.dir}/classes" includeantruntime="false" debug="true" classpathref="compile.path"/>
        </target>
    
        <target name="compile-tests" depends="compile" description="Compile tests">
            <mkdir dir="${build.dir}/test-classes"/>
            <javac srcdir="${test.src.dir}" destdir="${build.dir}/test-classes" includeantruntime="false" debug="true">
                <classpath>
                    <path refid="test.path"/>
                    <pathelement path="${build.dir}/classes"/>
                </classpath>
            </javac>
        </target>
    
        <!--
        ============
        Test targets
        ============
        -->
        <target name="test" depends="compile-tests" description="Run unit tests">
            <mkdir dir="${build.dir}/test-reports"/>
            <junit printsummary="yes" haltonfailure="yes">
                <classpath>
                    <path refid="test.path"/>
                    <pathelement path="${build.dir}/classes"/>
                    <pathelement path="${build.dir}/test-classes"/>
                </classpath>
                <formatter type="xml"/>
                <batchtest fork="yes" todir="${build.dir}/test-reports">
                    <fileset dir="${test.src.dir}">
                        <include name="**/*Test*.java"/>
                        <exclude name="**/AllTests.java"/>
                    </fileset>
                </batchtest>
            </junit>
        </target>
    
        <!--
        =====================
        Build project
        =====================
        -->
        <target name="build" depends="test" description="Create executable jar archive">
            <ivy:retrieve pattern="${dist.dir}/lib/[artifact]-[revision](-[classifier]).[ext]" conf="runtime"/>
    
            <manifestclasspath property="jar.classpath" jarfile="${jar.file}">
                <classpath>
                    <fileset dir="${dist.dir}/lib" includes="*.jar"/>
                </classpath>
            </manifestclasspath>
    
            <jar destfile="${jar.file}" basedir="${build.dir}/classes">
                <manifest>
                    <attribute name="Main-Class" value="${jar.main.class}" />
                    <attribute name="Class-Path" value="${jar.classpath}" />
                </manifest>
            </jar>
        </target>
    
        <!--
        =====================
        Publish project
        =====================
        -->
        <target name="publish" depends="build" description="Publish artifacts to shared repo">
          <ivy:buildnumber organisation="${ivy.organisation}" module="${ivy.module}" revision="${pub.revision}"/>
    
          <ivy:publish resolver="${pub.resolver}" pubrevision="${ivy.new.revision}">
            <artifacts pattern="${build.dir}/dist/[artifact].[ext]"/>
          </ivy:publish>
        </target>
    
        <!--
        =============
        Clean project
        =============
        -->
        <target name="clean" description="Cleanup build files">
            <delete dir="${build.dir}"/>
        </target>
    
    </project>
    

    注意事项:

    • ivy buildnumber 任务对于确保每次运行构建时正确增加构建号非常有用。它会查看之前发布的文件。

    服务器/ivy.xml

    此模块仅依赖于最新版本的“app”模块。实际的修订号是在构建时根据本地存储库中存在的文件确定的。

    <ivy-module version="2.0">
      <info organisation="com.myspotontheweb" module="demo-server"/>
    
      <configurations>
        <conf name="compile" description="Required to compile application"/>
        <conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
        <conf name="test"    description="Required for test only" extends="runtime"/>
      </configurations>
    
      <publications>
        <artifact name="demo-server" type="war"/>
      </publications>
    
      <dependencies>
        <!-- runtime dependencies -->
        <dependency org="com.myspotontheweb" name="demo-app" rev="latest.integration" conf="runtime"/>
      </dependencies>
    
    </ivy-module>
    

    服务器/build.xml

    这个构建只是将库打包到一个 WAR 文件中。值得注意的是它使用了 ivy retrieve 任务。它将拉取“app”模块依赖项及其所有传递依赖项。手动跟踪这些可能很困难。

    <project name="demo-server" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
    
        <!--
        ================
        Build properties
        ================
        -->
        <property name="build.dir"        location="target"/>
        <property name="dist.dir"         location="${build.dir}/dist"/>
    
        <property name="war.file"       value="${dist.dir}/${ant.project.name}.war"/>
    
        <property name="pub.revision" value="1.0"/>
        <property name="pub.resolver" value="local"/>
    
        <!--
        ===========
        Build setup
        ===========
        -->
        <target name="resolve" description="Use ivy to resolve classpaths">
            <ivy:resolve/>
            <ivy:report todir='${build.dir}/ivy-reports' graph='false' xml='false'/>
        </target>
    
        <!--
        =====================
        Build project
        =====================
        -->
        <target name="build" depends="resolve" description="Create executable jar archive">
            <ivy:retrieve pattern="${build.dir}/lib/[artifact]-[revision](-[classifier]).[ext]" conf="runtime"/>
    
            <war destfile="${war.file}" webxml="src/resources/web.xml">
               <lib dir="${build.dir}/lib"/>
            </war>
        </target>
    
        <!--
        =====================
        Publish project
        =====================
        -->
        <target name="publish" depends="build" description="Publish artifacts to shared repo">
          <ivy:buildnumber organisation="${ivy.organisation}" module="${ivy.module}" revision="${pub.revision}"/>
    
          <ivy:publish resolver="${pub.resolver}" pubrevision="${ivy.new.revision}">
            <artifacts pattern="${build.dir}/dist/[artifact].[ext]"/>
          </ivy:publish>
        </target>
    
        <!--
        =============
        Clean project
        =============
        -->
        <target name="clean" description="Cleanup build files">
            <delete dir="${build.dir}"/>
        </target>
    
    </project>
    

    【讨论】:

    • 感谢您的详细回答。非常好的例子,如果有一天我决定学习常春藤,我一定会使用它。但是现在我的问题还没有那么先进。我只需要解析文件路径。而且我真的不想插入常春藤,因为这是另一回事。是否可以仅使用 ant 来解析相对于加载文件的路径?
    • 查看导入任务文档中的“针对导入的文件解析文件”部分:ant.apache.org/manual/Tasks/import.html
    • 我就是从那里来的。根据您和 Ralfs 的回答,我想我想要的是不可能的。再次感谢您的时间和非常完整的答案。
    【解决方案2】:

    一个简单的方法如下:在 app 的 build.xml 中,而不是

    <property name="app.build.dir" location="build"/>
    

    使用

    <property name="app.build.dir" location="../app/build"/>
    

    如果您按位置(并使用相对路径)指定属性,则 ant 会解析相对于您当前项目的路径。使用这种表示法,ant 首先上升到一个目录级别,然后下降到应用程序目录,它就在您的两个项目中。

    更好的方法是将两个构建脚本使用的设置放在一个单独的属性文件中,并在两个构建中包含此文件。

    【讨论】:

    • 我想我可以以某种方式告诉 ant 相对于定义此属性的文件来解析位置。很遗憾我做不到。还是谢谢你。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-29
    相关资源
    最近更新 更多