diff --git a/src/it/mrm/repository/copy-transitive-root-1.0.pom b/src/it/mrm/repository/copy-transitive-root-1.0.pom
new file mode 100644
index 000000000..77f2a3f94
--- /dev/null
+++ b/src/it/mrm/repository/copy-transitive-root-1.0.pom
@@ -0,0 +1,37 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.dependency
+ copy-transitive-root
+ 1.0
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact
+ 1.0
+
+
+
diff --git a/src/it/mrm/repository/provided-root-1.0.pom b/src/it/mrm/repository/provided-root-1.0.pom
new file mode 100644
index 000000000..078c4b8a7
--- /dev/null
+++ b/src/it/mrm/repository/provided-root-1.0.pom
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ org.apache.maven.its.dependency
+ provided-root
+ 1.0
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+ 1.0
+
+
+
diff --git a/src/it/mrm/repository/scope-leaf-1.0.pom b/src/it/mrm/repository/scope-leaf-1.0.pom
new file mode 100644
index 000000000..2b78511fc
--- /dev/null
+++ b/src/it/mrm/repository/scope-leaf-1.0.pom
@@ -0,0 +1,27 @@
+
+
+
+ 4.0.0
+ org.apache.maven.its.dependency
+ scope-leaf
+ 1.0
+
diff --git a/src/it/mrm/repository/scope-leaf-2.0.pom b/src/it/mrm/repository/scope-leaf-2.0.pom
new file mode 100644
index 000000000..6f74667e0
--- /dev/null
+++ b/src/it/mrm/repository/scope-leaf-2.0.pom
@@ -0,0 +1,27 @@
+
+
+
+ 4.0.0
+ org.apache.maven.its.dependency
+ scope-leaf
+ 2.0
+
diff --git a/src/it/mrm/repository/scope-root-1.0.pom b/src/it/mrm/repository/scope-root-1.0.pom
new file mode 100644
index 000000000..427384138
--- /dev/null
+++ b/src/it/mrm/repository/scope-root-1.0.pom
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ org.apache.maven.its.dependency
+ scope-root
+ 1.0
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+ 1.0
+
+
+
diff --git a/src/it/mrm/repository/scope-test-root-1.0.pom b/src/it/mrm/repository/scope-test-root-1.0.pom
new file mode 100644
index 000000000..8601f6494
--- /dev/null
+++ b/src/it/mrm/repository/scope-test-root-1.0.pom
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+ org.apache.maven.its.dependency
+ scope-test-root
+ 1.0
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+ 2.0
+
+
+
diff --git a/src/it/projects/copy-relocation-without-version/invoker.properties b/src/it/projects/copy-relocation-without-version/invoker.properties
new file mode 100644
index 000000000..101be4e95
--- /dev/null
+++ b/src/it/projects/copy-relocation-without-version/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.goals = clean process-sources
diff --git a/src/it/projects/copy-relocation-without-version/pom.xml b/src/it/projects/copy-relocation-without-version/pom.xml
new file mode 100644
index 000000000..f866f628e
--- /dev/null
+++ b/src/it/projects/copy-relocation-without-version/pom.xml
@@ -0,0 +1,71 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.dependency
+ copy-relocation-without-version
+ 1.0-SNAPSHOT
+
+ Test
+
+ Test dependency:copy with relocation and a version resolved from the project dependencies
+
+
+
+ UTF-8
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact-relocation
+ 1.0
+
+
+
+
+
+
+ maven-dependency-plugin
+ @project.version@
+
+
+ test
+
+ copy
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact-relocation
+
+
+
+
+
+
+
+
+
diff --git a/src/it/projects/copy-relocation-without-version/verify.groovy b/src/it/projects/copy-relocation-without-version/verify.groovy
new file mode 100644
index 000000000..54a60549d
--- /dev/null
+++ b/src/it/projects/copy-relocation-without-version/verify.groovy
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+assert new File(basedir, 'target/dependency/ResourceArtifact-1.0.jar').isFile()
diff --git a/src/it/projects/mdep-726-copy-transitive-version/invoker.properties b/src/it/projects/mdep-726-copy-transitive-version/invoker.properties
new file mode 100644
index 000000000..101be4e95
--- /dev/null
+++ b/src/it/projects/mdep-726-copy-transitive-version/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.goals = clean process-sources
diff --git a/src/it/projects/mdep-726-copy-transitive-version/pom.xml b/src/it/projects/mdep-726-copy-transitive-version/pom.xml
new file mode 100644
index 000000000..fc65c92d8
--- /dev/null
+++ b/src/it/projects/mdep-726-copy-transitive-version/pom.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.plugins.dependency.its
+ mdep-726-copy-transitive-version
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.its.dependency
+ copy-transitive-root
+ 1.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ @project.version@
+
+
+ copy-transitive-artifact
+ process-sources
+
+ copy
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact
+ ${project.build.directory}/copied
+
+
+
+
+
+ unpack-transitive-artifact
+ process-sources
+
+ unpack
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact
+ ${project.build.directory}/unpacked
+
+
+
+
+
+
+
+
+
diff --git a/src/it/projects/mdep-726-copy-transitive-version/verify.groovy b/src/it/projects/mdep-726-copy-transitive-version/verify.groovy
new file mode 100644
index 000000000..ded72a27c
--- /dev/null
+++ b/src/it/projects/mdep-726-copy-transitive-version/verify.groovy
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+assert new File(basedir, 'target/copied/ResourceArtifact-1.0.jar').isFile()
+assert new File(basedir, 'target/unpacked/resource1.txt').isFile()
diff --git a/src/it/projects/mdep-726-managed-version-before-graph/invoker.properties b/src/it/projects/mdep-726-managed-version-before-graph/invoker.properties
new file mode 100644
index 000000000..101be4e95
--- /dev/null
+++ b/src/it/projects/mdep-726-managed-version-before-graph/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.goals = clean process-sources
diff --git a/src/it/projects/mdep-726-managed-version-before-graph/pom.xml b/src/it/projects/mdep-726-managed-version-before-graph/pom.xml
new file mode 100644
index 000000000..eb34b1bf8
--- /dev/null
+++ b/src/it/projects/mdep-726-managed-version-before-graph/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ 4.0.0
+ org.apache.maven.plugins.dependency.its
+ mdep-726-managed-version-before-graph
+ 1.0-SNAPSHOT
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact
+ 1.0
+
+
+
+
+
+
+ review.missing
+ unrelated
+ [1.0,2.0)
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ @project.version@
+
+
+ copy-managed-artifact
+ process-sources
+
+ copy
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact
+
+
+
+
+
+
+
+
+
diff --git a/src/it/projects/mdep-726-managed-version-before-graph/verify.groovy b/src/it/projects/mdep-726-managed-version-before-graph/verify.groovy
new file mode 100644
index 000000000..54a60549d
--- /dev/null
+++ b/src/it/projects/mdep-726-managed-version-before-graph/verify.groovy
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+assert new File(basedir, 'target/dependency/ResourceArtifact-1.0.jar').isFile()
diff --git a/src/it/projects/mdep-726-scope-ambiguity/invoker.properties b/src/it/projects/mdep-726-scope-ambiguity/invoker.properties
new file mode 100644
index 000000000..f8608a3d2
--- /dev/null
+++ b/src/it/projects/mdep-726-scope-ambiguity/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+invoker.goals = clean process-sources
+invoker.buildResult = failure
diff --git a/src/it/projects/mdep-726-scope-ambiguity/pom.xml b/src/it/projects/mdep-726-scope-ambiguity/pom.xml
new file mode 100644
index 000000000..af6835d36
--- /dev/null
+++ b/src/it/projects/mdep-726-scope-ambiguity/pom.xml
@@ -0,0 +1,67 @@
+
+
+
+ 4.0.0
+ org.apache.maven.plugins.dependency.its
+ mdep-726-scope-ambiguity
+ 1.0-SNAPSHOT
+
+
+ org.apache.maven.its.dependency
+ scope-test-root
+ 1.0
+ test
+
+
+ org.apache.maven.its.dependency
+ provided-root
+ 1.0
+ provided
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ @project.version@
+
+
+ ambiguous-version
+ process-sources
+
+ copy
+
+
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+
+
+
+
+
+
+
+
+
diff --git a/src/it/projects/mdep-726-scope-ambiguity/verify.groovy b/src/it/projects/mdep-726-scope-ambiguity/verify.groovy
new file mode 100644
index 000000000..0d81a8c06
--- /dev/null
+++ b/src/it/projects/mdep-726-scope-ambiguity/verify.groovy
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+def log = new File(basedir, 'build.log').text
+assert log.contains('Dependency graphs select different versions of org.apache.maven.its.dependency:scope-leaf')
+assert log.contains('compile=1.0')
+assert log.contains('test=2.0')
diff --git a/src/it/projects/mdep-726-scope-selection/invoker.properties b/src/it/projects/mdep-726-scope-selection/invoker.properties
new file mode 100644
index 000000000..1df273350
--- /dev/null
+++ b/src/it/projects/mdep-726-scope-selection/invoker.properties
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+invoker.goals = clean process-sources
diff --git a/src/it/projects/mdep-726-scope-selection/pom.xml b/src/it/projects/mdep-726-scope-selection/pom.xml
new file mode 100644
index 000000000..f67c570d2
--- /dev/null
+++ b/src/it/projects/mdep-726-scope-selection/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+ 4.0.0
+ org.apache.maven.plugins.dependency.its
+ mdep-726-scope-selection
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.its.dependency
+ scope-test-root
+ 1.0
+ test
+
+
+ org.apache.maven.its.dependency
+ scope-root
+ 1.0
+
+
+ org.apache.maven.its.dependency
+ provided-root
+ 1.0
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ @project.version@
+
+
+ copy-scope-selected-versions
+ process-sources
+
+ copy
+
+
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+ compile
+ ${project.build.directory}/compile
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+ runtime
+ ${project.build.directory}/runtime
+
+
+ org.apache.maven.its.dependency
+ scope-leaf
+ test
+ ${project.build.directory}/test
+
+
+
+
+
+
+
+
+
diff --git a/src/it/projects/mdep-726-scope-selection/verify.groovy b/src/it/projects/mdep-726-scope-selection/verify.groovy
new file mode 100644
index 000000000..ea1342ea3
--- /dev/null
+++ b/src/it/projects/mdep-726-scope-selection/verify.groovy
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+assert new File(basedir, 'target/compile/scope-leaf-1.0.jar').isFile()
+assert new File(basedir, 'target/runtime/scope-leaf-1.0.jar').isFile()
+assert new File(basedir, 'target/test/scope-leaf-2.0.jar').isFile()
diff --git a/src/it/projects/unpack-relocation-without-version/invoker.properties b/src/it/projects/unpack-relocation-without-version/invoker.properties
new file mode 100644
index 000000000..101be4e95
--- /dev/null
+++ b/src/it/projects/unpack-relocation-without-version/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.goals = clean process-sources
diff --git a/src/it/projects/unpack-relocation-without-version/pom.xml b/src/it/projects/unpack-relocation-without-version/pom.xml
new file mode 100644
index 000000000..9ab345018
--- /dev/null
+++ b/src/it/projects/unpack-relocation-without-version/pom.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.dependency
+ unpack-relocation-without-version
+ 1.0-SNAPSHOT
+
+ Test
+
+ Test dependency:unpack with relocation and a version resolved from the project dependencies
+
+
+
+ UTF-8
+
+
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact-relocation
+ 1.0
+
+
+
+
+
+
+
+ maven-dependency-plugin
+ @project.version@
+
+
+ unpack
+
+ unpack
+
+
+ ${project.build.directory}/ResourceArtifact
+
+
+ org.apache.maven.its.dependency
+ ResourceArtifact-relocation
+
+
+
+
+
+
+
+
+
diff --git a/src/it/projects/unpack-relocation-without-version/verify.groovy b/src/it/projects/unpack-relocation-without-version/verify.groovy
new file mode 100644
index 000000000..ef4b5dcb0
--- /dev/null
+++ b/src/it/projects/unpack-relocation-without-version/verify.groovy
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+assert new File(basedir, 'target/ResourceArtifact/META-INF/MANIFEST.MF').isFile()
+assert new File(basedir, 'target/ResourceArtifact/resource1.txt').isFile()
+assert new File(basedir, 'target/ResourceArtifact/resource2.txt').isFile()
diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/AbstractFromConfigurationMojo.java b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/AbstractFromConfigurationMojo.java
index f5a42f813..53ea31431 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/AbstractFromConfigurationMojo.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/AbstractFromConfigurationMojo.java
@@ -21,7 +21,6 @@
import java.io.File;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.RepositoryUtils;
@@ -29,7 +28,6 @@
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.execution.MavenSession;
-import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
@@ -39,13 +37,10 @@
import org.apache.maven.plugins.dependency.utils.filters.ArtifactItemFilter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
-import org.eclipse.aether.DefaultRepositoryCache;
-import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyCollectorBuilder;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
-import org.eclipse.aether.repository.LocalRepository;
-import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.sonatype.plexus.build.incremental.BuildContext;
@@ -100,8 +95,9 @@ public abstract class AbstractFromConfigurationMojo extends AbstractDependencyMo
private boolean overWriteIfNewer;
/**
- * Collection of ArtifactItems to work on. (ArtifactItem contains groupId, artifactId, version, type, classifier,
- * outputDirectory, destFileName, overWrite and encoding.) See Usage for details.
+ * Collection of ArtifactItems to work on. (ArtifactItem contains groupId, artifactId, version, dependencyScope,
+ * type, classifier, outputDirectory, destFileName, overWrite and encoding.) See Usage
+ * for details.
*
* @since 1.0
*/
@@ -119,10 +115,10 @@ public abstract class AbstractFromConfigurationMojo extends AbstractDependencyMo
private final ArtifactHandlerManager artifactHandlerManager;
- private final RepositorySystem repositorySystem;
-
private final ResolverUtil resolverUtil;
+ private final DependencyVersionResolver dependencyVersionResolver;
+
protected AbstractFromConfigurationMojo(
MavenSession session,
BuildContext buildContext,
@@ -132,8 +128,9 @@ protected AbstractFromConfigurationMojo(
ResolverUtil resolverUtil) {
super(session, buildContext, project);
this.artifactHandlerManager = artifactHandlerManager;
- this.repositorySystem = repositorySystem;
this.resolverUtil = resolverUtil;
+ this.dependencyVersionResolver = new DependencyVersionResolver(
+ session, project, new DefaultDependencyCollectorBuilder(repositorySystem));
}
abstract ArtifactItemFilter getMarkedArtifactFilter(ArtifactItem item);
@@ -207,22 +204,15 @@ private boolean checkIfProcessingNeeded(ArtifactItem item) throws ArtifactFilter
}
private RepositorySystemSession createSystemSessionForLocalRepo() {
- RepositorySystemSession repositorySystemSession = session.getRepositorySession();
+ RepositorySystemSession repositorySystemSession =
+ resolverUtil.repositorySystemSession(localRepositoryDirectory);
if (localRepositoryDirectory != null) {
- // "clone" repository session and replace localRepository
- DefaultRepositorySystemSession newSession =
- new DefaultRepositorySystemSession(session.getRepositorySession());
- // Clear cache, since we're using a new local repository
- newSession.setCache(new DefaultRepositoryCache());
- LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(
- newSession, new LocalRepository(localRepositoryDirectory));
-
- newSession.setLocalRepositoryManager(localRepositoryManager);
- repositorySystemSession = newSession;
getLog().debug("localRepoPath: "
- + localRepositoryManager.getRepository().getBasedir());
+ + repositorySystemSession
+ .getLocalRepositoryManager()
+ .getRepository()
+ .getBasedir());
}
-
return repositorySystemSession;
}
@@ -273,43 +263,7 @@ protected Artifact getArtifact(ArtifactItem artifactItem) throws MojoExecutionEx
* @throws MojoExecutionException
*/
private void fillMissingArtifactVersion(ArtifactItem artifact) throws MojoExecutionException {
- MavenProject project = getProject();
- List deps = project.getDependencies();
- List depMngt = project.getDependencyManagement() == null
- ? Collections.emptyList()
- : project.getDependencyManagement().getDependencies();
-
- if (!findDependencyVersion(artifact, deps, false)
- && (project.getDependencyManagement() == null || !findDependencyVersion(artifact, depMngt, false))
- && !findDependencyVersion(artifact, deps, true)
- && (project.getDependencyManagement() == null || !findDependencyVersion(artifact, depMngt, true))) {
- throw new MojoExecutionException("Unable to find artifact version of " + artifact.getGroupId() + ":"
- + artifact.getArtifactId() + " in either dependency list or in project's dependency management.");
- }
- }
-
- /**
- * Tries to find missing version from a list of dependencies. If found, the artifact is updated with the correct
- * version.
- *
- * @param artifact representing configured file
- * @param dependencies list of dependencies to search
- * @param looseMatch only look at artifactId and groupId
- * @return the found dependency
- */
- private boolean findDependencyVersion(ArtifactItem artifact, List dependencies, boolean looseMatch) {
- for (Dependency dependency : dependencies) {
- if (Objects.equals(dependency.getArtifactId(), artifact.getArtifactId())
- && Objects.equals(dependency.getGroupId(), artifact.getGroupId())
- && (looseMatch || Objects.equals(dependency.getClassifier(), artifact.getClassifier()))
- && (looseMatch || Objects.equals(dependency.getType(), artifact.getType()))) {
- artifact.setVersion(dependency.getVersion());
-
- return true;
- }
- }
-
- return false;
+ artifact.setVersion(dependencyVersionResolver.resolveVersion(artifact));
}
/**
diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java
index b697533ce..3fc14e761 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ArtifactItem.java
@@ -53,6 +53,16 @@ public class ArtifactItem implements DependableCoordinate {
@Parameter
private String version = null;
+ /**
+ * Classpath scope from which to infer a missing version when the artifact is not found in direct dependencies or
+ * dependency management. Supported values are {@code compile}, {@code runtime}, and {@code test}. When omitted,
+ * all three dependency graphs are considered and must agree on the selected version.
+ *
+ * @since 3.11.1
+ */
+ @Parameter
+ private String dependencyScope;
+
/**
* Type of artifact (War, Jar, etc.)
*/
@@ -199,6 +209,22 @@ public void setVersion(String version) {
this.version = filterEmptyString(version);
}
+ /**
+ * @return classpath scope used to infer a missing version
+ * @since 3.11.1
+ */
+ public String getDependencyScope() {
+ return dependencyScope;
+ }
+
+ /**
+ * @param dependencyScope classpath scope used to infer a missing version
+ * @since 3.11.1
+ */
+ public void setDependencyScope(String dependencyScope) {
+ this.dependencyScope = filterEmptyString(dependencyScope);
+ }
+
/**
* @return the base version
*/
diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathDependencySelector.java b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathDependencySelector.java
new file mode 100644
index 000000000..2592ecbc3
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathDependencySelector.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.fromConfiguration;
+
+import java.util.Objects;
+
+import org.eclipse.aether.collection.DependencyCollectionContext;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.util.artifact.JavaScopes;
+
+/**
+ * Selects dependencies by their effective Maven classpath scope while the graph is being collected. Filtering after
+ * conflict resolution is too late because dependencies excluded from one classpath can otherwise affect mediation in
+ * another classpath.
+ */
+final class ClasspathDependencySelector implements DependencySelector {
+ private final ClasspathScope classpathScope;
+
+ private final String parentScope;
+
+ ClasspathDependencySelector(ClasspathScope classpathScope) {
+ this(classpathScope, null);
+ }
+
+ private ClasspathDependencySelector(ClasspathScope classpathScope, String parentScope) {
+ this.classpathScope = Objects.requireNonNull(classpathScope);
+ this.parentScope = parentScope;
+ }
+
+ @Override
+ public boolean selectDependency(Dependency dependency) {
+ return classpathScope.includes(deriveScope(parentScope, dependency.getScope()));
+ }
+
+ @Override
+ public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
+ Dependency dependency = context.getDependency();
+ if (dependency == null) {
+ return this;
+ }
+ return new ClasspathDependencySelector(classpathScope, deriveScope(parentScope, dependency.getScope()));
+ }
+
+ // Keep this in sync with Resolver's legacy JavaScopeDeriver. Its callable context is not binary compatible
+ // between Resolver 1.x and 2.x, while this plugin must run with both Maven 3 and Maven 4.
+ static String deriveScope(String parentScope, String childScope) {
+ String child = childScope == null || childScope.isEmpty() ? JavaScopes.COMPILE : childScope;
+ if (JavaScopes.SYSTEM.equals(child) || JavaScopes.TEST.equals(child)) {
+ return child;
+ } else if (parentScope == null || parentScope.isEmpty() || JavaScopes.COMPILE.equals(parentScope)) {
+ return child;
+ } else if (JavaScopes.TEST.equals(parentScope) || JavaScopes.RUNTIME.equals(parentScope)) {
+ return parentScope;
+ } else if (JavaScopes.SYSTEM.equals(parentScope) || JavaScopes.PROVIDED.equals(parentScope)) {
+ return JavaScopes.PROVIDED;
+ } else {
+ return JavaScopes.RUNTIME;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathScope.java b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathScope.java
new file mode 100644
index 000000000..1bb35f388
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathScope.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.fromConfiguration;
+
+import java.util.Locale;
+
+import org.eclipse.aether.util.artifact.JavaScopes;
+
+enum ClasspathScope {
+ COMPILE,
+ RUNTIME,
+ TEST;
+
+ static ClasspathScope fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Unknown dependency scope '" + value + "'. Expected one of: compile, runtime, test.", e);
+ }
+ }
+
+ boolean includes(String scope) {
+ String effectiveScope = scope == null || scope.isEmpty() ? JavaScopes.COMPILE : scope;
+ switch (this) {
+ case COMPILE:
+ return JavaScopes.COMPILE.equals(effectiveScope)
+ || JavaScopes.PROVIDED.equals(effectiveScope)
+ || JavaScopes.SYSTEM.equals(effectiveScope);
+ case RUNTIME:
+ return JavaScopes.COMPILE.equals(effectiveScope) || JavaScopes.RUNTIME.equals(effectiveScope);
+ case TEST:
+ return true;
+ default:
+ throw new IllegalStateException("Unhandled classpath scope " + this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/DependencyVersionResolver.java b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/DependencyVersionResolver.java
new file mode 100644
index 000000000..47940cdd6
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/fromConfiguration/DependencyVersionResolver.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.fromConfiguration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.DefaultProjectBuildingRequest;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
+import org.apache.maven.shared.dependency.graph.DependencyCollectorRequest;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
+import org.eclipse.aether.util.graph.selector.AndDependencySelector;
+import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
+import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
+import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
+import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
+import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
+import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
+
+final class DependencyVersionResolver {
+ private final MavenSession session;
+
+ private final MavenProject project;
+
+ private final DependencyCollectorBuilder dependencyCollectorBuilder;
+
+ private final Map>> artifactIndexByScope =
+ new EnumMap<>(ClasspathScope.class);
+
+ DependencyVersionResolver(
+ MavenSession session, MavenProject project, DependencyCollectorBuilder dependencyCollectorBuilder) {
+ this.session = session;
+ this.project = project;
+ this.dependencyCollectorBuilder = dependencyCollectorBuilder;
+ }
+
+ String resolveVersion(ArtifactItem artifactItem) throws MojoExecutionException {
+ List directDependencies = project.getDependencies();
+ List managedDependencies = project.getDependencyManagement() == null
+ ? Collections.emptyList()
+ : project.getDependencyManagement().getDependencies();
+
+ String modelVersion = findModelVersion(artifactItem, directDependencies, false);
+ if (modelVersion == null) {
+ modelVersion = findModelVersion(artifactItem, managedDependencies, false);
+ }
+ if (modelVersion == null) {
+ modelVersion = findModelVersion(artifactItem, directDependencies, true);
+ }
+ if (modelVersion == null) {
+ modelVersion = findModelVersion(artifactItem, managedDependencies, true);
+ }
+ if (modelVersion != null) {
+ return modelVersion;
+ }
+
+ List requestedScopes = requestedScopes(artifactItem);
+ Map> versions = findGraphVersions(artifactItem, requestedScopes, false);
+ if (versions.isEmpty()) {
+ versions = findGraphVersions(artifactItem, requestedScopes, true);
+ }
+ if (!versions.isEmpty()) {
+ return uniqueVersion(artifactItem, versions);
+ }
+
+ String scopeDescription = artifactItem.getDependencyScope() == null
+ ? "compile, runtime, or test dependency graph"
+ : artifactItem.getDependencyScope() + " dependency graph";
+ throw new MojoExecutionException("Unable to find artifact version of " + artifactItem.getGroupId() + ":"
+ + artifactItem.getArtifactId() + " in direct dependencies, dependency management, or the project's "
+ + scopeDescription + ".");
+ }
+
+ private List requestedScopes(ArtifactItem artifactItem) throws MojoExecutionException {
+ String dependencyScope = artifactItem.getDependencyScope();
+ if (dependencyScope == null || dependencyScope.isEmpty()) {
+ List scopes = new ArrayList<>(3);
+ Collections.addAll(scopes, ClasspathScope.COMPILE, ClasspathScope.RUNTIME, ClasspathScope.TEST);
+ return scopes;
+ }
+ try {
+ return Collections.singletonList(ClasspathScope.fromString(dependencyScope));
+ } catch (IllegalArgumentException e) {
+ throw new MojoExecutionException(e.getMessage(), e);
+ }
+ }
+
+ private Map> findGraphVersions(
+ ArtifactItem artifactItem, List scopes, boolean looseMatch) throws MojoExecutionException {
+ Map> result = new LinkedHashMap<>();
+ GroupArtifactKey key = GroupArtifactKey.from(artifactItem);
+ for (ClasspathScope scope : scopes) {
+ List artifacts = artifactIndex(scope).get(key);
+ Set versions =
+ artifacts == null ? Collections.emptySet() : matchingVersions(artifactItem, artifacts, looseMatch);
+ if (!versions.isEmpty()) {
+ result.put(scope, versions);
+ }
+ }
+ return result;
+ }
+
+ private String uniqueVersion(ArtifactItem artifactItem, Map> versions)
+ throws MojoExecutionException {
+ Set uniqueVersions = versions.values().stream()
+ .flatMap(Collection::stream)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ if (uniqueVersions.size() == 1) {
+ return uniqueVersions.iterator().next();
+ }
+
+ String selections = versions.entrySet().stream()
+ .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
+ .collect(Collectors.joining(", "));
+ throw new MojoExecutionException("Dependency graphs select different versions of " + artifactItem.getGroupId()
+ + ":" + artifactItem.getArtifactId() + " (" + selections
+ + "). Set dependencyScope to compile, runtime, or test, or specify version explicitly.");
+ }
+
+ private Map> artifactIndex(ClasspathScope scope) throws MojoExecutionException {
+ Map> cached = artifactIndexByScope.get(scope);
+ if (cached != null) {
+ return cached;
+ }
+
+ ProjectBuildingRequest buildingRequest;
+ if (session.getProjectBuildingRequest() == null) {
+ buildingRequest = new DefaultProjectBuildingRequest();
+ buildingRequest.setRepositorySession(session.getRepositorySession());
+ } else {
+ buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
+ }
+ buildingRequest.setProject(project);
+
+ DependencyCollectorRequest request = new DependencyCollectorRequest(buildingRequest);
+ request.dependencySelector(dependencySelector(scope));
+ request.dependencyGraphTransformer(new ConflictResolver(
+ new NearestVersionSelector(),
+ new JavaScopeSelector(),
+ new SimpleOptionalitySelector(),
+ new JavaScopeDeriver()));
+ request.addConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, false);
+ request.addConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, false);
+
+ try {
+ DependencyNode root = dependencyCollectorBuilder.collectDependencyGraph(request);
+ List collected = new ArrayList<>();
+ collectArtifacts(root, collected, true);
+ Map> index = indexArtifacts(collected);
+ artifactIndexByScope.put(scope, index);
+ return index;
+ } catch (DependencyCollectorBuilderException e) {
+ throw new MojoExecutionException("Unable to collect the project's " + scope + " dependency graph.", e);
+ }
+ }
+
+ private DependencySelector dependencySelector(ClasspathScope scope) {
+ return new AndDependencySelector(
+ new ClasspathDependencySelector(scope),
+ new ScopeDependencySelector(JavaScopes.TEST, JavaScopes.PROVIDED),
+ new OptionalDependencySelector(),
+ new ExclusionDependencySelector());
+ }
+
+ private void collectArtifacts(DependencyNode node, List artifacts, boolean root) {
+ if (!root && node.getArtifact() != null) {
+ artifacts.add(node.getArtifact());
+ }
+ for (DependencyNode child : node.getChildren()) {
+ collectArtifacts(child, artifacts, false);
+ }
+ }
+
+ static Map> indexArtifacts(Collection artifacts) {
+ Map> index = new LinkedHashMap<>();
+ for (Artifact artifact : artifacts) {
+ index.computeIfAbsent(GroupArtifactKey.from(artifact), key -> new ArrayList<>())
+ .add(artifact);
+ }
+ for (Map.Entry> entry : index.entrySet()) {
+ entry.setValue(Collections.unmodifiableList(entry.getValue()));
+ }
+ return Collections.unmodifiableMap(index);
+ }
+
+ static Set matchingVersions(ArtifactItem artifactItem, List artifacts, boolean looseMatch) {
+ return artifacts.stream()
+ .filter(artifact -> looseMatch
+ || (Objects.equals(artifact.getClassifier(), artifactItem.getClassifier())
+ && Objects.equals(artifact.getType(), artifactItem.getType())))
+ .map(Artifact::getVersion)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ }
+
+ private String findModelVersion(ArtifactItem artifactItem, List dependencies, boolean looseMatch) {
+ for (Dependency dependency : dependencies) {
+ if (Objects.equals(dependency.getArtifactId(), artifactItem.getArtifactId())
+ && Objects.equals(dependency.getGroupId(), artifactItem.getGroupId())
+ && (looseMatch || Objects.equals(dependency.getClassifier(), artifactItem.getClassifier()))
+ && (looseMatch || Objects.equals(dependency.getType(), artifactItem.getType()))) {
+ return dependency.getVersion();
+ }
+ }
+ return null;
+ }
+
+ static final class GroupArtifactKey {
+ private final String groupId;
+
+ private final String artifactId;
+
+ private GroupArtifactKey(String groupId, String artifactId) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ }
+
+ static GroupArtifactKey from(ArtifactItem artifactItem) {
+ return new GroupArtifactKey(artifactItem.getGroupId(), artifactItem.getArtifactId());
+ }
+
+ static GroupArtifactKey from(Artifact artifact) {
+ return new GroupArtifactKey(artifact.getGroupId(), artifact.getArtifactId());
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (!(object instanceof GroupArtifactKey)) {
+ return false;
+ }
+ GroupArtifactKey that = (GroupArtifactKey) object;
+ return Objects.equals(groupId, that.groupId) && Objects.equals(artifactId, that.artifactId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(groupId, artifactId);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java
index a2eb7b889..78f6ee27f 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/utils/ResolverUtil.java
@@ -23,6 +23,7 @@
import javax.inject.Provider;
import javax.inject.Singleton;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -42,6 +43,8 @@
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.Reporting;
import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.DefaultRepositoryCache;
+import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
@@ -52,6 +55,7 @@
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
@@ -82,6 +86,25 @@ public ResolverUtil(RepositorySystem repositorySystem, Provider ma
this.mavenSessionProvider = mavenSessionProvider;
}
+ /**
+ * Returns the current repository session, optionally using an alternate local repository.
+ *
+ * @param localRepositoryDirectory alternate local repository directory, or {@code null}
+ * @return repository system session
+ */
+ public RepositorySystemSession repositorySystemSession(File localRepositoryDirectory) {
+ RepositorySystemSession repositorySystemSession =
+ mavenSessionProvider.get().getRepositorySession();
+ if (localRepositoryDirectory != null) {
+ DefaultRepositorySystemSession newSession = new DefaultRepositorySystemSession(repositorySystemSession);
+ newSession.setCache(new DefaultRepositoryCache());
+ newSession.setLocalRepositoryManager(repositorySystem.newLocalRepositoryManager(
+ newSession, new LocalRepository(localRepositoryDirectory)));
+ repositorySystemSession = newSession;
+ }
+ return repositorySystemSession;
+ }
+
/**
* Collects the transitive dependencies.
*
diff --git a/src/site/markdown/examples/copying-artifacts.md.vm b/src/site/markdown/examples/copying-artifacts.md.vm
index 78c564414..d8ecbebcd 100644
--- a/src/site/markdown/examples/copying-artifacts.md.vm
+++ b/src/site/markdown/examples/copying-artifacts.md.vm
@@ -81,7 +81,12 @@ Artifacts are resolved from the following sources in order:
If the artifact cannot be resolved from the above sources then the build will fail.
-If the artifact is also listed as a dependency, the `version` of the `artifactItem` will default to the version from the `dependencies` or `dependencyManagement`, e.g.
+If the artifact is also a direct dependency or is listed in `dependencyManagement`, the `version` of the
+`artifactItem` may be omitted. The plugin checks those model entries first, preferring an exact match on type and
+classifier before matching only group ID and artifact ID. Otherwise, a version may be inferred from a transitive
+dependency. For that fallback, the plugin compares the versions selected by the project's compile, runtime, and test
+dependency graphs. If the graphs select different versions, use `dependencyScope` with `compile`, `runtime`, or `test`
+to choose the intended graph, e.g.
```xml
diff --git a/src/site/markdown/examples/unpacking-artifacts.md.vm b/src/site/markdown/examples/unpacking-artifacts.md.vm
index db823086b..d8453b9ce 100644
--- a/src/site/markdown/examples/unpacking-artifacts.md.vm
+++ b/src/site/markdown/examples/unpacking-artifacts.md.vm
@@ -76,6 +76,12 @@ This is pretty similar to the [Copying Specific Artifacts](./copying-artifacts.h
And after invoking `mvn package`, the artifacts are unpacked. Because checking the existence of an unpacked archive is difficult to do reliably, marker files are used instead. The location of the marker files is controlled by the [markersDirectory](../unpack-dependencies-mojo.html#markersDirectory) parameter.
+If an artifact is also a direct project dependency or is listed in `dependencyManagement`, its `version` may be
+omitted. The plugin checks those model entries first, preferring an exact type and classifier match before matching only
+group ID and artifact ID. Otherwise, a version may be inferred from a transitive dependency. For that fallback, the
+plugin compares the versions selected by the compile, runtime, and test dependency graphs. If those graphs select
+different versions, set `dependencyScope` on the `artifactItem` to `compile`, `runtime`, or `test`.
+
# Unpacking from the command line:
If you intend to configure this mojo for execution on the command line using:
diff --git a/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathDependencySelectorTest.java b/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathDependencySelectorTest.java
new file mode 100644
index 000000000..bcf92a59e
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/ClasspathDependencySelectorTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.fromConfiguration;
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ClasspathDependencySelectorTest {
+ @Test
+ void providedDependenciesAreOnCompileAndTestClasspaths() {
+ Dependency provided = dependency(JavaScopes.PROVIDED);
+
+ assertTrue(new ClasspathDependencySelector(ClasspathScope.COMPILE).selectDependency(provided));
+ assertFalse(new ClasspathDependencySelector(ClasspathScope.RUNTIME).selectDependency(provided));
+ assertTrue(new ClasspathDependencySelector(ClasspathScope.TEST).selectDependency(provided));
+ }
+
+ @Test
+ void derivesTransitiveScopesLikeResolver() {
+ assertEquals(
+ JavaScopes.PROVIDED, ClasspathDependencySelector.deriveScope(JavaScopes.PROVIDED, JavaScopes.COMPILE));
+ assertEquals(
+ JavaScopes.RUNTIME, ClasspathDependencySelector.deriveScope(JavaScopes.RUNTIME, JavaScopes.COMPILE));
+ assertEquals(JavaScopes.TEST, ClasspathDependencySelector.deriveScope(JavaScopes.COMPILE, JavaScopes.TEST));
+ assertEquals(
+ JavaScopes.RUNTIME, ClasspathDependencySelector.deriveScope(JavaScopes.COMPILE, JavaScopes.RUNTIME));
+ }
+
+ private Dependency dependency(String scope) {
+ return new Dependency(new DefaultArtifact("groupId:artifactId:jar:1.0"), scope);
+ }
+}
diff --git a/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/DependencyVersionResolverTest.java b/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/DependencyVersionResolverTest.java
new file mode 100644
index 000000000..097bd3456
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/DependencyVersionResolverTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.fromConfiguration;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.DefaultArtifactHandler;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DependencyVersionResolverTest {
+ @Test
+ void indexesAllVariantsUnderTheirGroupAndArtifactId() {
+ Artifact main = artifact("group", "artifact", "1.0", "jar", null);
+ Artifact sources = artifact("group", "artifact", "2.0", "jar", "sources");
+ Artifact other = artifact("group", "other", "3.0", "jar", null);
+
+ Map> index =
+ DependencyVersionResolver.indexArtifacts(Arrays.asList(main, sources, other));
+
+ assertEquals(Arrays.asList(main, sources), index.get(DependencyVersionResolver.GroupArtifactKey.from(main)));
+ assertEquals(Arrays.asList(other), index.get(DependencyVersionResolver.GroupArtifactKey.from(other)));
+ assertThrows(UnsupportedOperationException.class, () -> index.clear());
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> index.get(DependencyVersionResolver.GroupArtifactKey.from(main))
+ .clear());
+ }
+
+ @Test
+ void matchesExactVariantBeforeUsingAllGaVariants() {
+ Artifact main = artifact("group", "artifact", "1.0", "jar", null);
+ Artifact sources = artifact("group", "artifact", "2.0", "jar", "sources");
+ List variants = Arrays.asList(main, sources);
+
+ ArtifactItem item = new ArtifactItem();
+ item.setGroupId("group");
+ item.setArtifactId("artifact");
+ item.setType("jar");
+ item.setClassifier("sources");
+
+ assertIterableEquals(Arrays.asList("2.0"), DependencyVersionResolver.matchingVersions(item, variants, false));
+ assertIterableEquals(
+ Arrays.asList("1.0", "2.0"), DependencyVersionResolver.matchingVersions(item, variants, true));
+ }
+
+ private Artifact artifact(String groupId, String artifactId, String version, String type, String classifier) {
+ return new DefaultArtifact(
+ groupId, artifactId, version, null, type, classifier, new DefaultArtifactHandler(type));
+ }
+}
diff --git a/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/TestArtifactItem.java b/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/TestArtifactItem.java
index f29043cd8..95d5cd4d2 100644
--- a/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/TestArtifactItem.java
+++ b/src/test/java/org/apache/maven/plugins/dependency/fromConfiguration/TestArtifactItem.java
@@ -48,4 +48,13 @@ void testArtifactItemDefaultType() {
// check type default
assertEquals("jar", item.getType());
}
+
+ @Test
+ void testDependencyScope() {
+ ArtifactItem item = new ArtifactItem();
+
+ item.setDependencyScope("runtime");
+
+ assertEquals("runtime", item.getDependencyScope());
+ }
}