Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: unmanaged dependency check #2223

Merged
merged 68 commits into from Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
3b7a7a9
feat: add a unmanaged dependency check
JoeWang1127 Oct 30, 2023
0cc007d
feat: add unmanaged dependency check
JoeWang1127 Nov 3, 2023
3b151d3
change parent
JoeWang1127 Nov 3, 2023
49574ec
change source/target
JoeWang1127 Nov 3, 2023
cbd15e3
add private constructor
JoeWang1127 Nov 3, 2023
420e8ec
throw exception if the private constructor is called
JoeWang1127 Nov 4, 2023
b97b883
remove module
JoeWang1127 Nov 4, 2023
7c4ae40
Merge branch 'main' into feat/unmanaged-dependency-check
JoeWang1127 Nov 28, 2023
09f3a0e
add main function
JoeWang1127 Nov 29, 2023
10c4243
add action yaml
JoeWang1127 Nov 29, 2023
e42a382
add ci to check dependencies
JoeWang1127 Nov 29, 2023
e8a6412
delete unused file
JoeWang1127 Nov 29, 2023
2290942
change action path
JoeWang1127 Nov 29, 2023
74f69b2
change working directory
JoeWang1127 Nov 29, 2023
39c5e87
install modules
JoeWang1127 Nov 29, 2023
73d79f3
parse version of shared-dependencies
JoeWang1127 Nov 29, 2023
ae9b747
upgrade checkout action
JoeWang1127 Nov 29, 2023
ec17df1
Merge branch 'main' into feat/unmanaged-dependency-check
JoeWang1127 Nov 30, 2023
22f99ff
add error exit
JoeWang1127 Nov 30, 2023
df34a0e
add an unmanaged dependency
JoeWang1127 Nov 30, 2023
8b1e836
surpress mvn output
JoeWang1127 Nov 30, 2023
ff4a502
add slf4j
JoeWang1127 Nov 30, 2023
5ba8ce8
add echo to debug
JoeWang1127 Nov 30, 2023
8cacf76
checkout branch head
JoeWang1127 Nov 30, 2023
abd704e
Merge branch 'main' into feat/unmanaged-dependency-check
JoeWang1127 Dec 1, 2023
a952d7a
add an unit test
JoeWang1127 Dec 1, 2023
e404f90
add an execution
JoeWang1127 Dec 1, 2023
f15ecd9
change phase
JoeWang1127 Dec 1, 2023
f72ad9f
use ClassPathBuilder
JoeWang1127 Dec 2, 2023
df445ba
restore BOM
JoeWang1127 Dec 2, 2023
3feb3e9
fix shared dependency version
JoeWang1127 Dec 2, 2023
d3537f9
change check's description
JoeWang1127 Dec 19, 2023
5df6a31
change version
JoeWang1127 Dec 19, 2023
1170018
run unit tests in ci
JoeWang1127 Dec 19, 2023
79530e8
change error message
JoeWang1127 Dec 19, 2023
6590740
Merge branch 'main' into feat/unmanaged-dependency-check
JoeWang1127 Dec 19, 2023
81c23f0
only run tests in unmanaged dependency check module
JoeWang1127 Dec 19, 2023
c230765
change install command
JoeWang1127 Dec 19, 2023
6a1e3e5
add a debug echo
JoeWang1127 Dec 19, 2023
3dd389e
change exec path
JoeWang1127 Dec 19, 2023
4300d70
do not install check
JoeWang1127 Dec 20, 2023
a17d693
change working dir
JoeWang1127 Dec 20, 2023
cd64141
print mvn log
JoeWang1127 Dec 20, 2023
b28ac5e
install modules
JoeWang1127 Dec 20, 2023
a526ab0
modify ut
JoeWang1127 Dec 20, 2023
99baeaa
add javadoc
JoeWang1127 Dec 20, 2023
4c0b1c6
use github.action_path
JoeWang1127 Dec 20, 2023
c92525d
change pom path
JoeWang1127 Dec 20, 2023
0723511
change command sequence
JoeWang1127 Dec 20, 2023
11bb40e
exclude handwritten artifacts
JoeWang1127 Jan 2, 2024
8c39284
retrieve latest shared dependencies using git tag
JoeWang1127 Jan 2, 2024
20fcebe
add a variable
JoeWang1127 Jan 2, 2024
9b6d3ae
debug
JoeWang1127 Jan 2, 2024
b01b0f0
combine steps
JoeWang1127 Jan 2, 2024
67310bd
use shared dependencies bom path
JoeWang1127 Jan 2, 2024
ae72da2
install pom in test
JoeWang1127 Jan 2, 2024
0f35e6a
revert depdendency
JoeWang1127 Jan 2, 2024
3b276df
install shared dependencies in action.yaml
JoeWang1127 Jan 2, 2024
f47bb73
add a tag for the check
JoeWang1127 Jan 2, 2024
3f182fa
Merge branch 'main' into feat/unmanaged-dependency-check
JoeWang1127 Jan 2, 2024
6c59374
refactor
JoeWang1127 Jan 3, 2024
abfe946
restore public
JoeWang1127 Jan 3, 2024
ac23cf1
use artifact instead of toString
JoeWang1127 Jan 3, 2024
cce1d69
change error message
JoeWang1127 Jan 3, 2024
d06099b
change variable name
JoeWang1127 Jan 3, 2024
38b3ee4
remove main method
JoeWang1127 Jan 3, 2024
5060033
Revert "remove main method"
JoeWang1127 Jan 3, 2024
37a5837
change description
JoeWang1127 Jan 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yaml
Expand Up @@ -299,3 +299,25 @@ jobs:
with:
bom-path: gapic-generator-java-bom/pom.xml

