From 7720763f4206025ba37abb73ace27712e3a11cc8 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 3 Oct 2019 17:51:37 +0200 Subject: [PATCH] Support for hidden dependencies #12 --- .../fuse/mvnd/builder/DependencyGraph.java | 59 ++++++++++++++++++- .../jboss/fuse/mvnd/builder/SmartBuilder.java | 6 +- .../mvnd/builder/DependencyGraphTest.java | 37 ++++++++++++ .../mvnd/builder/ProjectComparatorTest.java | 6 +- .../builder/ProjectExecutorServiceTest.java | 3 +- .../mvnd/builder/ReactorBuildQueueTest.java | 9 ++- .../builder/TestProjectDependencyGraph.java | 23 +------- 7 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 src/test/java/org/jboss/fuse/mvnd/builder/DependencyGraphTest.java diff --git a/src/main/java/org/jboss/fuse/mvnd/builder/DependencyGraph.java b/src/main/java/org/jboss/fuse/mvnd/builder/DependencyGraph.java index 058b9b77..23a50a58 100644 --- a/src/main/java/org/jboss/fuse/mvnd/builder/DependencyGraph.java +++ b/src/main/java/org/jboss/fuse/mvnd/builder/DependencyGraph.java @@ -1,7 +1,25 @@ +/* + * Copyright 2019 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.jboss.fuse.mvnd.builder; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -10,12 +28,51 @@ import org.apache.maven.project.MavenProject; interface DependencyGraph { - static DependencyGraph fromMaven(ProjectDependencyGraph graph) { + static DependencyGraph fromMaven(ProjectDependencyGraph graph, String rules) { List projects = graph.getAllProjects(); Map> upstreams = projects.stream() .collect(Collectors.toMap(p -> p, p -> graph.getUpstreamProjects(p, false))); Map> downstreams = projects.stream() .collect(Collectors.toMap(p -> p, p -> graph.getDownstreamProjects(p, false))); + + if (rules != null) { + for (String rule : rules.split("\\s*;\\s*|\n")) { + String[] parts = rule.split("\\s*->\\s*|\\s+before\\s+"); + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid rule: " + rule); + } + List> deps = Stream.of(parts).map(s -> + Pattern.compile( + Arrays.stream(s.split("\\s*,\\s*|\\s+and\\s+")) + .map(String::trim) + .map(r -> r.contains(":") ? r : "*:" + r) + .map(r -> r.replaceAll("\\.", "\\.") + .replaceAll("\\*", ".*")) + .collect(Collectors.joining("|")))) + .map(t -> projects.stream() + .filter(p -> t.matcher(p.getGroupId() + ":" + p.getArtifactId()).matches()) + .collect(Collectors.toSet())) + .collect(Collectors.toList()); + + Set common = deps.get(0).stream().filter(deps.get(1)::contains).collect(Collectors.toSet()); + if (!common.isEmpty()) { + boolean leftWildcard = parts[0].contains("*"); + boolean rightWildcard = parts[1].contains("*"); + if (leftWildcard && rightWildcard) { + throw new IllegalArgumentException("Invalid rule: " + rule + ". Both left and right parts have wildcards and match the same project."); + } else if (leftWildcard) { + deps.get(0).removeAll(common); + } else if (rightWildcard) { + deps.get(1).removeAll(common); + } else { + throw new IllegalArgumentException("Invalid rule: " + rule + ". Both left and right parts match the same project."); + } + } + + deps.get(1).forEach(p -> upstreams.get(p).addAll(deps.get(0))); + deps.get(0).forEach(p -> downstreams.get(p).addAll(deps.get(1))); + } + } return new DependencyGraph() { @Override public Stream getDownstreamProjects(MavenProject project) { diff --git a/src/main/java/org/jboss/fuse/mvnd/builder/SmartBuilder.java b/src/main/java/org/jboss/fuse/mvnd/builder/SmartBuilder.java index b685defc..ebe912bf 100644 --- a/src/main/java/org/jboss/fuse/mvnd/builder/SmartBuilder.java +++ b/src/main/java/org/jboss/fuse/mvnd/builder/SmartBuilder.java @@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory; public class SmartBuilder implements Builder { public static final String PROP_PROFILING = "smartbuilder.profiling"; + public static final String MVND_BUILDER_RULES = "mvnd.builder.rules"; private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -51,7 +52,10 @@ public class SmartBuilder implements Builder { ProjectBuildList projectBuilds, final List taskSegments, ReactorBuildStatus reactorBuildStatus) throws ExecutionException, InterruptedException { - DependencyGraph graph = DependencyGraph.fromMaven(session.getProjectDependencyGraph()); + String rules = session.getTopLevelProject().getProperties() + .getProperty(MVND_BUILDER_RULES); + + DependencyGraph graph = DependencyGraph.fromMaven(session.getProjectDependencyGraph(), rules); // log overall build info final int degreeOfConcurrency = session.getRequest().getDegreeOfConcurrency(); diff --git a/src/test/java/org/jboss/fuse/mvnd/builder/DependencyGraphTest.java b/src/test/java/org/jboss/fuse/mvnd/builder/DependencyGraphTest.java new file mode 100644 index 00000000..101fc1a1 --- /dev/null +++ b/src/test/java/org/jboss/fuse/mvnd/builder/DependencyGraphTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 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.jboss.fuse.mvnd.builder; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.stream.Collectors; + +import org.apache.maven.project.MavenProject; +import org.junit.Assert; +import org.junit.Test; + +public class DependencyGraphTest extends AbstractSmartBuilderTest { + + @Test + public void testRules() { + MavenProject a = newProject("a"), b = newProject("b"), c = newProject("c"); + TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); + graph.addDependency(b, a); + DependencyGraph dp = DependencyGraph.fromMaven(graph, "a before c"); + Assert.assertEquals(new HashSet<>(Arrays.asList(b, c)), + dp.getDownstreamProjects(a).collect(Collectors.toSet())); + } +} diff --git a/src/test/java/org/jboss/fuse/mvnd/builder/ProjectComparatorTest.java b/src/test/java/org/jboss/fuse/mvnd/builder/ProjectComparatorTest.java index 05eca120..2fb6e107 100644 --- a/src/test/java/org/jboss/fuse/mvnd/builder/ProjectComparatorTest.java +++ b/src/test/java/org/jboss/fuse/mvnd/builder/ProjectComparatorTest.java @@ -19,8 +19,9 @@ public class ProjectComparatorTest extends AbstractSmartBuilderTest { MavenProject a = newProject("a"), b = newProject("b"), c = newProject("c"); TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); graph.addDependency(b, a); + DependencyGraph dp = DependencyGraph.fromMaven(graph, null); - Comparator cmp = org.jboss.fuse.mvnd.builder.ProjectComparator.create0(graph, new HashMap<>(), p -> id(p)); + Comparator cmp = ProjectComparator.create0(dp, new HashMap<>(), ProjectComparator::id); Queue queue = new PriorityQueue<>(3, cmp); queue.add(a); @@ -37,13 +38,14 @@ public class ProjectComparatorTest extends AbstractSmartBuilderTest { MavenProject a = newProject("a"), b = newProject("b"), c = newProject("c"); TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); graph.addDependency(b, a); + DependencyGraph dp = DependencyGraph.fromMaven(graph, null); HashMap serviceTimes = new HashMap<>(); serviceTimes.put(id(a), new AtomicLong(1L)); serviceTimes.put(id(b), new AtomicLong(1L)); serviceTimes.put(id(c), new AtomicLong(3L)); - Comparator cmp = ProjectComparator.create0(graph, serviceTimes, ProjectComparator::id); + Comparator cmp = ProjectComparator.create0(dp, serviceTimes, ProjectComparator::id); Queue queue = new PriorityQueue<>(3, cmp); queue.add(a); diff --git a/src/test/java/org/jboss/fuse/mvnd/builder/ProjectExecutorServiceTest.java b/src/test/java/org/jboss/fuse/mvnd/builder/ProjectExecutorServiceTest.java index 20bddc69..1c11a06e 100644 --- a/src/test/java/org/jboss/fuse/mvnd/builder/ProjectExecutorServiceTest.java +++ b/src/test/java/org/jboss/fuse/mvnd/builder/ProjectExecutorServiceTest.java @@ -25,13 +25,14 @@ public class ProjectExecutorServiceTest extends AbstractSmartBuilderTest { final MavenProject c = newProject("c"); TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); graph.addDependency(b, a); + DependencyGraph dp = DependencyGraph.fromMaven(graph, null); HashMap serviceTimes = new HashMap<>(); serviceTimes.put(id(a), new AtomicLong(1L)); serviceTimes.put(id(b), new AtomicLong(1L)); serviceTimes.put(id(c), new AtomicLong(3L)); - Comparator cmp = org.jboss.fuse.mvnd.builder.ProjectComparator.create0(graph, serviceTimes, p -> id(p)); + Comparator cmp = ProjectComparator.create0(dp, serviceTimes, ProjectComparator::id); PausibleProjectExecutorService executor = new PausibleProjectExecutorService(1, cmp); diff --git a/src/test/java/org/jboss/fuse/mvnd/builder/ReactorBuildQueueTest.java b/src/test/java/org/jboss/fuse/mvnd/builder/ReactorBuildQueueTest.java index 6f966f01..0a54abfb 100644 --- a/src/test/java/org/jboss/fuse/mvnd/builder/ReactorBuildQueueTest.java +++ b/src/test/java/org/jboss/fuse/mvnd/builder/ReactorBuildQueueTest.java @@ -11,8 +11,9 @@ public class ReactorBuildQueueTest extends AbstractSmartBuilderTest { MavenProject a = newProject("a"), b = newProject("b"), c = newProject("c"); TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); graph.addDependency(b, a); + DependencyGraph dp = DependencyGraph.fromMaven(graph, null); - ReactorBuildQueue schl = new ReactorBuildQueue(graph.getSortedProjects(), graph); + ReactorBuildQueue schl = new ReactorBuildQueue(graph.getSortedProjects(), dp); assertProjects(schl.getRootProjects(), a, c); Assert.assertFalse(schl.isEmpty()); @@ -25,8 +26,9 @@ public class ReactorBuildQueueTest extends AbstractSmartBuilderTest { public void testNoDependencies() { MavenProject a = newProject("a"), b = newProject("b"), c = newProject("c"); TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); + DependencyGraph dp = DependencyGraph.fromMaven(graph, null); - ReactorBuildQueue schl = new ReactorBuildQueue(graph.getSortedProjects(), graph); + ReactorBuildQueue schl = new ReactorBuildQueue(graph.getSortedProjects(), dp); assertProjects(schl.getRootProjects(), a, b, c); Assert.assertTrue(schl.isEmpty()); @@ -38,8 +40,9 @@ public class ReactorBuildQueueTest extends AbstractSmartBuilderTest { TestProjectDependencyGraph graph = new TestProjectDependencyGraph(a, b, c); graph.addDependency(b, a); graph.addDependency(b, c); + DependencyGraph dp = DependencyGraph.fromMaven(graph, null); - ReactorBuildQueue schl = new ReactorBuildQueue(graph.getSortedProjects(), graph); + ReactorBuildQueue schl = new ReactorBuildQueue(graph.getSortedProjects(), dp); assertProjects(schl.getRootProjects(), a, c); Assert.assertFalse(schl.isEmpty()); diff --git a/src/test/java/org/jboss/fuse/mvnd/builder/TestProjectDependencyGraph.java b/src/test/java/org/jboss/fuse/mvnd/builder/TestProjectDependencyGraph.java index 0b330b64..69b1b453 100644 --- a/src/test/java/org/jboss/fuse/mvnd/builder/TestProjectDependencyGraph.java +++ b/src/test/java/org/jboss/fuse/mvnd/builder/TestProjectDependencyGraph.java @@ -3,7 +3,6 @@ package org.jboss.fuse.mvnd.builder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.stream.Stream; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; @@ -11,7 +10,7 @@ import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.project.MavenProject; import org.junit.Assert; -public class TestProjectDependencyGraph implements ProjectDependencyGraph, DependencyGraph { +public class TestProjectDependencyGraph implements ProjectDependencyGraph { private final List projects = new ArrayList(); @@ -30,16 +29,6 @@ public class TestProjectDependencyGraph implements ProjectDependencyGraph, Depen return projects; } - @Override - public Stream getProjects() { - return projects.stream(); - } - - @Override - public boolean isRoot(MavenProject project) { - return getUpstreamProjects(project, false).isEmpty(); - } - @Override public List getSortedProjects() { return projects; @@ -51,22 +40,12 @@ public class TestProjectDependencyGraph implements ProjectDependencyGraph, Depen return downstream.get(project); } - @Override - public Stream getDownstreamProjects(MavenProject project) { - return downstream.get(project).stream(); - } - @Override public List getUpstreamProjects(MavenProject project, boolean transitive) { Assert.assertFalse("not implemented", transitive); return upstream.get(project); } - @Override - public Stream getUpstreamProjects(MavenProject project) { - return upstream.get(project).stream(); - } - public void addProject(MavenProject project) { projects.add(project); }