diff --git a/.github/scripts/test_dependency_compatibility.sh b/.github/scripts/test_dependency_compatibility.sh new file mode 100755 index 0000000000..f2a5ae638e --- /dev/null +++ b/.github/scripts/test_dependency_compatibility.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# This script generates a maven command to test unit and integration tests for +# the repo. The outputted maven command will be in the rough following format +# `mvn verify ... -D{dependency.name}.version={dependency.version]`. The variables +# ${dependency.name} and ${dependency.version} are parsed from the input to the script. +# +# Default invocation ./.github/scripts/test_dependency_compatibility.sh will use the default +# upper-bounds dependency file at the root of the repo. +# There are two potential inputs to the script: +# 1. -f {file}: Custom file/path for the upper-bound dependencies to test +# 2. -l {deps_list}: Comma-separated list of dependencies to test (e.g. protobuf=4.31.0,guava=33.4.8-jre) +# Note: Do not include the `-D` prefix or `.version` suffix. Those values will be appended when generating +# the maven command. +# +# If both inputs are supplied, the deps_list input has precedence. For Github Actions workflow, +# the default workflow will run with the upper-bounds file. A `workflow_dispatch` option takes in +# an input for the deps_list to manually run a subset of dependencies. +# +# The default upper-bound dependencies file is `dependencies.txt` located in the root +# of sdk-platform-java. The upper-bound dependencies file will be in the format of: +# ${dependency.name}=${dependency.version} + +set -ex + +function print_help() { + echo "Unexpected input argument for this script." + echo "Use -f {file} for the directory of the upper-bound dependencies file." + echo "Use -l {deps_list} for a comma-separated list of dependencies to test (Format: dep1=1.0,dep2=2.0)" +} + +# Function to parse a dependency string and append it to the Maven command +function add_dependency_to_maven_command() { + local dep_pair=$1 + if [[ ! "${dep_pair}" =~ .*=.* ]]; then + echo "Malformed dependency string: ${dep_pair}. Expected format: dependency=version" + exit 1 + fi + local dependency=$(echo "${dep_pair}" | cut -d'=' -f1 | tr -d '[:space:]') + local version=$(echo "${dep_pair}" | cut -d'=' -f2 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + MAVEN_COMMAND+=" -D${dependency}.version=${version}" +} + +# Default to the upper bounds file in the root of the repo +file='dependencies.txt' +dependency_list='' + +# The colon (:) after the letter means that there is an input associated with the flag +while getopts 'f:l:' flag; do + case "${flag}" in + f) file="${OPTARG}" ;; + l) dependency_list="${OPTARG}" ;; + *) print_help && exit 1 + esac +done + +# Error if both the file and deps_list inputs is empty +if [[ -z "${file}" && -z "${dependency_list}" ]]; then + print_help && exit 1 +fi + +MAVEN_COMMAND="mvn verify -Penable-integration-tests -Dclirr.skip -Dcheckstyle.skip -Dfmt.skip -Denforcer.skip " + +# Check if a list of dependencies was provided as an argument. If the list of dependency inputted +# is empty, then run with the upper-bound dependencies file +if [ -z "${dependency_list}" ]; then + UPPER_BOUND_DEPENDENCY_FILE=$file + + if [ ! -e "${UPPER_BOUND_DEPENDENCY_FILE}" ]; then + echo "The inputted upper-bound dependency file '${UPPER_BOUND_DEPENDENCY_FILE}' cannot be found" + exit 1 + fi + + # Read the file line by line + while IFS= read -r line; do + # Ignore any comments and blank lines + if [[ "${line}" =~ ^[[:space:]]*# ]] || [[ -z "${line}" ]]; then + continue + fi + add_dependency_to_maven_command "${line}" + done < "${UPPER_BOUND_DEPENDENCY_FILE}" +else # This else block means that a list of dependencies was inputted + # Set the Internal Field Separator (IFS) to a comma. + # This tells 'read' to split the string by commas into an array named DEPS. + # The 'read -ra' command reads the input into an array. + IFS=',' read -ra DEPS <<< "${dependency_list}" + + # Loop through each item in the DEPS array. + for DEP_PAIR in "${DEPS[@]}"; do + # Skip any empty items that might result from trailing commas. + if [ -z "${DEP_PAIR}" ]; then + continue + fi + add_dependency_to_maven_command "${DEP_PAIR}" + done +fi + +# Run the generated maven command to test with the dependency versions +$MAVEN_COMMAND diff --git a/.github/workflows/dependency_compatibility_test.yaml b/.github/workflows/dependency_compatibility_test.yaml new file mode 100644 index 0000000000..f3aaee350c --- /dev/null +++ b/.github/workflows/dependency_compatibility_test.yaml @@ -0,0 +1,72 @@ +name: Dependency Compatibility Test + +on: + pull_request: + branches: + - 'main' + workflow_dispatch: + inputs: + dependencies-list: + description: 'Comma-separated list of dependencies to test (Example format: protobuf=4.31.0,guava=33.4.8-jre). + Do not include the `-D` prefix or `.version` suffix. Those values will be appended when generating + the command. No input (default) will run the the upper-bound dependencies file.' + required: false + default: '' + +jobs: + dependency-compatibility-test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout sdk-platform-java + uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + # The workflow_dispatch event is for team members who want to manually test certain dependencies + version combos + # The normal workflow is not from `workflow_dispatch` and will use the default upper-bounds dependencies file + - name: Determine Inputted Dependencies List + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.dependencies-list != '' }} + run: echo "DEPENDENCIES_LIST=${{ github.event.inputs.dependencies-list }}" >> $GITHUB_ENV + # Install the modules for the rest of the CI + - name: Install sdk-platform-java's modules + # gapic-generator-java requires Java 8 and is irrelevant for this CI + run: mvn -q -B -ntp install --projects '!gapic-generator-java' -Dcheckstyle.skip -Dfmt.skip -DskipTests -Dclirr.skip -T 1C + + # Run in the root module which should test for everything except for showcase + - name: Perform Dependency Compatibility Testing + shell: bash + run: | + if [[ -n "${{ env.DEPENDENCIES_LIST }}" ]]; then + ./.github/scripts/test_dependency_compatibility.sh -l ${{ env.DEPENDENCIES_LIST }} + else + ./.github/scripts/test_dependency_compatibility.sh + fi + # Set up local showcase server to run the showcase ITs + - name: Parse showcase version + working-directory: java-showcase/gapic-showcase + run: echo "SHOWCASE_VERSION=$(mvn help:evaluate -Dexpression=gapic-showcase.version -q -DforceStdout)" >> "$GITHUB_ENV" + - name: Install showcase server + run: | + sudo mkdir -p /usr/src/showcase + sudo chown -R ${USER} /usr/src/ + curl --location https://github.com/googleapis/gapic-showcase/releases/download/v${{env.SHOWCASE_VERSION}}/gapic-showcase-${{env.SHOWCASE_VERSION}}-linux-amd64.tar.gz --output /usr/src/showcase/showcase-${{env.SHOWCASE_VERSION}}-linux-amd64.tar.gz + cd /usr/src/showcase/ + tar -xf showcase-* + ./gapic-showcase run & + cd - + # Run specifically for showcase + - name: Perform Dependency Compatibility Testing (Showcase only) + shell: bash + # Need to cd out of the directory to get the scripts as this step is run inside the java-showcase directory + run: | + if [[ -n "${{ env.DEPENDENCIES_LIST }}" ]]; then + ../.github/scripts/test_dependency_compatibility.sh -l ${{ env.DEPENDENCIES_LIST }} + else + ../.github/scripts/test_dependency_compatibility.sh -f ../dependencies.txt + fi + working-directory: java-showcase diff --git a/dependencies.txt b/dependencies.txt new file mode 100644 index 0000000000..317b88ea1d --- /dev/null +++ b/dependencies.txt @@ -0,0 +1,42 @@ +# This file contains a list of dependencies and their versions to be tested for compatibility. +# The format is key=value, where the key is the dependency name and the value is the version. +# "1P" refers to First-Party dependencies (owned by Google). +# "3P" refers to Third-Party dependencies. + +# 1P Parent-Pom +javax.annotation-api=1.3.2 +grpc=1.74.0 +google.auth=1.37.1 +google.http-client=1.47.1 +gson=2.13.1 +guava=33.4.8-jre +protobuf=4.31.1 +opentelemetry=1.52.0 +errorprone=2.41.0 +j2objc-annotations=3.0.0 +threetenbp=1.7.1 +slf4j=2.0.17 + +# 1P Shared-Deps +grpc-gcp=1.6.1 +google.oauth-client=1.39.0 +google.api-client=2.8.0 + +# 3P Shared-Deps +threeten-extra=1.8.0 +opencensus=0.31.0 +findbugs=3.0.2 +jackson=2.19.2 +codec=1.19.0 +httpcomponents.httpcore=4.4.16 +httpcomponents.httpclient=4.5.14 +apache-httpclient-5=5.5 +apache-httpcore-5=5.3.4 +perfmark-api=0.27.0 +google.cloud.opentelemetry=0.36.0 +opentelemetry-semconv=1.34.0 +flogger=0.9 +arrow=18.3.0 +dev.cel=0.10.1 +com.google.crypto.tink=1.18.0 +io.opentelemetry.contrib.opentelemetry-gcp-resources=1.48.0-alpha