diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2020-07-06 14:39:42 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2020-07-06 14:59:21 +0200 |
commit | 3234aba2bf97ff2d90c5218edb5a9894d156f084 (patch) | |
tree | 679e3f394539af41b8d9694029966a60f7606a15 /bundle-plugin/src | |
parent | a56e3edad71c5c457cfee6b585be3763759f9ff5 (diff) |
Redesign dependency scopes are handled when constructing a test bundle
Introduce concept of translating dependency scopes.
Change test bundle config in bundle-plugin to take a list of overrides for dependency scopes.
Diffstat (limited to 'bundle-plugin/src')
7 files changed, 246 insertions, 157 deletions
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java index bc6a970140d..0dbed043de0 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/Artifacts.java @@ -13,7 +13,16 @@ import java.util.List; * @author ollivir */ class Artifacts { + interface ScopeTranslator { + String scopeOf(Artifact artifact); + } + + static class NoopScopeTranslator implements ScopeTranslator { + @Override public String scopeOf(Artifact artifact) { return artifact.getScope(); } + } + static class ArtifactSet { + private final List<Artifact> jarArtifactsToInclude; private final List<Artifact> jarArtifactsProvided; private final List<Artifact> nonJarArtifacts; @@ -40,29 +49,25 @@ class Artifacts { } } - static ArtifactSet getArtifacts(MavenProject project) { return getArtifacts(project, false, null); } + static ArtifactSet getArtifacts(MavenProject project) { return getArtifacts(project, new NoopScopeTranslator()); } - static ArtifactSet getArtifacts(MavenProject project, boolean includeTestArtifacts, String testProvidedConfig) { - TestProvidedArtifacts testProvidedArtifacts = TestProvidedArtifacts.from(project.getArtifactMap(), testProvidedConfig); + static ArtifactSet getArtifacts(MavenProject project, ScopeTranslator scopeTranslator) { List<Artifact> jarArtifactsToInclude = new ArrayList<>(); List<Artifact> jarArtifactsProvided = new ArrayList<>(); List<Artifact> nonJarArtifactsToInclude = new ArrayList<>(); List<Artifact> nonJarArtifactsProvided = new ArrayList<>(); for (Artifact artifact : project.getArtifacts()) { + String scope = scopeTranslator.scopeOf(artifact); if ("jar".equals(artifact.getType())) { - if (includeTestArtifacts && testProvidedArtifacts.isTestProvided(artifact)) { - jarArtifactsProvided.add(artifact); - } else if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())) { + if (Artifact.SCOPE_COMPILE.equals(scope)) { jarArtifactsToInclude.add(artifact); - } else if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) { + } else if (Artifact.SCOPE_PROVIDED.equals(scope)) { jarArtifactsProvided.add(artifact); - } else if (includeTestArtifacts && Artifact.SCOPE_TEST.equals(artifact.getScope())) { - jarArtifactsToInclude.add(artifact); } } else { - if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())) { + if (Artifact.SCOPE_COMPILE.equals(scope)) { nonJarArtifactsToInclude.add(artifact); - } else if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) { + } else if (Artifact.SCOPE_PROVIDED.equals(scope)) { nonJarArtifactsProvided.add(artifact); } } @@ -72,6 +77,6 @@ class Artifacts { } static Collection<Artifact> getArtifactsToInclude(MavenProject project) { - return getArtifacts(project, false, null).getJarArtifactsToInclude(); + return getArtifacts(project, new NoopScopeTranslator()).getJarArtifactsToInclude(); } } diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleTestBundleMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleTestBundleMojo.java index ab827275f53..ddf382b02dc 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleTestBundleMojo.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/AssembleTestBundleMojo.java @@ -20,11 +20,12 @@ import static com.yahoo.container.plugin.mojo.TestBundleUtils.manifestFile; public class AssembleTestBundleMojo extends AbstractAssembleBundleMojo { @Parameter - private String testProvidedArtifacts; + private String testBundleScopeOverrides; @Override public void execute() throws MojoExecutionException { - Artifacts.ArtifactSet artifacts = Artifacts.getArtifacts(project, true, testProvidedArtifacts); + Artifacts.ArtifactSet artifacts = Artifacts.getArtifacts( + project, TestBundleDependencyScopeTranslator.from(project.getArtifactMap(), testBundleScopeOverrides)); JarArchiver archiver = new JarArchiver(); addDirectory(archiver, Paths.get(project.getBuild().getOutputDirectory())); addDirectory(archiver, Paths.get(project.getBuild().getTestOutputDirectory())); diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateTestBundleOsgiManifestMojo.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateTestBundleOsgiManifestMojo.java index 811aff87b7e..0d4b4dbc8a4 100644 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateTestBundleOsgiManifestMojo.java +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/GenerateTestBundleOsgiManifestMojo.java @@ -31,11 +31,12 @@ import static java.util.stream.Collectors.toList; public class GenerateTestBundleOsgiManifestMojo extends AbstractGenerateOsgiManifestMojo { @Parameter - private String testProvidedArtifacts; + private String testBundleScopeOverrides; public void execute() throws MojoExecutionException { try { - Artifacts.ArtifactSet artifactSet = Artifacts.getArtifacts(project, true, testProvidedArtifacts); + Artifacts.ArtifactSet artifactSet = Artifacts.getArtifacts( + project, TestBundleDependencyScopeTranslator.from(project.getArtifactMap(), testBundleScopeOverrides)); List<File> providedJars = artifactSet.getJarArtifactsProvided().stream() .map(Artifact::getFile) diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/TestBundleDependencyScopeTranslator.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/TestBundleDependencyScopeTranslator.java new file mode 100644 index 00000000000..9ad0a0e34ba --- /dev/null +++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/TestBundleDependencyScopeTranslator.java @@ -0,0 +1,122 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.mojo; + +import org.apache.maven.artifact.Artifact; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +import static java.util.stream.Collectors.toList; + +/** + * Translates the scope of dependencies when constructing a test bundle. + * Used by {@link Artifacts} to determine which artifacts that are provided by the runtime or must be included in the bundle. + * + * Dependencies of scope 'test' are by default translated to 'compile'. Dependencies of other scopes are kept as is. + * + * Default scope translation can be overridden through a comma-separated configuration string. + * Each substring is a triplet on the form [groupId]:[artifactId]:[scope]. + * Scope translation overrides affects all transitive dependencies. + * The ordering of the triplets determines the priority - only the first matching override will affect a given dependency. + * + * @author bjorncs + */ +class TestBundleDependencyScopeTranslator implements Artifacts.ScopeTranslator { + + private static final Logger log = Logger.getLogger(TestBundleDependencyScopeTranslator.class.getName()); + + private final Map<Artifact, String> dependencyScopes; + + private TestBundleDependencyScopeTranslator(Map<Artifact, String> dependencyScopes) { + this.dependencyScopes = dependencyScopes; + } + + @Override public String scopeOf(Artifact artifact) { return Objects.requireNonNull(dependencyScopes.get(artifact)); } + + static TestBundleDependencyScopeTranslator from(Map<String, Artifact> dependencies, String rawConfig) { + List<DependencyOverride> dependencyOverrides = toDependencyOverrides(rawConfig); + Map<Artifact, String> dependencyScopes = new HashMap<>(); + for (Artifact dependency : dependencies.values()) { + dependencyScopes.put(dependency, getScopeForDependency(dependency, dependencyOverrides, dependencies)); + } + return new TestBundleDependencyScopeTranslator(dependencyScopes); + } + + private static List<DependencyOverride> toDependencyOverrides(String rawConfig) { + if (rawConfig == null || rawConfig.isBlank()) return List.of(); + return Arrays.stream(rawConfig.split(",")) + .map(String::strip) + .filter(s -> !s.isBlank()) + .map(TestBundleDependencyScopeTranslator::toDependencyOverride) + .collect(toList()); + } + + private static DependencyOverride toDependencyOverride(String overrideString) { + String[] elements = overrideString.split(":"); + if (elements.length != 3) { + throw new IllegalArgumentException("Invalid dependency override: " + overrideString); + } + return new DependencyOverride(elements[0], elements[1], elements[2]); + } + + private static String stripVersionAndScope(String idInDependencyTrail) { + int firstDelimiter = idInDependencyTrail.indexOf(':'); + int secondDelimiter = idInDependencyTrail.indexOf(':', firstDelimiter + 1); + return idInDependencyTrail.substring(0, secondDelimiter); + } + + private static String getScopeForDependency( + Artifact dependency, List<DependencyOverride> overrides, Map<String, Artifact> otherArtifacts) { + String oldScope = dependency.getScope(); + for (DependencyOverride override : overrides) { + for (Artifact dependent : dependencyTrailOf(dependency, otherArtifacts)) { + if (override.isForArtifact(dependent)) { + // This translation is not always correct for artifacts having 'runtime' scope dependencies. + // If such dependencies are overridden to 'compile' scope, its 'runtime' dependencies will get + // scope 'compile' instead of 'runtime'. + log.fine(() -> String.format( + "Overriding scope of '%s'; scope '%s' overridden to '%s'", + dependency.getId(), oldScope, override.scope)); + return override.scope; + } + } + } + String newScope = defaultScopeTranslationOf(oldScope); + log.fine(() -> String.format( + "Using default scope translation for '%s'; scope '%s' translated to '%s'", + dependency.getId(), oldScope, newScope)); + return newScope; + } + + private static List<Artifact> dependencyTrailOf(Artifact artifact, Map<String, Artifact> otherArtifacts) { + return artifact.getDependencyTrail().stream() + .skip(1) // Maven project itself is the first entry + .map(parentId -> otherArtifacts.get(stripVersionAndScope(parentId))) + .filter(Objects::nonNull) + .collect(toList()); + } + + private static String defaultScopeTranslationOf(String scope) { + return scope.equals(Artifact.SCOPE_TEST) ? Artifact.SCOPE_COMPILE : scope; + } + + private static class DependencyOverride { + final String groupId; + final String artifactId; + final String scope; + + DependencyOverride(String groupId, String artifactId, String scope) { + this.groupId = groupId; + this.artifactId = artifactId; + this.scope = scope; + } + + boolean isForArtifact(Artifact artifact) { + return artifact.getGroupId().equals(groupId) && artifact.getArtifactId().equals(artifactId); + } + } +} diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/TestProvidedArtifacts.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/TestProvidedArtifacts.java deleted file mode 100644 index e5a64744860..00000000000 --- a/bundle-plugin/src/main/java/com/yahoo/container/plugin/mojo/TestProvidedArtifacts.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.plugin.mojo; - -import org.apache.maven.artifact.Artifact; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; - -/** - * Determines the test dependencies that are provided by the Vespa/JDisc test runtime based on the resolved dependency graph and a config string. - * "Test provided" dependencies are treated as "provided" scope dependencies when building a test bundle. - * - * @author bjorncs - */ -class TestProvidedArtifacts { - - private final List<Artifact> artifacts; - - private TestProvidedArtifacts(List<Artifact> artifacts) { this.artifacts = artifacts; } - - boolean isTestProvided(Artifact artifact) { return artifacts.contains(artifact); } - - static TestProvidedArtifacts from(Map<String, Artifact> artifacts, String configString) { - if (configString == null || configString.isBlank()) return new TestProvidedArtifacts(List.of()); - return new TestProvidedArtifacts(getTestProvidedArtifacts(artifacts, configString)); - } - - private static List<Artifact> getTestProvidedArtifacts(Map<String, Artifact> artifacts, String configString) { - List<String> testProvidedArtifactStringIds = toTestProvidedArtifactStringIds(configString); - List<Artifact> testProvidedArtifacts = new ArrayList<>(); - for (Artifact artifact : artifacts.values()) { - boolean hasTestProvidedArtifactAsParent = - dependencyTrail(artifact, artifacts) - .anyMatch(parent -> testProvidedArtifactStringIds.contains(toArtifactStringId(parent))); - boolean isBlocked = testProvidedArtifactStringIds.contains(toBlockedArtifactStringId(artifact)); - if (hasTestProvidedArtifactAsParent && !isBlocked) { - testProvidedArtifacts.add(artifact); - } - } - return testProvidedArtifacts; - } - - private static List<String> toTestProvidedArtifactStringIds(String commaSeparatedString) { - if (commaSeparatedString == null || commaSeparatedString.isBlank()) return List.of(); - return Arrays.stream(commaSeparatedString.split(",")) - .map(String::strip) - .filter(s -> !s.isBlank()) - .collect(toList()); - } - - private static Stream<Artifact> dependencyTrail(Artifact artifact, Map<String, Artifact> otherArtifacts) { - return artifact.getDependencyTrail().stream() - .map(parentId -> otherArtifacts.get(stripVersionAndScope(parentId))) - .filter(Objects::nonNull); - } - - private static String stripVersionAndScope(String fullArtifactIdentifier) { - int firstDelimiter = fullArtifactIdentifier.indexOf(':'); - int secondDelimiter = fullArtifactIdentifier.indexOf(':', firstDelimiter + 1); - return fullArtifactIdentifier.substring(0, secondDelimiter); - } - - private static String toArtifactStringId(Artifact artifact) { return artifact.getGroupId() + ":" + artifact.getArtifactId(); } - - private static String toBlockedArtifactStringId(Artifact artifact) { return "!" + toArtifactStringId(artifact); } - -} diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/TestBundleDependencyScopeTranslatorTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/TestBundleDependencyScopeTranslatorTest.java new file mode 100644 index 00000000000..c0e1c9b4b9a --- /dev/null +++ b/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/TestBundleDependencyScopeTranslatorTest.java @@ -0,0 +1,101 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.plugin.mojo; + + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static org.junit.Assert.assertEquals; + +/** + * @author bjorncs + */ +public class TestBundleDependencyScopeTranslatorTest { + + private static final String GROUP_ID = "com.test"; + + @Test + public void findsAllTestProvidedDependencies() { + Map<String, Artifact> artifacts = new TreeMap<>(); + Artifact a = createArtifact(artifacts, "a", "compile", List.of()); + Artifact aa = createArtifact(artifacts, "a-a", "compile", List.of("a")); + Artifact ab = createArtifact(artifacts, "a-b", "runtime", List.of("a")); + Artifact aba = createArtifact(artifacts, "a-b-a", "runtime", List.of("a", "a-b")); + Artifact ac = createArtifact(artifacts, "a-c", "runtime", List.of("a")); + Artifact ad = createArtifact(artifacts, "a-d", "compile", List.of("a")); + Artifact ada = createArtifact(artifacts, "a-d-a", "compile", List.of("a", "a-d")); + Artifact adb = createArtifact(artifacts, "a-d-b", "compile", List.of("a", "a-d")); + Artifact b = createArtifact(artifacts, "b", "provided", List.of()); + Artifact ba = createArtifact(artifacts, "b-a", "provided", List.of("b")); + Artifact bb = createArtifact(artifacts, "b-b", "provided", List.of("b")); + Artifact c = createArtifact(artifacts, "c", "runtime", List.of()); + Artifact ca = createArtifact(artifacts, "c-a", "runtime", List.of("c")); + Artifact d = createArtifact(artifacts, "d", "test", List.of()); + Artifact da = createArtifact(artifacts, "d-a", "test", List.of("d")); + Artifact daa = createArtifact(artifacts, "d-a-a", "test", List.of("d", "d-a")); + Artifact db = createArtifact(artifacts, "d-b", "test", List.of("d")); + Artifact dc = createArtifact(artifacts, "d-c", "test", List.of("d")); + Artifact dca = createArtifact(artifacts, "d-c-a", "test", List.of("d", "d-c")); + + String configString = + "com.test:a-d:compile," + + "com.test:a:provided," + + "com.test:d-a:test," + + "com.test:d-c:compile," + + "com.test:d:runtime"; + TestBundleDependencyScopeTranslator translator = TestBundleDependencyScopeTranslator.from(artifacts, configString); + assertScope(translator, a, "provided"); + assertScope(translator, aa, "provided"); + assertScope(translator, ab, "provided"); + assertScope(translator, aba, "provided"); + assertScope(translator, ac, "provided"); + assertScope(translator, ad, "compile"); + assertScope(translator, ada, "compile"); + assertScope(translator, adb, "compile"); + assertScope(translator, b, "provided"); + assertScope(translator, ba, "provided"); + assertScope(translator, bb, "provided"); + assertScope(translator, c, "runtime"); + assertScope(translator, ca, "runtime"); + assertScope(translator, d, "runtime"); + assertScope(translator, da, "test"); + assertScope(translator, daa, "test"); + assertScope(translator, db, "runtime"); + assertScope(translator, dc, "compile"); + assertScope(translator, dca, "compile"); + } + + private static Artifact createArtifact( + Map<String, Artifact> artifacts, String artifactId, String scope, List<String> transitiveDependents) { + Artifact artifact = createArtifact(artifactId, scope, transitiveDependents); + artifacts.put(simpleId(artifactId), artifact); + return artifact; + } + + private static Artifact createArtifact(String artifactId, String scope, List<String> transitiveDependents) { + Artifact artifact = new DefaultArtifact( + GROUP_ID, artifactId, "1.0", scope, "jar", /*classifier*/null, new DefaultArtifactHandler("jar")); + List<String> dependencyTrail = new ArrayList<>(); + dependencyTrail.add(GROUP_ID + "my-project:container-plugin:1-SNAPSHOT"); + transitiveDependents.forEach(dependent -> dependencyTrail.add(fullId(dependent))); + dependencyTrail.add(fullId(artifactId)); + artifact.setDependencyTrail(dependencyTrail); + return artifact; + } + + private static void assertScope( + TestBundleDependencyScopeTranslator translator, Artifact artifact, String expectedScope) { + assertEquals(expectedScope, translator.scopeOf(artifact)); + } + + private static String fullId(String artifactId) { return simpleId(artifactId) + ":jar:1.0"; } + private static String simpleId(String artifactId) { return GROUP_ID + ":" + artifactId; } + +}
\ No newline at end of file diff --git a/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/TestProvidedArtifactsTest.java b/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/TestProvidedArtifactsTest.java deleted file mode 100644 index b60bce794f5..00000000000 --- a/bundle-plugin/src/test/java/com/yahoo/container/plugin/mojo/TestProvidedArtifactsTest.java +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.plugin.mojo; - - -import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.DefaultArtifact; -import org.apache.maven.artifact.handler.DefaultArtifactHandler; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * @author bjorncs - */ -public class TestProvidedArtifactsTest { - - private static final String GROUP_ID = "com.test"; - - @Test - public void findsAllTestProvidedDependencies() { - Map<String, Artifact> artifacts = new TreeMap<>(); - Artifact a = createArtifact(artifacts, "a"); - Artifact aa = createArtifact(artifacts, "a-a", "a"); - Artifact ab = createArtifact(artifacts, "a-b", "a"); - Artifact aaa = createArtifact(artifacts, "a-a-a", "a", "a-a"); - Artifact b = createArtifact(artifacts, "b"); - Artifact ba = createArtifact(artifacts, "b-a", "b"); - Artifact c = createArtifact(artifacts, "c"); - - String configString = "com.test:a,com.test:b-a,!com.test:a-b"; - TestProvidedArtifacts testProvidedArtifacts = TestProvidedArtifacts.from(artifacts, configString); - - assertTrue(testProvidedArtifacts.isTestProvided(a)); - assertTrue(testProvidedArtifacts.isTestProvided(aa)); - assertFalse(testProvidedArtifacts.isTestProvided(ab)); - assertTrue(testProvidedArtifacts.isTestProvided(aaa)); - assertFalse(testProvidedArtifacts.isTestProvided(b)); - assertTrue(testProvidedArtifacts.isTestProvided(ba)); - assertFalse(testProvidedArtifacts.isTestProvided(c)); - } - - private static Artifact createArtifact(Map<String, Artifact> artifacts, String artifactId, String... dependents) { - Artifact artifact = createArtifact(artifactId, dependents); - artifacts.put(simpleId(artifactId), artifact); - return artifact; - } - - private static Artifact createArtifact(String artifactId, String... dependents) { - Artifact artifact = new DefaultArtifact(GROUP_ID, artifactId, "1.0", "test", "jar", "deploy", new DefaultArtifactHandler("jar")); - List<String> dependencyTrail = new ArrayList<>(); - dependencyTrail.add(fullId("bundle-plugin")); - Arrays.stream(dependents).forEach(dependent -> dependencyTrail.add(fullId(dependent))); - dependencyTrail.add(fullId(artifactId)); - artifact.setDependencyTrail(dependencyTrail); - return artifact; - } - - private static String fullId(String artifactId) { return simpleId(artifactId) + ":1.0:compile"; } - private static String simpleId(String artifactId) { return GROUP_ID + ":" + artifactId; } - -}
\ No newline at end of file |