你如何解决 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。
我正在编写一个 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。