Skip to content

Commit

Permalink
Add method to gather dependencies
Browse files Browse the repository at this point in the history
This commit adds a couple helper methods used to aggregate symbol
dependencies into a map of maps. By default, conflicting package
versions will throw when two packages conflict. The merge behavior
can be customized by providing a custom merge function (for example, it
can de-conflict, throw, implement a better, picking strategy, etc.).
  • Loading branch information
mtdowling committed Sep 20, 2019
1 parent fa7729f commit e5eeb64
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@

package software.amazon.smithy.codegen.core;

import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

Expand Down Expand Up @@ -80,6 +86,68 @@ public static Builder builder() {
return new Builder();
}

/**
* Gets a mapping of all dependencies used by the provided symbols.
*
* <p>Given a stream of symbols, the dependencies of the symbol are gathered into
* a map of the dependencyType to a map of a package name to package version.
*
* <p>By default, when two versions conflict, an exception is thrown. In the
* case the a conflict is possible or it is necessary to detect incompatibilities,
* use {@link #gatherDependencies(Stream, BinaryOperator)} and provide a
* custom version merge function.
*
* @param symbolStream Stream of symbols to compute from.
* @return Returns a map of dependency types to a map of package to version.
* @throws CodegenException when two package versions conflict.
*/
public static Map<String, Map<String, SymbolDependency>> gatherDependencies(
Stream<SymbolDependency> symbolStream) {
return gatherDependencies(symbolStream, (a, b) -> {
throw new CodegenException(String.format(
"Found a conflicting `%s` dependency for `%s`: `%s` conflicts with `%s`",
a.getDependencyType(), a.getPackageName(), a.getVersion(), b.getVersion()));
});
}

/**
* Gets a mapping of all dependencies used by the provided symbols.
*
* <p>Given a stream of symbols, the dependencies of the symbol are gathered into
* a map of the dependencyType to a map of a package name to package version.
* Dependencies are sorted while they are collected, meaning that newer versions
* of a conflicting dependency typically take precedence over older versions.
* However, this is not always true with a natural sort order
* (e.g., 0.9 and 0.10).
*
* <p>{@code versionMergeFunction} is invoked each time a package import version
* of a package conflicts with another version of the same package for the
* same dependency type. The function accepts the dependency type, the package
* name, the previous version that was registered, the new conflicting version,
* and is expected to return the version that should be used or can throw in
* the case of an incompatible conflict. It is a target-specific concern to
* determine if two version are compatible or to find an acceptable compromise
* between the two versions.
*
* @param symbolStream Stream of symbols to compute from.
* @param versionMergeFunction Function that determines which two conflicting versions wins.
* @return Returns a map of dependency types to a map of package to version.
*/
public static Map<String, Map<String, SymbolDependency>> gatherDependencies(
Stream<SymbolDependency> symbolStream,
BinaryOperator<SymbolDependency> versionMergeFunction
) {
return symbolStream
.sorted()
.collect(Collectors.groupingBy(
SymbolDependency::getDependencyType,
Collectors.toMap(
SymbolDependency::getPackageName,
Function.identity(),
versionMergeFunction,
TreeMap::new)));
}

/**
* Gets the type of dependency (for example, "dev", "optional", etc).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.utils.Pair;

public class SymbolDependencyTest {
@Test
Expand Down Expand Up @@ -58,4 +64,41 @@ public void canBeSorted() {

assertThat(dependencies, contains(a, a2, a3, b, b2, c));
}

@Test
public void gathersDependencies() {
SymbolDependency a = SymbolDependency.builder().dependencyType("a").packageName("a").version("1").build();
SymbolDependency a2 = SymbolDependency.builder().dependencyType("a").packageName("a2").version("1").build();
SymbolDependency a3 = SymbolDependency.builder().dependencyType("a2").packageName("a").version("1").build();
SymbolDependency b = SymbolDependency.builder().dependencyType("b").packageName("b").version("1").build();
SymbolDependency b2 = SymbolDependency.builder().dependencyType("b").packageName("b").version("2").build();
SymbolDependency c = SymbolDependency.builder().dependencyType("b").packageName("c").version("1").build();

Assertions.assertThrows(CodegenException.class, () -> {
SymbolDependency.gatherDependencies(Stream.of(a, a2, a3, b, b2, c));
});

List<Pair<SymbolDependency, SymbolDependency>> conflicts = new ArrayList<>();
Map<String, Map<String, SymbolDependency>> result = SymbolDependency.gatherDependencies(
Stream.of(a, a2, a3, b, b2, c),
(sa, sb) -> {
conflicts.add(Pair.of(sa, sb));
return sb;
});

assertThat(conflicts, contains(Pair.of(b, b2)));
assertThat(result, hasKey("a"));
assertThat(result, hasKey("a2"));
assertThat(result, hasKey("b"));
assertThat(result.get("a"), hasKey("a"));
assertThat(result.get("a"), hasKey("a2"));
assertThat(result.get("a").get("a"), equalTo(a));
assertThat(result.get("a").get("a2"), equalTo(a2));
assertThat(result.get("a2"), hasKey("a"));
assertThat(result.get("a2").get("a"), equalTo(a3));
assertThat(result.get("b"), hasKey("b"));
assertThat(result.get("b").get("b"), equalTo(b2));
assertThat(result.get("b"), hasKey("c"));
assertThat(result.get("b").get("c"), equalTo(c));
}
}

0 comments on commit e5eeb64

Please sign in to comment.