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

How do you resolve dependencies across all modules in a maven plugin?

我正在编写一个 Maven 插件来获取已解析的依赖项。它适用于单个模块 project/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

从模块项目主文件夹,请运行

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

注意module/projectcore依赖commons-logging 1.0.4和commons-logging 1.0.3,但是1.0.3因为冲突被省略了和 1.0.4 已解决。 这意味着如果你要自己构建核心,你应该只获得 commons-logging 1.0.4.

请注意 module/project web 也依赖于 commons-logging 的冲突版本,但解析为 1.1.1.

现在,如果您要使用 "mvn package" 命令构建 "entire project"(模块项目),您应该会看到 modules-project/web/target/myweb/WEB-INF/lib 包含所有已解析的依赖项,并且它仅包含公共项-记录 1.1.1.

代码有问题

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

对于模块项目和网络,它解析和returns commons-logging 1.1.1。但是,对于 core 项目,它解析 returns commons-logging 1.0.4.

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

有什么想法吗?

您几乎已经掌握了您的问题所需的一切。以下插件将在控制台输出反应器中 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");
    }

}

它的作用是获取reactor中带有注入参数reactorProjects的所有项目。然后,它通过比较它们的包装来循环查找其中一个是 "war"。找到后,getArtifacts() 将 return 该项目的所有已解决工件。

让它发挥作用的魔法是 MOJO 定义中的 aggregator = true

Flags this Mojo to run it in a multi module way, i.e. aggregate the build with the set of projects listed as modules.

添加到 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>

and 运行 对于您的示例项目,这会在控制台中打印:

[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());

将当前工件与打包项目的工件进行比较,只保留那些具有相同 group/artifact 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,其目标是 return 与这些坐标相关联的 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。