Статьи

Maven: выливание воды из ванны, удержание ребенка

… другими словами, первый шаг к использованию Maven для управления зависимостями, но НЕ сборок. Это ирония Maven … они объединили две вещи (управление зависимостями и сборки) таким образом, что они делают полезную (управление зависимостями) болезненной, потому что система сборки настолько ужасна.

В качестве промежуточного шага между полным Maven и (наиболее вероятно) Gradle , я искал способ использовать Maven для зависимостей только таким образом, который совместим с Eclipse … без использования часто ненадежного и ненадежного плагина M2Eclipse .

В любом случае, вместо того, чтобы предполагать, что зависимости могут измениться в любой момент времени , давайте предположим, что когда я изменяю зависимости (вручную редактируя pom.xml), я знаю это и готов запустить команду, чтобы вызвать Eclipse (и мой Сборка на основе муравьев) в соответствии.

Во-первых, мой pom.xml:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>myapp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-hibernate</artifactId>
      <version>${tapestry-version}</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>log4j</artifactId>
          <groupId>log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>slf4j-log4j12</artifactId>
          <groupId>org.slf4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.tapestry</groupId>
      <artifactId>tapestry-test</artifactId>
      <version>5.2.0-SNAPSHOT</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>2.4.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search</artifactId>
      <version>3.1.1.GA</version>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.8.0</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>0.9.17</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.5.8</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>8.4-701.jdbc4</version>
    </dependency>
    <dependency>
      <groupId>xerces</groupId>
      <artifactId>xercesImpl</artifactId>
      <version>2.4.0</version>
      <type>jar</type>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.howardlewisship</groupId>
      <artifactId>tapx-datefield</artifactId>
      <version>${tapx-version}</version>
    </dependency>
    <dependency>
      <groupId>com.howardlewisship</groupId>
      <artifactId>tapx-prototype</artifactId>
      <version>${tapx-version}</version>
    </dependency>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>5.9</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <repositories>
    <repository>
      <id>tapestry360-snapshots</id>
      <url>http://tapestry.formos.com/maven-snapshot-repository/</url>
    </repository>
    <repository>
      <id>repository.jboss.org</id>
      <url>http://repository.jboss.org/maven2/</url>
    </repository>
  </repositories>
  <properties>
    <tapestry-version>5.1.0.5</tapestry-version>
    <lucene-version>2.4.1</lucene-version>
    <tapx-version>1.0.0-SNAPSHOT</tapx-version>
  </properties>
</project>

(Это было адаптировано из одного из POM моего клиента).

Затем файл сборки Ant, который компилирует это, выполняет тесты и создает WAR:

<project name="example" xmlns:mvn="urn:maven-artifact-ant">

  <property name="classes.dir" value="target/classes" />
  <property name="test.classes.dir" value="target/test-classes" />
  <property name="web.lib.dir" value="target/web-libs" />
  <property name="webapp.dir" value="src/main/webapp" />
  <property name="webinf.dir" value="${webapp.dir}/WEB-INF" />

  <path id="compile.path">
    <fileset dir="lib/provided" includes="*.jar"/>
    <fileset dir="lib/runtime" includes="*.jar" />
  </path>

  <path id="test.path">
    <path refid="compile.path" />
    <pathelement path="${classes.dir}" />
    <fileset dir="lib/test" includes="*.jar" />
  </path>

  <target name="clean" description="Delete all derived files.">
    <delete dir="target" quiet="true" />
  </target>

  <!-- Assumes that Maven's Ant library is installed in ${ANT_HOME}/lib/ext. -->

  <target name="-setup-maven">
    <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="urn:maven-artifact-ant" />
    <mvn:pom id="pom" file="pom.xml" />
  </target>

  <macrodef name="copy-libs">
    <attribute name="filesetrefid" />
    <attribute name="todir" />
    <sequential>
      <mkdir dir="@{todir}" />
      <copy todir="@{todir}">
        <fileset refid="@{filesetrefid}" />
        <mapper type="flatten" />
      </copy>
    </sequential>
  </macrodef>

  <macrodef name="rebuild-lib">
    <attribute name="base" />
    <attribute name="scope" />
    <attribute name="libs.id" default="@{base}.libs" />
    <attribute name="src.id" default="@{base}.src" />
    <sequential>
      <mvn:dependencies pomrefid="pom" filesetid="@{libs.id}" sourcesFilesetid="@{src.id}" scopes="@{scope}" />
      <copy-libs filesetrefid="@{libs.id}" todir="lib/@{base}" />
      <copy-libs filesetrefid="@{src.id}" todir="lib/@{base}-src" />
    </sequential>
  </macrodef>

  <target name="refresh-libraries" depends="-setup-maven" description="Downloads runtime and test libraries as per POM.">
    <delete dir="lib" quiet="true" />
    <rebuild-lib base="provided" scope="provided"/>
    <rebuild-lib base="runtime" scope="runtime,compile" />
    <rebuild-lib base="test" scope="test" />
    <echo>
      
