如何解决maven插件中所有模块的依赖关系?

时间:2016-04-12 23:37:52

标签: maven maven-plugin maven-assembly-plugin

我正在编写一个Maven插件来获取已解析的依赖项。它适用于单个模块项目/ pom,但在多个模块项目中失败。

这是一段代码片段

@Mojo(
  name="scan",
  aggregator = true,
  defaultPhase = LifecyclePhase.COMPILE,
  threadSafe = true,
  requiresDependencyCollection = ResolutionScope.TEST,
  requiresDependencyResolution = ResolutionScope.TEST,
  requiresOnline = true
)
public class MyMojo extends AbstractMojo {

  @Parameter(property = "project", required = true, readonly = true)
  private MavenProject project;

  @Parameter(property = "reactorProjects", required = true, readonly = true)
  private List<MavenProject> reactorProjects;


  @Override
  public void execute() throws MojoExecutionException {
    for(MavenProject p : reactorProjects) {
      for(Artifact a : p.getArtifacts()) {
         ...consolidate artifacts
      }
    }
  }
}

上述代码将整合所有模块中所有已解析的依赖项,但它包含一些额外的依赖项。

这是一个可以使用的示例项目。请下载this github repo

从modules-project主文件夹,请运行

mvn dependency:tree -Dverbose -Dincludes=commons-logging

你应该看到像这样的输出

[INFO] ------------------------------------------------------------------------
[INFO] Building core 0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ core ---
[INFO] com.github:core:jar:0.1-SNAPSHOT
[INFO] \- axis:axis:jar:1.4:compile
[INFO]    +- commons-logging:commons-logging:jar:1.0.4:runtime
[INFO]    \- commons-discovery:commons-discovery:jar:0.2:runtime
[INFO]       \- (commons-logging:commons-logging:jar:1.0.3:runtime - omitted for conflict with 1.0.4)
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building web 0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ web ---
[INFO] com.github:web:war:0.1-SNAPSHOT
[INFO] +- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] \- com.github:core:jar:0.1-SNAPSHOT:compile
[INFO]    \- axis:axis:jar:1.4:compile
[INFO]       +- (commons-logging:commons-logging:jar:1.0.4:runtime - omitted for conflict with 1.1.1)
[INFO]       \- commons-discovery:commons-discovery:jar:0.2:runtime
[INFO]          \- (commons-logging:commons-logging:jar:1.0.3:runtime - omitted for conflict with 1.1.1)
[INFO] ------------------------------------------------------------------------

请注意,模块/项目核心依赖于commons-logging 1.0.4和commons-logging 1.0.3,但由于冲突而忽略了1.0.3并且解决了1.0.4。 这意味着如果你自己构建核心,你应该只获得commons-logging 1.0.4。

请注意,模块/项目 web 依赖于公共日志的冲突版本,但也会解析为1.1.1。

现在,如果您使用“mvn package”命令构建“整个项目”(modules-project),您应该看到modules-project / web / target / myweb / WEB-INF / lib包含所有已解析的依赖项,它包括ONLY commons-logging 1.1.1。

以下是代码

的问题

在上面的代码中,reactorProjects实例化了3个MavenProject:modules-project,core和web。

对于modules-project和web,它解析并返回commons-logging 1.1.1。但是,对于核心项目,它会解析并返回commons-logging 1.0.4。

我希望我的插件代码知道commons-logging 1.1.1是构建将产生的依赖,而不是commons-logging 1.0.4

有什么想法吗?

1 个答案:

答案 0 :(得分:5)

您几乎可以完成所有问题。以下插件将在控制台输出反应器中WAR项目的工件:

@Mojo(name = "foo", aggregator = true, requiresDependencyResolution = ResolutionScope.TEST)
public class MyMojo extends AbstractMojo {

    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    private MavenSession session;

    @Parameter(property = "reactorProjects", required = true, readonly = true)
    private List<MavenProject> reactorProjects;

    public void execute() throws MojoExecutionException, MojoFailureException {
        MavenProject packagedProject = getWarProject(reactorProjects);
        for (Artifact artifact : packagedProject.getArtifacts()) {
            getLog().info(artifact.toString());
        }
    }

    private MavenProject getWarProject(List<MavenProject> list) throws MojoExecutionException {
        for (MavenProject project : list) {
            if ("war".equals(project.getPackaging())) {
                return project;
            }
        }
        throw new MojoExecutionException("No WAR project found in the reactor");
    }

}

这样做是因为它使用注入的参数reactorProjects获取了反应堆中的所有项目。然后,通过比较它们的包装,它循环查找哪一个是"war"。找到后,getArtifacts()将返回该项目的所有已解析工件。

使其有效的神奇之处在于MOJO定义中的aggregator = true

  

标记此Mojo以多模块方式运行它,即使用列为模块的项目集合聚合构建。

添加到core POM