unmanaged_dependency_check:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-java@v3
with:
java-version: 11
distribution: temurin
- run: mvn -version
- name: Unit Tests
run: |
mvn test --batch-mode --no-transfer-progress
working-directory: java-shared-dependencies/unmanaged-dependency-check
- name: Install Maven modules
run: |
mvn install -B -ntp -DskipTests -Dclirr.skip -Dcheckstyle.skip
- name: Unmanaged dependency check
uses: ./java-shared-dependencies/unmanaged-dependency-check
with:
bom-path: gapic-generator-java-bom/pom.xml
11 changes: 8 additions & 3 deletions .github/workflows/create_additional_release_tag.yaml
Expand Up @@ -14,16 +14,14 @@ jobs:
uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Git
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"

- name: Fetch all tags
run: git fetch --tags

- name: Create additional tags
bash: shell
run: |
ARTIFACT_IDS=('google-cloud-shared-dependencies' 'api-common' 'gax')
for ARTIFACT_ID in "${ARTIFACT_IDS[@]}"; do
Expand All @@ -36,3 +34,10 @@ jobs:
git tag $TAG_NAME
git push origin $TAG_NAME
done
# Generate a tag for unmanaged dependencies check.
# Use fixed tag so that checks in handwritten libraries do not need to
# update the version.
CHECK_LATEST_TAG="unmanaged-dependencies-check-latest"
git tag ${CHECK_LATEST_TAG}
git push origin -f ${CHECK_LATEST_TAG}

43 changes: 43 additions & 0 deletions java-shared-dependencies/unmanaged-dependency-check/action.yaml
@@ -0,0 +1,43 @@
name: "Unmanaged dependency check"
description: "Checks whether there's a dependency that is not managed by java shared dependencies."
inputs:
bom-path:
description: "The relative path from the repository root to the pom.xml file"
required: true
runs:
using: "composite"
steps:
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
cache: maven
- name: Set up Maven
uses: stCarolas/setup-maven@v4.5
with:
maven-version: 3.8.2
- name: Install latest Java shared dependencies
shell: bash
run: |
cd ${{ github.action_path }}/..
echo "Install Java shared dependencies"
mvn clean install -V --batch-mode --no-transfer-progress -DskipTests
- name: Install check
shell: bash
run: |
cd ${{ github.action_path }}
echo "Install Unmanaged Dependency Check in $(pwd)"
mvn clean install -V --batch-mode --no-transfer-progress -DskipTests
- name: Run unmanaged dependency check
shell: bash
run: |
bom_absolute_path=$(realpath "${{ inputs.bom-path }}")
cd ${{ github.action_path }}
echo "Running Unmanaged Dependency Check against ${bom_absolute_path}"
unmanaged_dependencies=$(mvn exec:java -Dexec.args="../pom.xml ${bom_absolute_path}" -q)
if [[ "${unmanaged_dependencies}" != "[]" ]]; then
echo "This pull request seems to add new third-party dependency, ${unmanaged_dependencies}, among the artifacts listed in ${{ inputs.bom-path }}."
echo "Please see go/cloud-sdk-java-dependency-governance."
exit 1
fi

85 changes: 85 additions & 0 deletions java-shared-dependencies/unmanaged-dependency-check/pom.xml
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.cloud</groupId>
<artifactId>unmanaged-dependency-check</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Unmanaged dependency check</name>
<description>

</description>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
<execution>
<!-- run the shell script to install test poms so the tests can be executed -->
<id>install-test-poms</id>
<phase>test-compile</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>bash</executable>
<arguments>
<argument>local-install.sh</argument>
</arguments>
<workingDirectory>src/test/resources</workingDirectory>
</configuration>
</execution>
</executions>
<configuration>
<mainClass>com.google.cloud.UnmanagedDependencyCheck</mainClass>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>com.google.cloud.tools</groupId>
<artifactId>dependencies</artifactId>
<version>1.5.13</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>

</project>
@@ -0,0 +1,82 @@
package com.google.cloud;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.cloud.tools.opensource.classpath.ClassPathBuilder;
import com.google.cloud.tools.opensource.classpath.DependencyMediation;
import com.google.cloud.tools.opensource.dependencies.Bom;
import com.google.cloud.tools.opensource.dependencies.MavenRepositoryException;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.version.InvalidVersionSpecificationException;

