Skip to content

Commit 3439691

Browse files
authored
feat: unmanaged dependency check (#2223)
* feat: add a unmanaged dependency check * feat: add unmanaged dependency check * change parent * change source/target * add private constructor * throw exception if the private constructor is called * remove module * add main function * add action yaml * add ci to check dependencies * delete unused file * change action path * change working directory * install modules * parse version of shared-dependencies * upgrade checkout action * add error exit * add an unmanaged dependency * surpress mvn output * add slf4j * add echo to debug * checkout branch head * add an unit test * add an execution * change phase * use ClassPathBuilder * restore BOM * fix shared dependency version * change check's description * change version * run unit tests in ci * change error message * only run tests in unmanaged dependency check module * change install command * add a debug echo * change exec path * do not install check * change working dir * print mvn log * install modules * modify ut * add javadoc * use github.action_path * change pom path * change command sequence * exclude handwritten artifacts * retrieve latest shared dependencies using git tag * add a variable * debug * combine steps * use shared dependencies bom path * install pom in test * revert depdendency * install shared dependencies in action.yaml * add a tag for the check * refactor * restore public * use artifact instead of toString * change error message * change variable name * remove main method * Revert "remove main method" This reverts commit 38b3ee4. * change description
1 parent c7de93e commit 3439691

File tree

12 files changed

+529
-3
lines changed

12 files changed

+529
-3
lines changed

.github/workflows/ci.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,25 @@ jobs:
299299
with:
300300
bom-path: gapic-generator-java-bom/pom.xml
301301

302+
unmanaged_dependency_check:
303+
runs-on: ubuntu-22.04
304+
steps:
305+
- uses: actions/checkout@v4
306+
with:
307+
ref: ${{ github.event.pull_request.head.sha }}
308+
- uses: actions/setup-java@v3
309+
with:
310+
java-version: 11
311+
distribution: temurin
312+
- run: mvn -version
313+
- name: Unit Tests
314+
run: |
315+
mvn test --batch-mode --no-transfer-progress
316+
working-directory: java-shared-dependencies/unmanaged-dependency-check
317+
- name: Install Maven modules
318+
run: |
319+
mvn install -B -ntp -DskipTests -Dclirr.skip -Dcheckstyle.skip
320+
- name: Unmanaged dependency check
321+
uses: ./java-shared-dependencies/unmanaged-dependency-check
322+
with:
323+
bom-path: gapic-generator-java-bom/pom.xml

.github/workflows/create_additional_release_tag.yaml

+8-3
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ jobs:
1414
uses: actions/checkout@v3
1515
with:
1616
token: ${{ secrets.GITHUB_TOKEN }}
17-
1817
- name: Set up Git
1918
run: |
2019
git config --local user.email "[email protected]"
2120
git config --local user.name "GitHub Action"
22-
2321
- name: Fetch all tags
2422
run: git fetch --tags
25-
2623
- name: Create additional tags
24+
bash: shell
2725
run: |
2826
ARTIFACT_IDS=('google-cloud-shared-dependencies' 'api-common' 'gax')
2927
for ARTIFACT_ID in "${ARTIFACT_IDS[@]}"; do
@@ -36,3 +34,10 @@ jobs:
3634
git tag $TAG_NAME
3735
git push origin $TAG_NAME
3836
done
37+
# Generate a tag for unmanaged dependencies check.
38+
# Use fixed tag so that checks in handwritten libraries do not need to
39+
# update the version.
40+
CHECK_LATEST_TAG="unmanaged-dependencies-check-latest"
41+
git tag ${CHECK_LATEST_TAG}
42+
git push origin -f ${CHECK_LATEST_TAG}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: "Unmanaged dependency check"
2+
description: "Checks whether there's a dependency that is not managed by java shared dependencies."
3+
inputs:
4+
bom-path:
5+
description: "The relative path from the repository root to the pom.xml file"
6+
required: true
7+
runs:
8+
using: "composite"
9+
steps:
10+
- uses: actions/setup-java@v3
11+
with:
12+
distribution: temurin
13+
java-version: 11
14+
cache: maven
15+
- name: Set up Maven
16+
uses: stCarolas/[email protected]
17+
with:
18+
maven-version: 3.8.2
19+
- name: Install latest Java shared dependencies
20+
shell: bash
21+
run: |
22+
cd ${{ github.action_path }}/..
23+
echo "Install Java shared dependencies"
24+
mvn clean install -V --batch-mode --no-transfer-progress -DskipTests
25+
- name: Install check
26+
shell: bash
27+
run: |
28+
cd ${{ github.action_path }}
29+
echo "Install Unmanaged Dependency Check in $(pwd)"
30+
mvn clean install -V --batch-mode --no-transfer-progress -DskipTests
31+
- name: Run unmanaged dependency check
32+
shell: bash
33+
run: |
34+
bom_absolute_path=$(realpath "${{ inputs.bom-path }}")
35+
cd ${{ github.action_path }}
36+
echo "Running Unmanaged Dependency Check against ${bom_absolute_path}"
37+
unmanaged_dependencies=$(mvn exec:java -Dexec.args="../pom.xml ${bom_absolute_path}" -q)
38+
if [[ "${unmanaged_dependencies}" != "[]" ]]; then
39+
echo "This pull request seems to add new third-party dependency, ${unmanaged_dependencies}, among the artifacts listed in ${{ inputs.bom-path }}."
40+
echo "Please see go/cloud-sdk-java-dependency-governance."
41+
exit 1
42+
fi
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<groupId>com.google.cloud</groupId>
7+
<artifactId>unmanaged-dependency-check</artifactId>
8+
<version>0.0.1-SNAPSHOT</version>
9+
<name>Unmanaged dependency check</name>
10+
<description>
11+
12+
</description>
13+
14+
<properties>
15+
<maven.compiler.source>1.8</maven.compiler.source>
16+
<maven.compiler.target>1.8</maven.compiler.target>
17+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18+
</properties>
19+
20+
<build>
21+
<plugins>
22+
<plugin>
23+
<groupId>org.codehaus.mojo</groupId>
24+
<artifactId>exec-maven-plugin</artifactId>
25+
<version>3.1.1</version>
26+
<executions>
27+
<execution>
28+
<goals>
29+
<goal>java</goal>
30+
</goals>
31+
</execution>
32+
<execution>
33+
<!-- run the shell script to install test poms so the tests can be executed -->
34+
<id>install-test-poms</id>
35+
<phase>test-compile</phase>
36+
<goals>
37+
<goal>exec</goal>
38+
</goals>
39+
<configuration>
40+
<executable>bash</executable>
41+
<arguments>
42+
<argument>local-install.sh</argument>
43+
</arguments>
44+
<workingDirectory>src/test/resources</workingDirectory>
45+
</configuration>
46+
</execution>
47+
</executions>
48+
<configuration>
49+
<mainClass>com.google.cloud.UnmanagedDependencyCheck</mainClass>
50+
</configuration>
51+
</plugin>
52+
</plugins>
53+
</build>
54+
55+
<dependencies>
56+
<dependency>
57+
<groupId>com.google.cloud.tools</groupId>
58+
<artifactId>dependencies</artifactId>
59+
<version>1.5.13</version>
60+
</dependency>
61+
<dependency>
62+
<groupId>junit</groupId>
63+
<artifactId>junit</artifactId>
64+
<version>4.13.2</version>
65+
<scope>test</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>com.google.truth</groupId>
69+
<artifactId>truth</artifactId>
70+
<version>1.1.5</version>
71+
<scope>test</scope>
72+
</dependency>
73+
<dependency>
74+
<groupId>org.slf4j</groupId>
75+
<artifactId>slf4j-api</artifactId>
76+
<version>2.0.9</version>
77+
</dependency>
78+
<dependency>
79+
<groupId>org.slf4j</groupId>
80+
<artifactId>slf4j-simple</artifactId>
81+
<version>2.0.9</version>
82+
</dependency>
83+
</dependencies>
84+
85+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.google.cloud;
2+
3+
import static com.google.common.base.Preconditions.checkArgument;
4+
5+
import com.google.cloud.tools.opensource.classpath.ClassPathBuilder;
6+
import com.google.cloud.tools.opensource.classpath.DependencyMediation;
7+
import com.google.cloud.tools.opensource.dependencies.Bom;
8+
import com.google.cloud.tools.opensource.dependencies.MavenRepositoryException;
9+
import java.nio.file.Paths;
10+
import java.util.HashSet;
11+
import java.util.List;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
14+
import org.eclipse.aether.artifact.Artifact;
15+
import org.eclipse.aether.version.InvalidVersionSpecificationException;
16+
17+
/**
18+
* A utility class to check unmanaged dependencies in BOM.
19+
*/
20+
public class UnmanagedDependencyCheck {
21+
// regex of handwritten artifacts
22+
private final static String downstreamArtifact = "(com.google.cloud:google-cloud-.*)|(com.google.api.grpc:(grpc|proto)-google-cloud-.*)";
23+
24+
25+
/**
26+
* @param args An array with two elements.<p> The first string is the path of Java shared
27+
* dependencies BOM. <p> The second string is the path of a pom.xml contains BOM.
28+
*/
29+
public static void main(String[] args)
30+
throws MavenRepositoryException, InvalidVersionSpecificationException {
31+
checkArgument(args.length == 2, "The length of the inputs should be 2");
32+
System.out.println(getUnmanagedDependencies(args[0], args[1]));
33+
}
34+
35+
/**
36+
* Returns dependency coordinates that are not managed by shared dependency BOM.
37+
*
38+
* @param sharedDependenciesBomPath the path of shared dependency BOM
39+
* @param projectBomPath the path of current project BOM
40+
* @return a list of unmanaged dependencies by the given version of shared dependency BOM
41+
* @throws MavenRepositoryException thrown if the artifacts in Bom can't be reached in remote or
42+
* local Maven repository
43+
* @throws InvalidVersionSpecificationException thrown if the shared dependency version can't be
44+
* parsed
45+
*/
46+
public static List<String> getUnmanagedDependencies(
47+
String sharedDependenciesBomPath, String projectBomPath)
48+
throws MavenRepositoryException, InvalidVersionSpecificationException {
49+
Set<String> sharedDependencies = getManagedDependencies(sharedDependenciesBomPath);
50+
Set<String> managedDependencies = getManagedDependencies(projectBomPath);
51+
52+
return managedDependencies.stream()
53+
.filter(dependency -> !sharedDependencies.contains(dependency))
54+
// handwritten artifacts, e.g., com.google.cloud:google-cloud-bigtable, should be excluded.
55+
.filter(dependency -> !dependency.matches(downstreamArtifact))
56+
.collect(Collectors.toList());
57+
}
58+
59+
private static Set<String> getManagedDependencies(String projectBomPath)
60+
throws MavenRepositoryException, InvalidVersionSpecificationException {
61+
return getManagedDependenciesFromBom(Bom.readBom(Paths.get(projectBomPath)));
62+
}
63+
64+
private static Set<String> getManagedDependenciesFromBom(Bom bom)
65+
throws InvalidVersionSpecificationException {
66+
Set<String> res = new HashSet<>();
67+
new ClassPathBuilder()
68+
.resolve(bom.getManagedDependencies(), true, DependencyMediation.MAVEN)
69+
.getClassPath()
70+
.forEach(
71+
classPath -> {
72+
Artifact artifact = classPath.getArtifact();
73+
res.add(String.format("%s:%s", artifact.getGroupId(), artifact.getArtifactId()));
74+
});
75+
76+
return res;
77+
}
78+
79+
private UnmanagedDependencyCheck() {
80+
throw new IllegalStateException("Utility class");
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.google.cloud;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import com.google.cloud.tools.opensource.dependencies.MavenRepositoryException;
7+
import com.google.common.collect.ImmutableList;
8+
import java.util.List;
9+
import org.eclipse.aether.version.InvalidVersionSpecificationException;
10+
import org.junit.Test;
11+
12+
public class UnmanagedDependencyCheckTest {
13+
@Test
14+
public void getUnmanagedDependencyFromSamePomTest()
15+
throws MavenRepositoryException, InvalidVersionSpecificationException {
16+
String sharedDependenciesBom = "src/test/resources/shared-dependency-3.18.0-pom.xml";
17+
List<String> unManagedDependencies =
18+
UnmanagedDependencyCheck.getUnmanagedDependencies(sharedDependenciesBom, sharedDependenciesBom);
19+
assertTrue(unManagedDependencies.isEmpty());
20+
}
21+
22+
@Test
23+
public void getUnmanagedDependencyFromHWBomTest()
24+
throws MavenRepositoryException, InvalidVersionSpecificationException {
25+
List<String> unManagedDependencies =
26+
UnmanagedDependencyCheck.getUnmanagedDependencies(
27+
"src/test/resources/shared-dependency-3.18.0-pom.xml", "src/test/resources/bigtable-pom.xml");
28+
assertTrue(unManagedDependencies.isEmpty());
29+
}
30+
31+
@Test
32+
public void getUnmanagedDependencyFromNestedPomTest()
33+
throws MavenRepositoryException, InvalidVersionSpecificationException {
34+
List<String> unManagedDependencies =
35+
UnmanagedDependencyCheck.getUnmanagedDependencies(
36+
"src/test/resources/shared-dependency-3.18.0-pom.xml", "src/test/resources/transitive-dependency-pom.xml");
37+
assertThat(unManagedDependencies)
38+
.containsAtLeastElementsIn(ImmutableList.of("com.h2database:h2"));
39+
// test dependency should be ignored.
40+
assertThat(unManagedDependencies)
41+
.doesNotContain(ImmutableList.of("com.mysql:mysql-connector-j"));
42+
}
43+
}

0 commit comments

Comments
 (0)