I want to make project with two modules. App and server. Server depends on app. When I compile server I want to include class files from app into the build. But it resolves classpath relatively to server and not to app because of import issues. How to make ant resolve app locations relatively to app and server locations relatively to server. I din't get how it's done in ant docs. Could you please explain in a more simple way? Code snippet to clarify question little bit.
App build.xml:
<project name="app">
<property name="app.build.dir" location="build"/>
<target name="compile">
<echo message="Compiling app to ${app.build.dir}"/>
</target>
</project>
Server build.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>
Output:
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
Desired output:
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
A simple approach would be the following: In the build.xml for app instead of
<property name="app.build.dir" location="build"/>
use
<property name="app.build.dir" location="../app/build"/>
If you specify a property by location (and with a relative path), ant resolves the path relative to your current project. With this notation, ant first goes up a directory level and then down to the app dir, which is right from both of your projects.
A better approach would be to put the settings used by both build scripts in a separate property file and include this file from both builds.
Multi module builds are difficult because there are no standards, each build author have his own approach to solving this problem.
My personal preference is to emulate how Maven does it. Each module creates and publishes a jar file to the "local" repository. This jar file is then a dependency of the other modules that consume its classes. This approach creates clean separation between modules and means you don't need to build the entire project when working on one sub-module.
So how is this done using ANT? Well you'll need to embrace another Maven concept, dependency management. The ivy plugin provides this feature to ANT.
Example
My dummy project. A single module called "app" which is dependency of the "server" module
├── 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
..
Unless you customize locations, ivy uses the following directories to store files:
~/.ivy2/cache <-- Downloaded 3rd party dependencies go here
~/.ivy2/local <-- Repository which is private to the user.
Creating alternative storage locations and leveraging Maven repository managers is beyond the scope of this question.
After running this example by build produces the following explicitly versioned files:
~/.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
Builds all modules in the correct order. This is determined by the module dependencies documented in each module's ivy.xml file (See ivy buildlist task). This is a very useful feature when you have a large number of interdependent modules.
<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>
Notes:
- Contains logic to ensure the ivy jar dependency is installed if missing
- Ivy will cache downloaded 3rd party dependencies. The "clean-all" task is useful for ensuring the build is sweaky clean :-)
app/ivy.xml
Lists the 3rd party dependencies that the module has. This is a very useful Maven feature. Dependencies get downloaded automatically from Maven Central. No need to commit them into your source code repository.
<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>
Note:
- Ivy configurations are used to classify and group dependencies. Used later to populate classpaths
app/build.xml
Pretty standard build process. Code is compiled tested and packaged. Note how ivy configurations are used to control the classpaths.
The "publish" target is worthy of special note it pushes the built jar into a local location where it can be picked up by other module builds.
<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>
Notes:
- The ivy buildnumber task is really useful for ensuring your build number is properly incremented each time you run a build. It looks at the files previously published.
server/ivy.xml
This module has a single dependency on the latest version of the "app" module. The actual revision number is determined at build time based on the files present in the local repository.
<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>
server/build.xml
This build just packages up the libraries into a WAR file. What makes it noteworthy is it's use of the ivy retrieve task. It will pull the "app" module dependency and all its transitive dependencies. It can be difficult to keep track of these manually.
<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>
来源:https://stackoverflow.com/questions/25114699/how-to-import-properties-and-targets-from-ant-build-file-properly