/**
* A utility class to check unmanaged dependencies in BOM.
*/
public class UnmanagedDependencyCheck {
suztomo marked this conversation as resolved.
Show resolved Hide resolved
// regex of handwritten artifacts
private final static String downstreamArtifact = "(com.google.cloud:google-cloud-.*)|(com.google.api.grpc:(grpc|proto)-google-cloud-.*)";


/**
* @param args An array with two elements.<p> The first string is the version of Java shared
JoeWang1127 marked this conversation as resolved.
Show resolved Hide resolved
* dependencies. <p> The second string is the path of a pom.xml contains BOM.
*/
public static void main(String[] args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add Javadoc for the arguments?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think passing the version of Java shared dependencies will be come inconvenient.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users of unmanaged dependency check don't need to pass this parameter.

The version of shared dependencies needs update every two weeks so I think it's fine to set a parameter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As talked yesterday ${{ github.action_path }}../../java-shared-dependencies/pom.xml is convenient.

throws MavenRepositoryException, InvalidVersionSpecificationException {
checkArgument(args.length == 2, "The length of the inputs should be 2");
System.out.println(getUnmanagedDependencies(args[0], args[1]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are good error message for library developers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea. "This pull request seems to be adding a new third-party dependency com.h2.xxx:h2 among the artifacts listed in BOM com.google.cloud:bigtable-bom. Please see go/cloud-sdk-java-dependency-governance. "

}

/**
* Returns dependency coordinates that are not managed by shared dependency BOM.
*
* @param sharedDependenciesBomPath the path of shared dependency BOM
* @param projectBomPath the path of current project BOM
* @return a list of unmanaged dependencies by the given version of shared dependency BOM
* @throws MavenRepositoryException thrown if the artifacts in Bom can't be reached in remote or
* local Maven repository
* @throws InvalidVersionSpecificationException thrown if the shared dependency version can't be
* parsed
*/
public static List<String> getUnmanagedDependencies(
String sharedDependenciesBomPath, String projectBomPath)
throws MavenRepositoryException, InvalidVersionSpecificationException {
Set<String> sharedDependencies = getManagedDependencies(sharedDependenciesBomPath);
Set<String> managedDependencies = getManagedDependencies(projectBomPath);

return managedDependencies.stream()
.filter(dependency -> !sharedDependencies.contains(dependency))
// handwritten artifacts, e.g., com.google.cloud:google-cloud-bigtable, should be excluded.
.filter(dependency -> !dependency.matches(downstreamArtifact))
.collect(Collectors.toList());
}

private static Set<String> getManagedDependencies(String projectBomPath)
throws MavenRepositoryException, InvalidVersionSpecificationException {
return getManagedDependenciesFromBom(Bom.readBom(Paths.get(projectBomPath)));
}

private static Set<String> getManagedDependenciesFromBom(Bom bom)
throws InvalidVersionSpecificationException {
Set<String> res = new HashSet<>();
new ClassPathBuilder()
.resolve(bom.getManagedDependencies(), true, DependencyMediation.MAVEN)
suztomo marked this conversation as resolved.
Show resolved Hide resolved
.getClassPath()
.forEach(
classPath -> {
Artifact artifact = classPath.getArtifact();
res.add(String.format("%s:%s", artifact.getGroupId(), artifact.getArtifactId()));
});

return res;
}

private UnmanagedDependencyCheck() {
throw new IllegalStateException("Utility class");
}
}
@@ -0,0 +1,43 @@
package com.google.cloud;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;

import com.google.cloud.tools.opensource.dependencies.MavenRepositoryException;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.junit.Test;

public class UnmanagedDependencyCheckTest {
suztomo marked this conversation as resolved.
Show resolved Hide resolved
@Test
public void getUnmanagedDependencyFromSamePomTest()
throws MavenRepositoryException, InvalidVersionSpecificationException {
String sharedDependenciesBom = "src/test/resources/shared-dependency-3.18.0-pom.xml";
List<String> unManagedDependencies =
UnmanagedDependencyCheck.getUnmanagedDependencies(sharedDependenciesBom, sharedDependenciesBom);
assertTrue(unManagedDependencies.isEmpty());
}

@Test
public void getUnmanagedDependencyFromHWBomTest()
throws MavenRepositoryException, InvalidVersionSpecificationException {
List<String> unManagedDependencies =
UnmanagedDependencyCheck.getUnmanagedDependencies(
"src/test/resources/shared-dependency-3.18.0-pom.xml", "src/test/resources/bigtable-pom.xml");
assertTrue(unManagedDependencies.isEmpty());
}

@Test
public void getUnmanagedDependencyFromNestedPomTest()
suztomo marked this conversation as resolved.
Show resolved Hide resolved
throws MavenRepositoryException, InvalidVersionSpecificationException {
List<String> unManagedDependencies =
UnmanagedDependencyCheck.getUnmanagedDependencies(
"src/test/resources/shared-dependency-3.18.0-pom.xml", "src/test/resources/transitive-dependency-pom.xml");
assertThat(unManagedDependencies)
.containsAtLeastElementsIn(ImmutableList.of("com.h2database:h2"));
// test dependency should be ignored.
assertThat(unManagedDependencies)
.doesNotContain(ImmutableList.of("com.mysql:mysql-connector-j"));
}
}