diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..fe9ab59 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,73 @@ +name: 'Setup Java and Dependency Cache' +description: "Configures the build environment and caches Gradle, dependencies, and build outputs." +runs: + using: "composite" + steps: + - name: Set Env + shell: bash + run: | + echo "home=${HOME}" >> "$GITHUB_ENV" + - name: Set up Java + uses: actions/setup-java@9519cf1382ac8dc61ad461f7f7cb45f033220189 + with: + distribution: 'zulu' + java-version: 17 + - name: Disable Gradle Daemon + shell: bash + run: | + mkdir ~/.gradle + + echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties + - name: Gradle Wrapper Cache + id: gradle-wrapper-cache + uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles(format('{0}{1}', github.workspace, '/gradle/wrapper/gradle-wrapper.properties')) }} + - name: Gradle Dependency Cache + id: gradle-dependency-cache + uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 + with: + path: ~/.gradle/caches/modules-2 + key: ${{ runner.os }}-gradle-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/gradle.properties')) }} + restore-keys: | + ${{ runner.os }}-gradle-deps + # This tries to fall back to the build cache from the main branch, while ensuring that + # main branch builds repopulate the cache each time. + - name: Gradle Build Cache Main + id: gradle-build-cache-main + if: github.event.pull_request.head.sha == '' + uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 + with: + path: | + ~/.gradle/caches/build-cache-1 + ~/.gradle/caches/transforms-3 + key: ${{ runner.os }}-gradle-build-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-gradle-build + - name: Gradle Build Cache Pull Request + id: gradle-build-cache-pr + if: github.event.pull_request.head.sha != '' + uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09 + with: + path: | + ~/.gradle/caches/build-cache-1 + ~/.gradle/caches/transforms-3 + key: ${{ runner.os }}-gradle-build-${{ github.event.pull_request.base.sha }} + restore-keys: | + ${{ runner.os }}-gradle-build + - name: Download Gradle + if: steps.gradle-wrapper-cache.outputs.cache-hit != 'true' + shell: bash + run: | + ./gradlew --version + - name: Download Gradle Dependencies + if: steps.gradle-dependency-cache.outputs.cache-hit != 'true' + shell: bash + run: | + ./gradlew dependencies :lib:dependencies + - name: Compile + if: steps.gradle-build-cache-main.outputs.cache-hit != 'true' && steps.gradle-build-cache-pr.outputs.cache-hit != 'true' + shell: bash + run: | + ./gradlew assemble testClasses diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 0000000..db04912 --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,109 @@ +# Expected secrets +# MAVEN_CENTRAL_USERNAME - Username for Maven Central. +# MAVEN_CENTRAL_PASSWORD - Password for Maven Central. +# MAVEN_SIGNING_KEYRING_FILE_BASE64 - Base64 encoded GPG keyring file. +# MAVEN_SIGNING_KEY_ID - ID for the key in the GPG keyring file. +# MAVEN_SIGNING_PASSWORD - Password for the key in the GPG keyring file. + +name: Deploy Release + +on: + workflow_dispatch: + +concurrency: deploy_release + +jobs: + validate_gradle_wrapper: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + # Gradle Wrapper validation can be flaky + # https://github.com/gradle/wrapper-validation-action/issues/40 + - name: Gradle Wrapper Validation + timeout-minutes: 1 + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + + check_secrets: + environment: deployment + permissions: + contents: read + runs-on: ubuntu-latest + outputs: + has-secrets: ${{ steps.check_secrets.outputs.defined }} + steps: + - id: check_secrets + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_SIGNING_KEYRING_FILE_BASE64: ${{ secrets.MAVEN_SIGNING_KEYRING_FILE_BASE64 }} + MAVEN_SIGNING_KEY_ID: ${{ secrets.MAVEN_SIGNING_KEY_ID }} + MAVEN_SIGNING_PASSWORD: ${{ secrets.MAVEN_SIGNING_PASSWORD }} + if: "${{ env.MAVEN_CENTRAL_USERNAME != '' && env.MAVEN_CENTRAL_PASSWORD != '' && env.MAVEN_SIGNING_KEYRING_FILE_BASE64 != '' && env.MAVEN_SIGNING_KEY_ID != '' && env.MAVEN_SIGNING_PASSWORD != '' }}" + run: echo "::set-output name=defined::true" + + deploy_release: + environment: deployment + if: needs.check_secrets.outputs.has-secrets == 'true' + needs: [validate_gradle_wrapper, check_secrets] + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - name: Setup + id: setup + timeout-minutes: 30 + uses: ./.github/actions/setup + - name: Export Maven Signing Key + env: + MAVEN_SIGNING_KEYRING_FILE_BASE64: ${{ secrets.MAVEN_SIGNING_KEYRING_FILE_BASE64 }} + GPG_KEY_PATH: ${{ format('{0}/keyring.gpg', env.home) }} + shell: bash + run: | + echo ${MAVEN_SIGNING_KEYRING_FILE_BASE64} | base64 --decode > ${GPG_KEY_PATH} + # While not strictly necessary, this sanity checks the build before attempting to upload. + # This adds minimal additional build time, since most of the work is cached and re-used + # in the next step. + - name: Deploy to Maven Local + timeout-minutes: 25 + env: + ORG_GRADLE_PROJECT_IS_SNAPSHOT: false + ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false + run: | + ./gradlew publishToMavenLocal --no-parallel + - name: Deploy to Maven Central + timeout-minutes: 8 + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_IS_SNAPSHOT: false + ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: true + GPG_KEY_PATH: ${{ format('{0}/keyring.gpg', env.home) }} + GPG_KEY_ID: ${{ secrets.MAVEN_SIGNING_KEY_ID }} + GPG_PASSWORD: ${{ secrets.MAVEN_SIGNING_PASSWORD }} + run: | + ./gradlew publish -Psigning.secretKeyRingFile=$GPG_KEY_PATH -Psigning.keyId=$GPG_KEY_ID -Psigning.password=$GPG_PASSWORD --no-parallel + ./gradlew closeAndReleaseRepository --no-parallel + - name: Collect Artifacts + timeout-minutes: 1 + if: ${{ always() }} + env: + ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} + BINARIES_ZIP_PATH: ${{ format('{0}/artifacts/release_binaries.zip', env.home) }} + run: | + mkdir ${ARTIFACTS_DIR_PATH} + + zip -r ${BINARIES_ZIP_PATH} . -i *build/outputs/* + - name: Upload Artifacts + if: ${{ always() }} + uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 + timeout-minutes: 1 + with: + name: Release binaries + path: ~/artifacts diff --git a/.github/workflows/deploy-snapshot.yml b/.github/workflows/deploy-snapshot.yml new file mode 100644 index 0000000..ecc1b3f --- /dev/null +++ b/.github/workflows/deploy-snapshot.yml @@ -0,0 +1,102 @@ +# Expected secrets +# MAVEN_CENTRAL_USERNAME - Username for Maven Central +# MAVEN_CENTRAL_PASSWORD - Password for Maven Central + +# Note that snapshot releases do not require GPG signing + +name: Deploy Snapshot + +on: + workflow_dispatch: + push: + branches: + - master + paths-ignore: + - '.github/ISSUE_TEMPLATE/*' + - '.github/PULL_REQUEST_TEMPLATE.md' + - 'LICENSE' + - 'README.md' + - 'docs/**' + +concurrency: deploy_snapshot + +jobs: + validate_gradle_wrapper: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + # Gradle Wrapper validation can be flaky + # https://github.com/gradle/wrapper-validation-action/issues/40 + - name: Gradle Wrapper Validation + timeout-minutes: 1 + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + + check_secrets: + environment: deployment + permissions: + contents: read + runs-on: ubuntu-latest + outputs: + has-secrets: ${{ steps.check_secrets.outputs.defined }} + steps: + - id: check_secrets + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + if: "${{ env.MAVEN_CENTRAL_USERNAME != '' && env.MAVEN_CENTRAL_PASSWORD != '' }}" + run: echo "::set-output name=defined::true" + + deploy_snapshot: + if: needs.check_secrets.outputs.has-secrets == 'true' + needs: [validate_gradle_wrapper, check_secrets] + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - name: Setup + id: setup + timeout-minutes: 30 + uses: ./.github/actions/setup + # While not strictly necessary, this sanity checks the build before attempting to upload. + # This adds minimal additional build time, since most of the work is cached and re-used + # in the next step. + - name: Deploy to Maven Local + timeout-minutes: 25 + env: + ORG_GRADLE_PROJECT_IS_SNAPSHOT: true + ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false + run: | + ./gradlew publishToMavenLocal --no-parallel + - name: Deploy to Maven Central + timeout-minutes: 8 + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_IS_SNAPSHOT: true + ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false + run: | + ./gradlew publish --no-parallel + - name: Collect Artifacts + timeout-minutes: 1 + if: ${{ always() }} + env: + ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} + BINARIES_ZIP_PATH: ${{ format('{0}/artifacts/snapshot_binaries.zip', env.home) }} + run: | + mkdir ${ARTIFACTS_DIR_PATH} + + zip -r ${BINARIES_ZIP_PATH} . -i *build/outputs/* + - name: Upload Artifacts + if: ${{ always() }} + uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 + timeout-minutes: 1 + with: + name: Snapshot binaries + path: ~/artifacts diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..d9e6b9e --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,147 @@ +name: Pull Request + +on: + pull_request: + paths-ignore: + - '.github/ISSUE_TEMPLATE/*' + - '.github/PULL_REQUEST_TEMPLATE.md' + - 'LICENSE' + - 'README.md' + - 'docs/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate_gradle_wrapper: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + # Gradle Wrapper validation can be flaky + # https://github.com/gradle/wrapper-validation-action/issues/40 + - name: Gradle Wrapper Validation + timeout-minutes: 1 + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + + prime_cache: + runs-on: ubuntu-latest + needs: validate_gradle_wrapper + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - name: Setup + id: setup + timeout-minutes: 15 + uses: ./.github/actions/setup + +# static_analysis_detekt: +# needs: prime_cache +# runs-on: ubuntu-latest +# permissions: +# contents: read +# steps: +# - name: Checkout +# timeout-minutes: 1 +# uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 +# - name: Setup +# id: setup +# timeout-minutes: 5 +# uses: ./.github/actions/setup +# - name: Detekt +# timeout-minutes: 4 +# run: | +# ./gradlew detektAll +# - name: Collect Artifacts +# timeout-minutes: 1 +# if: ${{ always() }} +# env: +# ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} +# REPORTS_ZIP_PATH: ${{ format('{0}/artifacts/static_analysis_detekt.zip', env.home) }} +# run: | +# mkdir ${ARTIFACTS_DIR_PATH} +# +# zip -r ${REPORTS_ZIP_PATH} . -i build/reports/detekt/\* +# - name: Upload Artifacts +# if: ${{ always() }} +# uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 +# timeout-minutes: 1 +# with: +# name: Detekt static analysis results +# path: ~/artifacts + +# static_analysis_ktlint: +# needs: prime_cache +# runs-on: ubuntu-latest +# permissions: +# contents: read +# steps: +# - name: Checkout +# timeout-minutes: 1 +# uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 +# - name: Setup +# id: setup +# timeout-minutes: 5 +# uses: ./.github/actions/setup +# - name: Ktlint +# timeout-minutes: 4 +# run: | +# ./gradlew ktlint +# - name: Collect Artifacts +# timeout-minutes: 1 +# if: ${{ always() }} +# env: +# ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} +# REPORTS_ZIP_PATH: ${{ format('{0}/artifacts/static_analysis_ktlint.zip', env.home) }} +# run: | +# mkdir ${ARTIFACTS_DIR_PATH} +# +# zip -r ${REPORTS_ZIP_PATH} . -i build/reports/ktlint/\* +# - name: Upload Artifacts +# if: ${{ always() }} +# uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 +# timeout-minutes: 1 +# with: +# name: Ktlint static analysis results +# path: ~/artifacts + + test: + needs: prime_cache + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@dcd71f646680f2efd8db4afa5ad64fdcba30e748 + - name: Setup + timeout-minutes: 5 + uses: ./.github/actions/setup + - name: Test + timeout-minutes: 12 + run: | + ./gradlew check + - name: Collect Artifacts + timeout-minutes: 1 + if: ${{ always() }} + env: + ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }} + TEST_RESULTS_ZIP_PATH: ${{ format('{0}/artifacts/test_results.zip', env.home) }} + run: | + mkdir ${ARTIFACTS_DIR_PATH} + + zip -r ${TEST_RESULTS_ZIP_PATH} . -i build/reports/\* \*/build/reports/\* + - name: Upload Artifacts + uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 + timeout-minutes: 1 + if: ${{ always() }} + with: + name: Test results + path: ~/artifacts diff --git a/.github/workflows/unwedge-maven-central.yml b/.github/workflows/unwedge-maven-central.yml new file mode 100644 index 0000000..853ff3d --- /dev/null +++ b/.github/workflows/unwedge-maven-central.yml @@ -0,0 +1,56 @@ +# Although our CI builds should automatically close and release repositories after publishing, sometimes +# this process can fail. This GitHub Action allows a team member to manually unwedge this stuck deployment. + +# Expected secrets +# MAVEN_CENTRAL_USERNAME - Username for Maven Central +# MAVEN_CENTRAL_PASSWORD - Password for Maven Central + +name: Close and release repository + +on: + workflow_dispatch: + inputs: + mavenCentralRepository: + description: 'Repository name to close' + required: true + +concurrency: deploy_release + +jobs: + validate_gradle_wrapper: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + # Gradle Wrapper validation can be flaky + # https://github.com/gradle/wrapper-validation-action/issues/40 + - name: Gradle Wrapper Validation + timeout-minutes: 1 + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + + unwedge: + environment: deployment + needs: validate_gradle_wrapper + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + timeout-minutes: 1 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - name: Setup + id: setup + timeout-minutes: 30 + uses: ./.github/actions/setup + - name: Close and release repository + timeout-minutes: 8 + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_IS_SNAPSHOT: true + ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false + run: | + ./gradlew closeAndReleaseRepository --repository="${{ github.event.inputs.mavenCentralRepository }}" --no-parallel