*** Use the rebuild-classpath command to update the Eclipse .classpath file.</echo>
  </target>

  <target name="compile" description="Compile main source code.">
    <mkdir dir="${classes.dir}" />
    <javac srcdir="src/main/java" destdir="${classes.dir}" debug="true" debuglevel="lines,vars,source">
      <classpath refid="compile.path" />
    </javac>
  </target>

  <target name="compile-tests" depends="compile" description="Compile test sources.">
    <mkdir dir="${test.classes.dir}" />
    <javac srcdir="src/test/java" destdir="${test.classes.dir}" debug="true" debuglevel="lines,vars,source">
      <classpath refid="test.path" />
    </javac>
  </target>

  <target name="run-tests" depends="compile-tests" description="Run unit and integration tests.">
    <taskdef resource="testngtasks" classpathref="test.path" />
    <testng haltonfailure="true">
      <classpath>
        <path refid="test.path" />
        <pathelement path="${test.classes.dir}" />
      </classpath>

      <xmlfileset dir="src/test/conf" includes="testng.xml" />

    </testng>
  </target>


  <target name="war" depends="run-tests,-setup-maven" description="Assemble WAR file.">

    <!-- Copy and flatten the libraries ready for packaging. -->

    <mkdir dir="${web.lib.dir}" />
    <copy todir="${web.lib.dir}" flatten="true">
      <fileset dir="lib/runtime" />
    </copy>
    <jar destfile="${web.lib.dir}/${pom.artifactId}-${pom.version}.jar" index="true">
      <fileset dir="src/main/resources" />
      <fileset dir="${classes.dir}" />
    </jar>

    <war destfile="target/${pom.artifactId}-${pom.version}.war">
      <fileset dir="${webapp.dir}" />
      <lib dir="${web.lib.dir}" />
    </war>
  </target>
</project>

Ключевая цель здесь — обновить библиотеки, которая удаляет каталог lib, а затем снова заполняет его. Он создает подпапку для каждой области (lib / предоставляются, lib / runtime, lib / test) и другую подпапку для исходных файлов JAR (lib / предоставляются-src, lib / runtime-src и т. Д.).

Так как это поможет Eclipse? Рубин на помощь:

#!/usr/bin/ruby
# Rebuild the .classpath file based on the contents of lib/runtime, etc.

# Probably easier using XML Generator but don't have the docs handy

def process_scope(f, scope)
 # Now find the actual JARs and add an entry for each one.
 
 dir = "lib/#{scope}"

 return unless File.exists?(dir)
 
 Dir.entries(dir).select { |name| name =~ /\.jar$/ }.sort.each do |name|
   f.write %{  <classpathentry kind="lib" path="#{dir}/#{name}"}
   
   srcname = dir + "-src/" + name.gsub(/\.jar$/, "-sources.jar") 

   if File.exist?(srcname)
      f.write %{ sourcepath="#{srcname}"}
   end
   
   f.write %{/>\n}
 end
end

File.open(".classpath", "w") do |f|
  f.write %{<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path="src/main/java"/>
  <classpathentry kind="lib" path="src/main/resources"/>
  <classpathentry kind="src" path="src/test/java"/>
  <classpathentry kind="lib" path="src/test/resources"/>
  <classpathentry kind="output" path="target/classes"/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
}

 process_scope(f, "provided")
 process_scope(f, "runtime")
 process_scope(f, "test")
 
 f.write %{
</classpath>
}
    
end

Это довольно хорошо для получаса работы. Раньше это было намного сложнее (в Maven 2.0.9), но новый атрибут scopes в задаче зависимостей Maven имеет все значение.

Используя это, у вас остается выбор: либо вы не регистрируете .classpath и содержимое папки lib, в этом случае вам нужно выполнить цель и скрипт, чтобы быть функциональными … или вы просто проверяете все в. Я использую GitHub для своего репозитория проекта … дополнительное пространство для нескольких МБ библиотек не является проблемой и гарантирует, что я могу установить точный путь к классу, необходимый другим разработчикам в проекте, ни с одним из обычных Maven догадки. Я с нетерпением жду, чтобы никогда не сказать «Но это работает для меня?» или «Какую версию всего, что вы установили?» или «Попробуйте чистую сборку, закройте и снова откройте ваш проект, а затем удалите и добавьте поддержку зависимостей Maven, а затем снова пожертвуйте небольшим козлом».

Следующий? Упаковка большей части этого в библиотеку Ant, чтобы я мог легко использовать ее в разных проектах … или потратить время на изучение Gradle и позволить ему обрабатывать большую часть этого отвлекающего мусора.

С http://tapestryjava.blogspot.com/