<plugin>
  <groupId>sample.plugin</groupId>
  <artifactId>test-maven-plugin</artifactId>
  <version>1.0.0</version>
  <executions>
    <execution>
      <id>test</id>
      <phase>compile</phase>
      <goals>
        <goal>foo</goal>
      </goals>
    </execution>
  </executions>
</plugin>

并使用您的示例项目运行,这将在控制台中打印:

[INFO] commons-logging:commons-logging:jar:1.1.1:compile
[INFO] com.github:core:jar:0.1-SNAPSHOT:compile
[INFO] axis:axis:jar:1.4:compile
[INFO] org.apache.axis:axis-jaxrpc:jar:1.4:compile
[INFO] org.apache.axis:axis-saaj:jar:1.4:compile
[INFO] axis:axis-wsdl4j:jar:1.5.1:runtime
[INFO] commons-discovery:commons-discovery:jar:0.2:runtime

这很好。有了它,我们可以继续前进,例如,比较正在构建的当前项目和打包项目的已解析工件。如果我们添加方法

private void printConflictingArtifacts(Set<Artifact> packaged, Set<Artifact> current) {
    for (Artifact a1 : current) {
        for (Artifact a2 : packaged) {
            if (a1.getGroupId().equals(a2.getGroupId()) && 
                    a1.getArtifactId().equals(a2.getArtifactId()) &&
                    !a1.getVersion().equals(a2.getVersion())) {
                getLog().warn("Conflicting dependency: " + a2 + " will be packaged and found " + a1);
            }
        }
    }
}

调用
printConflictingArtifacts(packagedProject.getArtifacts(), project.getArtifacts());

将当前工件与打包项目的工件进行比较,并且仅保留具有相同组/工件ID但版本不同的工件,我们可以使用您的示例进入控制台输出:

[WARNING] Conflicting dependency: commons-logging:commons-logging:jar:1.1.1:compile will be packaged and found commons-logging:commons-logging:jar:1.0.4:runtime

以上假设我们的最终包装模块是WAR模块。我们可以使它更通用,让用户指定哪个模块是目标模块(即将打包实际交付)。

为此,我们可以在MOJO中添加一个参数

@Parameter(property = "packagingArtifact")
private String packagingArtifact;

此参数的格式为groupId:artifactId,表示目标模块的坐标。然后,我们可以添加方法getPackagingProject,其目标是返回与这些坐标相关联的MavenProject

core内的插件配置为

<plugin>
    <groupId>sample.plugin</groupId>
    <artifactId>test-maven-plugin</artifactId>
    <version>1.0.0</version>
    <executions>
        <execution>
            <id>test</id>
            <phase>compile</phase>
            <goals>
                <goal>foo</goal>
            </goals>
            <configuration>
                <packagingArtifact>com.github:web</packagingArtifact>
            </configuration>
        </execution>
    </executions>
</plugin>

完整的MOJO将是:

@Mojo(name = "foo", aggregator = true, requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.COMPILE)
public class MyMojo extends AbstractMojo {

    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    private MavenSession session;

    @Parameter(property = "reactorProjects", required = true, readonly = true)
    private List<MavenProject> reactorProjects;

    @Parameter(property = "packagingArtifact")
    private String packagingArtifact;

    public void execute() throws MojoExecutionException, MojoFailureException {
        MavenProject packagedProject = getPackagingProject(reactorProjects, packagingArtifact);
        printConflictingArtifacts(packagedProject.getArtifacts(), project.getArtifacts());
    }

    private void printConflictingArtifacts(Set<Artifact> packaged, Set<Artifact> current) {
        for (Artifact a1 : current) {
            for (Artifact a2 : packaged) {
                if (a1.getGroupId().equals(a2.getGroupId()) && a1.getArtifactId().equals(a2.getArtifactId())
                        && !a1.getVersion().equals(a2.getVersion())) {
                    getLog().warn("Conflicting dependency: " + a2 + " will be packaged and found " + a1);
                }
            }
        }
    }

    private MavenProject getPackagingProject(List<MavenProject> list, String artifact) throws MojoExecutionException {
        if (artifact == null) {
            return getWarProject(list);
        }
        String[] tokens = artifact.split(":");
        for (MavenProject project : list) {
            if (project.getGroupId().equals(tokens[0]) && project.getArtifactId().equals(tokens[1])) {
                return project;
            }
        }
        throw new MojoExecutionException("No " + artifact + " project found in the reactor");
    }

    private MavenProject getWarProject(List<MavenProject> list) throws MojoExecutionException {
        for (MavenProject project : list) {
            if ("war".equals(project.getPackaging())) {
                return project;
            }
        }
        throw new MojoExecutionException("No WAR project found in the reactor");
    }

}

这实现了上述思想:当用户给出目标模块时,我们将其用作参考。如果此参数不存在,我们默认在反应堆中找到WAR。