Browse Source
Update spring-boot-dependency-tools to support transitive excludes. Transitive excludes are useful with Gradle which considers each dependency independently (see GRADLE-3061). Transitive excludes are supported by parsing the dependency-tree file from spring-boot-versions. See gh-1047pull/1052/head
12 changed files with 1604 additions and 4 deletions
@ -0,0 +1,131 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2014 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed 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.springframework.boot.dependency.tools; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.boot.dependency.tools.Dependency.Exclusion; |
||||||
|
import org.springframework.boot.dependency.tools.Dependency.ExclusionType; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link Dependencies} to extend an existing {@link Dependencies} instance with |
||||||
|
* transitive {@link Exclusion}s located from a {@link DependencyTree}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 1.1.0 |
||||||
|
*/ |
||||||
|
class DependenciesWithTransitiveExclusions extends AbstractDependencies { |
||||||
|
|
||||||
|
public DependenciesWithTransitiveExclusions(Dependencies dependencies, |
||||||
|
DependencyTree tree) { |
||||||
|
DependencyBuilder builder = new DependencyBuilder(dependencies); |
||||||
|
builder.addTransitiveExcludes(tree); |
||||||
|
builder.finish(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Builder used to collect the transitive exclusions. |
||||||
|
*/ |
||||||
|
private class DependencyBuilder { |
||||||
|
|
||||||
|
private Map<ArtifactAndGroupId, DependencyAndTransitiveExclusions> dependencies; |
||||||
|
|
||||||
|
public DependencyBuilder(Dependencies dependencies) { |
||||||
|
this.dependencies = new LinkedHashMap<ArtifactAndGroupId, DependencyAndTransitiveExclusions>(); |
||||||
|
for (Dependency dependency : dependencies) { |
||||||
|
this.dependencies.put(new ArtifactAndGroupId(dependency), |
||||||
|
new DependencyAndTransitiveExclusions(dependency)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addTransitiveExcludes(DependencyTree tree) { |
||||||
|
for (DependencyNode node : tree) { |
||||||
|
DependencyAndTransitiveExclusions dependency = this.dependencies |
||||||
|
.get(asArtifactAndGroupId(node)); |
||||||
|
if (dependency != null) { |
||||||
|
for (DependencyNode child : node) { |
||||||
|
addTransitiveExcludes(dependency, child); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void addTransitiveExcludes(DependencyAndTransitiveExclusions dependency, |
||||||
|
DependencyNode node) { |
||||||
|
DependencyAndTransitiveExclusions exclusions = this.dependencies |
||||||
|
.get(asArtifactAndGroupId(node)); |
||||||
|
if (exclusions != null) { |
||||||
|
dependency.addTransitiveExclusions(exclusions.getSourceDependency()); |
||||||
|
} |
||||||
|
for (DependencyNode child : node) { |
||||||
|
addTransitiveExcludes(dependency, child); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private ArtifactAndGroupId asArtifactAndGroupId(DependencyNode node) { |
||||||
|
return new ArtifactAndGroupId(node.getGroupId(), node.getArtifactId()); |
||||||
|
} |
||||||
|
|
||||||
|
public void finish() { |
||||||
|
for (Map.Entry<ArtifactAndGroupId, DependencyAndTransitiveExclusions> entry : this.dependencies |
||||||
|
.entrySet()) { |
||||||
|
add(entry.getKey(), entry.getValue().createNewDependency()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Holds a {@link Dependency} with additional transitive {@link Exclusion}s. |
||||||
|
*/ |
||||||
|
private static class DependencyAndTransitiveExclusions { |
||||||
|
|
||||||
|
private Dependency dependency; |
||||||
|
|
||||||
|
private Set<Exclusion> transitiveExclusions = new LinkedHashSet<Exclusion>(); |
||||||
|
|
||||||
|
public DependencyAndTransitiveExclusions(Dependency dependency) { |
||||||
|
this.dependency = dependency; |
||||||
|
} |
||||||
|
|
||||||
|
public Dependency getSourceDependency() { |
||||||
|
return this.dependency; |
||||||
|
} |
||||||
|
|
||||||
|
public void addTransitiveExclusions(Dependency dependency) { |
||||||
|
for (Exclusion exclusion : dependency.getExclusions()) { |
||||||
|
this.transitiveExclusions.add(new Exclusion(exclusion.getGroupId(), |
||||||
|
exclusion.getArtifactId(), ExclusionType.TRANSITIVE)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Dependency createNewDependency() { |
||||||
|
Set<Exclusion> exclusions = new LinkedHashSet<Dependency.Exclusion>(); |
||||||
|
exclusions.addAll(this.dependency.getExclusions()); |
||||||
|
exclusions.addAll(this.transitiveExclusions); |
||||||
|
return new Dependency(this.dependency.getGroupId(), |
||||||
|
this.dependency.getArtifactId(), this.dependency.getVersion(), |
||||||
|
new ArrayList<Exclusion>(exclusions)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,82 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2014 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed 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.springframework.boot.dependency.tools; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* A single node in a {@link DependencyTree}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @see DependencyTree |
||||||
|
* @since 1.1.0 |
||||||
|
*/ |
||||||
|
class DependencyNode implements Iterable<DependencyNode> { |
||||||
|
|
||||||
|
private final String groupId; |
||||||
|
|
||||||
|
private final String artifactId; |
||||||
|
|
||||||
|
private final String version; |
||||||
|
|
||||||
|
private List<DependencyNode> dependencies; |
||||||
|
|
||||||
|
DependencyNode(String groupId, String artifactId, String version) { |
||||||
|
this.groupId = groupId; |
||||||
|
this.artifactId = artifactId; |
||||||
|
this.version = version; |
||||||
|
this.dependencies = new ArrayList<DependencyNode>(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterator<DependencyNode> iterator() { |
||||||
|
return getDependencies().iterator(); |
||||||
|
} |
||||||
|
|
||||||
|
public String getGroupId() { |
||||||
|
return this.groupId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getArtifactId() { |
||||||
|
return this.artifactId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getVersion() { |
||||||
|
return this.version; |
||||||
|
} |
||||||
|
|
||||||
|
public List<DependencyNode> getDependencies() { |
||||||
|
return Collections.unmodifiableList(this.dependencies); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return this.groupId + ":" + this.artifactId + ":" + this.version; |
||||||
|
} |
||||||
|
|
||||||
|
void addDependency(DependencyNode node) { |
||||||
|
this.dependencies.add(node); |
||||||
|
} |
||||||
|
|
||||||
|
DependencyNode getLastDependency() { |
||||||
|
return this.dependencies.get(this.dependencies.size() - 1); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,159 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2014 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed 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.springframework.boot.dependency.tools; |
||||||
|
|
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.InputStreamReader; |
||||||
|
import java.util.ArrayDeque; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Deque; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
/** |
||||||
|
* Dependency tree information that can be loaded from the output of |
||||||
|
* {@literal mvn dependency:tree}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 1.1.0 |
||||||
|
* @see DependencyNode |
||||||
|
*/ |
||||||
|
class DependencyTree implements Iterable<DependencyNode> { |
||||||
|
|
||||||
|
private final DependencyNode root; |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@link DependencyTree} instance for the given input stream. |
||||||
|
* @param inputStream input stream containing content from |
||||||
|
* {@literal mvn dependency:tree} (the stream will be closed). |
||||||
|
*/ |
||||||
|
public DependencyTree(InputStream inputStream) { |
||||||
|
try { |
||||||
|
this.root = parse(inputStream); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new IllegalStateException(ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private DependencyNode parse(InputStream inputStream) throws IOException { |
||||||
|
try { |
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); |
||||||
|
Parser parser = new Parser(); |
||||||
|
String line; |
||||||
|
while ((line = reader.readLine()) != null) { |
||||||
|
parser.append(line); |
||||||
|
} |
||||||
|
return parser.getRoot(); |
||||||
|
} |
||||||
|
finally { |
||||||
|
inputStream.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterator<DependencyNode> iterator() { |
||||||
|
return getDependencies().iterator(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the root node for the tree. |
||||||
|
*/ |
||||||
|
public DependencyNode getRoot() { |
||||||
|
return this.root; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the dependencies of the root node. |
||||||
|
*/ |
||||||
|
public List<DependencyNode> getDependencies() { |
||||||
|
return this.root.getDependencies(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the node at the specified index. |
||||||
|
* @param index the index (multiple indexes can be used to traverse the tree) |
||||||
|
* @return the node at the specified index |
||||||
|
*/ |
||||||
|
public DependencyNode get(int... index) { |
||||||
|
DependencyNode rtn = this.root; |
||||||
|
for (int i : index) { |
||||||
|
rtn = rtn.getDependencies().get(i); |
||||||
|
} |
||||||
|
return rtn; |
||||||
|
} |
||||||
|
|
||||||
|
private static class Parser { |
||||||
|
|
||||||
|
private static final int INDENT = 3; |
||||||
|
|
||||||
|
private static final Set<Character> PREFIX_CHARS = new HashSet<Character>( |
||||||
|
Arrays.asList(' ', '+', '-', '\\', '|')); |
||||||
|
|
||||||
|
private static final Pattern LINE_PATTERN = Pattern |
||||||
|
.compile("[(]?([^:]*):([^:]*):([^:]*):([^:\\s]*)"); |
||||||
|
|
||||||
|
private Deque<DependencyNode> stack = new ArrayDeque<DependencyNode>(); |
||||||
|
|
||||||
|
public void append(String line) { |
||||||
|
int depth = getDepth(line); |
||||||
|
String data = line.substring(depth * INDENT); |
||||||
|
if (depth == 0) { |
||||||
|
this.stack.push(createNode(data)); |
||||||
|
} |
||||||
|
else { |
||||||
|
while (depth < this.stack.size()) { |
||||||
|
this.stack.pop(); |
||||||
|
} |
||||||
|
if (depth > this.stack.size()) { |
||||||
|
this.stack.push(this.stack.peek().getLastDependency()); |
||||||
|
} |
||||||
|
this.stack.peek().addDependency(createNode(data)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private int getDepth(String line) { |
||||||
|
for (int i = 0; i < line.length(); i++) { |
||||||
|
if (!Parser.PREFIX_CHARS.contains(line.charAt(i))) { |
||||||
|
return i / INDENT; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
private DependencyNode createNode(String line) { |
||||||
|
Matcher matcher = LINE_PATTERN.matcher(line); |
||||||
|
if (!matcher.find()) { |
||||||
|
throw new IllegalStateException("Unable to parese line " + line); |
||||||
|
} |
||||||
|
return new DependencyNode(matcher.group(1), matcher.group(2), |
||||||
|
matcher.group(4)); |
||||||
|
} |
||||||
|
|
||||||
|
public DependencyNode getRoot() { |
||||||
|
return this.stack.getLast(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2014 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed 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.springframework.boot.dependency.tools; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo; |
||||||
|
import static org.junit.Assert.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link DependenciesWithTransitiveExclusions}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
*/ |
||||||
|
public class DependenciesWithTransitiveExclusionsTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void findsTransitiveExclusions() throws Exception { |
||||||
|
Dependencies source = new PomDependencies(getClass().getResourceAsStream( |
||||||
|
"test-effective-pom.xml")); |
||||||
|
DependencyTree tree = new DependencyTree(getClass().getResourceAsStream( |
||||||
|
"test-effective-pom-dependency-tree.txt")); |
||||||
|
DependenciesWithTransitiveExclusions dependencies = new DependenciesWithTransitiveExclusions( |
||||||
|
source, tree); |
||||||
|
assertExcludes(dependencies, "sample01", "[org.exclude:exclude01]"); |
||||||
|
assertExcludes(source, "sample02", "[]"); |
||||||
|
assertExcludes(dependencies, "sample02", "[org.exclude:exclude01]"); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertExcludes(Dependencies dependencies, String artifactId, |
||||||
|
String expected) { |
||||||
|
Dependency dependency = dependencies.find("org.sample", artifactId); |
||||||
|
assertThat(dependency.getExclusions().toString(), equalTo(expected)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2014 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed 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.springframework.boot.dependency.tools; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.boot.dependency.tools.DependencyTree; |
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo; |
||||||
|
import static org.junit.Assert.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link DependencyTree}. |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
*/ |
||||||
|
public class DependencyTreeTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void parse() throws Exception { |
||||||
|
DependencyTree tree = new DependencyTree(getClass().getResourceAsStream( |
||||||
|
"sample-dependency-tree.txt")); |
||||||
|
assertThat(tree.getRoot().toString(), equalTo("org.springframework.boot:" |
||||||
|
+ "spring-boot-versions-dependency-tree:1.1.0.BUILD-SNAPSHOT")); |
||||||
|
assertThat(tree.getDependencies().size(), equalTo(204)); |
||||||
|
assertThat(tree.get(0, 1).toString(), equalTo("org.slf4j:slf4j-api:1.7.6")); |
||||||
|
assertThat(tree.get(203).toString(), equalTo("org.springframework.security:" |
||||||
|
+ "spring-security-web:3.2.4.RELEASE")); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@ |
|||||||
|
org.sample:sample:pom:1.0.0.BUILD-SNAPSHOT |
||||||
|
+- org.sample:sample01:jar:1.0.0:compile |
||||||
|
+- org.sample:sample02:jar:1.0.0:compile |
||||||
|
| +- (org.sample:sample01:jar:1.0.0:compile - omitted for duplicate) |
||||||
|
\- org.springframework.boot:spring-boot:jar:1.0.0.BUILD-SNAPSHOT:compile |
||||||
Loading…
Reference in new issue