Как правильно импортировать свойства и цели из файла сборки ant?

Я хочу сделать проект с двумя модулями. Приложение и сервер. Сервер зависит от приложения. Когда я компилирую сервер, я хочу включить файлы классов из приложения в сборку. Но он разрешает classpath относительно сервера, а не приложения из-за проблем с импортом. Как заставить ant разрешать расположение приложений относительно расположения приложений и серверов относительно server. Я не понимаю, как это делается в ant docs. Не могли бы вы объяснить более простым способом? Фрагмент кода, чтобы немного прояснить вопрос.

Сборка приложения.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

person Moses    schedule 04.08.2014    source источник


Ответы (2)


Простым подходом будет следующий: в файле build.xml для приложения вместо

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

использовать

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

Если вы указываете свойство по местоположению (и с относительным путем), ant разрешает путь относительно вашего текущего проекта. С этой нотацией ant сначала поднимается на уровень каталога, а затем спускается в каталог приложения, который находится прямо из обоих ваших проектов.

Лучшим подходом было бы поместить параметры, используемые обоими скриптами сборки, в отдельный файл свойств и включить этот файл из обеих сборок.

person Ralf Wagner    schedule 05.08.2014
comment
Я подумал, что могу каким-то образом указать муравью определить местоположение относительно файла, в котором определено это свойство. Грустно, что я не могу. Все равно спасибо. - person Moses; 05.08.2014

Многомодульные сборки сложны, потому что нет стандартов, у каждого автора сборки свой подход к решению этой проблемы.

Лично я предпочитаю подражать тому, как это делает Maven. Каждый модуль создает и публикует jar-файл в «локальном» репозитории. Затем этот файл jar является зависимостью других модулей, которые используют его классы. Этот подход создает четкое разделение между модулями и означает, что вам не нужно создавать весь проект при работе над одним подмодулем.

Так как же это делается с помощью ANT? Ну, вам нужно будет принять другую концепцию Maven, управление зависимостями. подключаемый модуль ivy предоставляет эту функцию для ANT.

Пример

Мой фиктивный проект. Один модуль под названием «приложение», который зависит от модуля «сервер».

├── 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 список сборки). Это очень полезная функция при наличии большого количества взаимозависимых модулей.

<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 будет кэшировать загруженные сторонние зависимости. Задача «очистить все» полезна для обеспечения безупречной чистоты сборки :-)

приложение /ivy.xml

Список сторонних зависимостей, которые имеет модуль. Это очень полезная функция 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 используются для классификации и группировки зависимостей. Используется позже для заполнения путей к классам

приложение/build.xml

Довольно стандартный процесс сборки. Код скомпилирован, протестирован и упакован. Обратите внимание, как конфигурации плюща используются для управления путями к классам.

Цель «публикация» заслуживает особого упоминания: она помещает встроенный jar в локальное расположение, откуда его могут подобрать другие сборки модуля.

<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 номер сборки действительно полезна для обеспечения число правильно увеличивается каждый раз, когда вы запускаете сборку. Он просматривает ранее опубликованные файлы.

сервер /ivy.xml

Этот модуль имеет единственную зависимость от последней версии модуля «приложение». Фактический номер версии определяется во время сборки на основе файлов, присутствующих в локальном репозитории.

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

сервер/сборка.xml

Эта сборка просто упаковывает библиотеки в файл WAR. Что делает его примечательным, так это использование ivy retrive задача. Он вытянет зависимость модуля «приложение» и все его транзитивные зависимости. Отследить их вручную может быть сложно.

<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>
person Mark O'Connor    schedule 04.08.2014
comment
Спасибо за большой и подробный ответ. Очень хороший пример, и если когда-нибудь я решу изучить ivy, я обязательно им воспользуюсь. Но пока мой вопрос не настолько продвинут. Мне просто нужно было разрешить путь к файлу. И я действительно не хочу подключать плющ, потому что это еще одна вещь, которую нужно изучить. Можно ли просто с помощью муравья разрешить путь относительно загруженного файла? - person Moses; 04.08.2014
comment
Ознакомьтесь с разделом Сопоставление файлов с импортированными файлами в документации по задачам импорта: ant.apache.org /manual/Tasks/import.html - person Mark O'Connor; 04.08.2014
comment
Вот откуда я родом. Основываясь на ваших ответах и ​​ответах Ральфа, я думаю, что то, что я хочу, невозможно. Еще раз спасибо за ваше время и очень полный ответ. - person Moses; 05.08.2014