-
Notifications
You must be signed in to change notification settings - Fork 49
License updates #184
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
Merged
Merged
License updates #184
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git c/.asf.yaml i/.asf.yaml new file mode 100644 index 0000000..c971dc3 --- /dev/null +++ i/.asf.yaml @@ -0,0 +1,17 @@ +github: + environments: + source: + required_reviewers: + - id: grails-committers + type: Team + wait_timer: 0 + release: + required_reviewers: + - id: grails-committers + type: Team + wait_timer: 0 + docs: + required_reviewers: + - id: grails-committers + type: Team + wait_timer: 0 diff --git c/.github/workflows/gradle.yml i/.github/workflows/gradle.yml index bd7c396..3011091 100644 --- c/.github/workflows/gradle.yml +++ i/.github/workflows/gradle.yml @@ -24,9 +24,13 @@ on: workflow_dispatch: permissions: packages: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false jobs: - build: - name: "Build (Redis ${{ matrix.redis-version }})" + tests: + if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }} + name: "Test (Redis ${{ matrix.redis-version }})" runs-on: ubuntu-24.04 strategy: matrix: @@ -60,8 +64,8 @@ jobs: REDIS_PORT: 6379 run: ./gradlew build --continue publish_snapshot: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - needs: build + needs: tests + if: ${{ always() && github.repository_owner == 'apache' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (needs.tests.result == 'success' || needs.tests.result == 'skipped') }} runs-on: ubuntu-24.04 permissions: contents: write @@ -80,11 +84,10 @@ jobs: - name: "📤 Publish Snapshot Artifacts" id: publish env: - GITHUB_MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GRAILS_PUBLISH_RELEASE: 'false' - MAVEN_PUBLISH_USERNAME: ${{ secrets.NEXUS_USER }} - MAVEN_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PW }} MAVEN_PUBLISH_URL: ${{ secrets.GRAILS_NEXUS_PUBLISH_SNAPSHOT_URL }} + MAVEN_PUBLISH_USERNAME: ${{ secrets.NEXUS_USER }} + MAVEN_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PW }} run: ./gradlew --no-build-cache publish - name: "🔨 Generate Snapshot Documentation" run: ./gradlew :grails-redis:groovydoc diff --git c/.github/workflows/rat.yml i/.github/workflows/rat.yml index 041bf8e..11a11af 100644 --- c/.github/workflows/rat.yml +++ i/.github/workflows/rat.yml @@ -17,13 +17,11 @@ name: RAT Report on: push: branches: - - '[4-9]+.[0-9]+.x' - - '[3-9]+.[3-9]+.x' + - '[3-9]+.[0-9]+.x' - license-audit pull_request: branches: - - '[4-9]+.[0-9]+.x' - - '[3-9]+.[3-9]+.x' + - '[3-9]+.[0-9]+.x' - license-audit workflow_dispatch: # queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits diff --git c/.github/workflows/release.yml i/.github/workflows/release.yml index b3bf198..775884c 100644 --- c/.github/workflows/release.yml +++ i/.github/workflows/release.yml @@ -20,17 +20,43 @@ on: permissions: contents: write packages: read +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: - release: + publish: + permissions: + packages: read # pre-release workflow + contents: write # to create release + issues: write # to modify milestones runs-on: ubuntu-24.04 + outputs: + release_version: ${{ steps.release_version.outputs.value }} + extract_repository_name: ${{ steps.extract_repository_name.outputs.repository_name }} steps: + - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it + run: curl -s https://api.ipify.org + - name: "Extract repository name" + id: extract_repository_name + run: | + echo "repository_name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT - name: "📥 Checkout repository" uses: actions/checkout@v4 + - name: 'Ensure Common Build Date' # to ensure a reproducible build + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" + - name: "Ensure source files use common date" + run: | + find . -depth \( -type f -o -type d \) -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + + - name: '🔐 Set up GPG' + run: | + echo "${{ secrets.GRAILS_GPG_KEY }}" | gpg --batch --import + gpg --list-keys + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} - name: "☕️ Setup JDK" uses: actions/setup-java@v4 with: - java-version: 17 distribution: liberica + java-version: '17.0.15' # this must be a specific version for reproducible builds - name: "🐘 Setup Gradle" uses: gradle/actions/setup-gradle@v4 with: @@ -40,33 +66,200 @@ jobs: run: echo "release_version=${GITHUB_REF:11}" >> $GITHUB_OUTPUT - name: "⚙️ Run pre-release" uses: apache/grails-github-actions/pre-release@asf - - name: "🔐 Generate key file for artifact signing" env: - SECRING_FILE: ${{ secrets.SECRING_FILE }} - run: echo $SECRING_FILE | base64 -d > ${{ github.workspace }}/secring.gpg - - name: "📤 Publish to Sonatype - close and release staging repository" + RELEASE_VERSION: ${{ steps.release_version.outputs.value }} + - name: "📤 Publish to staging repository" env: - GITHUB_MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GRAILS_PUBLISH_RELEASE: 'true' - NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_USER }} - NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PW }} - NEXUS_PUBLISH_URL: ${{ secrets.GRAILS_NEXUS_PUBLISH_RELEASE_URL }} - NEXUS_PUBLISH_STAGING_PROFILE_ID: ${{ secrets.NEXUS_PUBLISH_STAGING_PROFILE_ID }} # TODO: What about this secret? - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_STAGE_DEPLOYER_USER }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_STAGE_DEPLOYER_PW }} + NEXUS_PUBLISH_URL: 'https://repository.apache.org/service/local/' + NEXUS_PUBLISH_STAGING_PROFILE_ID: ${{ secrets.STAGING_PROFILE_ID }} + NEXUS_PUBLISH_DESCRIPTION: '${{ steps.extract_repository_name.outputs.repository_name }}:${{ steps.release_version.outputs.value }}' + SIGNING_KEY: ${{ secrets.GPG_KEY_ID }} run: > - ./gradlew --refresh-dependencies - -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg + ./gradlew publishToSonatype - closeAndReleaseSonatypeStagingRepository + closeSonatypeStagingRepository + aggregateChecksums + aggregatePublishedArtifacts + - name: "Upload checksums" + uses: softprops/action-gh-release@v2 + with: + files: build/CHECKSUMS.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Upload published artifacts" + uses: softprops/action-gh-release@v2 + with: + files: build/PUBLISHED_ARTIFACTS.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Generate Build Date file" + run: echo "$SOURCE_DATE_EPOCH" >> build/BUILD_DATE.txt + - name: "Upload Build Date file" + uses: softprops/action-gh-release@v2 + with: + files: build/BUILD_DATE.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + source: + # to ensure we never publish any build artifacts, run the source distribution as a separate build workflow + environment: source + name: "Source Distribution" + needs: publish + permissions: + contents: write + runs-on: ubuntu-latest + outputs: + extract_repository_name: ${{ steps.extract_repository_name.outputs.repository_name }} + steps: + - name: "Extract repository name" + id: extract_repository_name + run: | + echo "repository_name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: ${{ github.ref_name }} + path: project + - name: "🗑️ Remove unnecessary files" + run: | + rm -f project/gradle/wrapper/gradle-wrapper.jar + rm -f project/gradle/wrapper/gradle-wrapper.properties + rm -f project/gradlew + rm -f project/.asf.yaml + - name: "Download CHECKSUMS.txt and rename to CHECKSUMS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd project + release_url=$(gh release view ${{ github.ref_name }} --json assets --repo ${{ github.repository }} --jq '.assets[] | select(.name == "CHECKSUMS.txt") | .url') + curl -L -H "Authorization: token $GH_TOKEN" -o CHECKSUMS "$release_url" + - name: "Download PUBLISHED_ARTIFACTS.txt and rename to PUBLISHED_ARTIFACTS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd project + release_url=$(gh release view ${{ github.ref_name }} --json assets --repo ${{ github.repository }} --jq '.assets[] | select(.name == "PUBLISHED_ARTIFACTS.txt") | .url') + curl -L -H "Authorization: token $GH_TOKEN" -o PUBLISHED_ARTIFACTS "$release_url" + - name: "Download BUILD_DATE.txt and rename to BUILD_DATE" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd project + release_url=$(gh release view ${{ github.ref_name }} --json assets --repo ${{ github.repository }} --jq '.assets[] | select(.name == "BUILD_DATE.txt") | .url') + curl -L -H "Authorization: token $GH_TOKEN" -o BUILD_DATE "$release_url" + - name: "Ensure source files use common date" + run: | + SOURCE_DATE_EPOCH=$(cat project/BUILD_DATE) + find . -depth \( -type f -o -type d \) -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + + - name: "📦 Create source distribution ZIP" + run: | + version="${{ github.ref_name }}" + version="${version#v}" # Strip 'v' prefix + zip -r "apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip" project -x 'project/.git/*' -x 'project/.github/*' + - name: '🔐 Set up GPG' + run: | + echo "${{ secrets.GRAILS_GPG_KEY }}" | gpg --batch --import + gpg --list-keys + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + - name: "🔏 Sign source distribution ZIP" + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + run: | + version="${{ github.ref_name }}" + version="${version#v}" # Strip 'v' prefix + gpg --default-key "${GPG_KEY_ID}" --batch --yes --pinentry-mode loopback --armor --detach-sign apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip + - name: "📦 Create source distribution checksum" + run: | + version="${{ github.ref_name }}" + version="${version#v}" # Strip 'v' prefix + sha512sum apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip > "apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip.sha512" + - name: "🚀 Upload ZIP and Signature to GitHub Release" + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + files: | + apache-${{ steps.extract_repository_name.outputs.repository_name }}-*-incubating-src.zip + apache-${{ steps.extract_repository_name.outputs.repository_name }}-*-incubating-src.zip.sha512 + apache-${{ steps.extract_repository_name.outputs.repository_name }}-*-incubating-src.zip.asc + - name: "Remove CHECKSUMS.txt asset from release" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + cd project + gh release --repo ${{ github.repository }} delete-asset ${{ github.ref_name }} CHECKSUMS.txt --yes + - name: "Remove BUILD_DATE.txt asset from release" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + cd project + gh release --repo ${{ github.repository }} delete-asset ${{ github.ref_name }} BUILD_DATE.txt --yes + - name: "Remove PUBLISHED_ARTIFACTS.txt asset from release" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + cd project + gh release --repo ${{ github.repository }} delete-asset ${{ github.ref_name }} PUBLISHED_ARTIFACTS.txt --yes + release: + environment: release + needs: [publish, source] + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + with: + ref: v${{ needs.publish.outputs.release_version }} + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: liberica + java-version: '17.0.15' + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + with: + develocity-access-key: ${{ secrets.GRAILS_DEVELOCITY_ACCESS_KEY }} + - name: "⚙️ Run post-release" + uses: apache/grails-github-actions/post-release@asf + docs: + environment: docs + name: "Publish Documentation" + needs: publish + runs-on: ubuntu-latest + permissions: + contents: write # required for gradle.properties revert + issues: write # required for milestone closing + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + with: + ref: v${{ needs.publish.outputs.release_version }} + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + java-version: '17.0.15' + distribution: liberica + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + with: + develocity-access-key: ${{ secrets.GRAILS_DEVELOCITY_ACCESS_KEY }} - name: "🔨 Build Documentation" run: ./gradlew :grails-redis:groovydoc + env: + GRAILS_PUBLISH_RELEASE: 'true' - name: "🚀 Publish to Github Pages" uses: apache/grails-github-actions/deploy-github-pages@asf env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GRADLE_PUBLISH_RELEASE: 'true' SOURCE_FOLDER: plugin/build/docs - VERSION: ${{ steps.release_version.outputs.release_version }} - - name: "⚙️ Run post-release" - uses: apache/grails-github-actions/post-release@asf + VERSION: ${{ needs.publish.outputs.release_version }} \ No newline at end of file diff --git c/.gitignore i/.gitignore index c37e68c..7105fd0 100644 --- c/.gitignore +++ i/.gitignore @@ -13,3 +13,5 @@ build *.iws *.swp out +!etc/bin +etc/bin/results \ No newline at end of file diff --git c/.sdkmanrc i/.sdkmanrc index 4c173c1..4d12ff3 100644 --- c/.sdkmanrc +++ i/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config - https://sdkman.io/usage#env -java=17.0.14-librca -gradle=8.14.2 \ No newline at end of file +java=17.0.15-librca +gradle=8.14.2 diff --git c/CODE_OF_CONDUCT.md i/CODE_OF_CONDUCT.md index 329203e..9af3eb8 100644 --- c/CODE_OF_CONDUCT.md +++ i/CODE_OF_CONDUCT.md @@ -1,8 +1,6 @@ # Code of Conduct -Apache Grails (Incubating) follows the ASF [Code of -Conduct](https://www.apache.org/foundation/policies/conduct). +Apache Grails (Incubating) follows the ASF [Code of Conduct](https://www.apache.org/foundation/policies/conduct). If you observe behavior that violates those rules please follow the -[ASF reporting -guidelines](https://www.apache.org/foundation/policies/conduct#reporting-guidelines). +[ASF reporting guidelines](https://www.apache.org/foundation/policies/conduct#reporting-guidelines). diff --git c/README.md i/README.md index e20fecb..a6effc1 100644 --- c/README.md +++ i/README.md @@ -19,9 +19,26 @@ limitations under the License. Grails Redis Plugin =================== -For integration between [Redis][redis] and Grails GORM layer, see the [Redis GORM plugin][redisgorm]. +Building +-------------- -That plugin was originally called "redis" (the name of this plugin), but it has since been refactored to "redis-gorm" and now relies on this plugin for connectivity. +To build this project from source, first bootstrap gradle: + + cd gradle-bootstrap + gradle + cd - + +After bootstrap the project, you can build it with the command: + + ./gradlew build + +To run the build only, and skip the tests, run: + + ./gradlew build -PskipTests + +Then publish the jar files to mavenLocal for usage: + + ./gradlew publishToMavenLocal What is Redis? -------------- @@ -46,22 +63,19 @@ What is Jedis? Installation ------------ -# Grails 3 +# Grails 7+ Add the following dependency to build.gradle: ``` dependencies { ... - implementation 'org.grails.plugins:grails-redis:3.0.0' + implementation 'org.apache.grails:grails-redis:5.0.0' } ``` -NOTE: The 2.X is compatible only with Grails 3. - - Out of the box, the plugin expects that Redis is running on `localhost:6379`. You can modify this (as well as any other pool config options) by adding a stanza like this to your `grails-app/conf/Config.groovy` file: grails { @@ -643,7 +657,6 @@ Release Notes Grails 2.x * 1.6.0 - released 11/04/2014 - Changed how `RedisService` is spring injected so that it's easier to mock out for tests by clients. Upgraded to Jedis 2.6.0. * 1.6.2 - released 02/06/2015 - Port and timeout properties injected by external properties file are now converted to Integer. If not integer, then defaults used. -[redisgorm]: http://grails.github.com/inconsequential/redis/ [redis]: http://redis.io [redisgroovy]: http://naleid.com/blog/2010/12/28/intro-to-using-redis-with-groovy/ [slideshareggr]: http://naleid.com/blog/2011/06/27/redis-groovy-and-grails-presentation-at-gr8conf-2011-and-gum/ diff --git c/build.gradle i/build.gradle index 2154873..f900e9f 100644 --- c/build.gradle +++ i/build.gradle @@ -17,28 +17,57 @@ * under the License. */ -// Workaround needed for nexus publishing bug -// version and group must be specified in the root project -// https://github.com/gradle-nexus/publish-plugin/issues/310 -version = projectVersion -group = 'this.will.be.overridden' +import java.time.Instant +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter +subprojects { + +ext { + isReproducibleBuild = System.getenv("SOURCE_DATE_EPOCH") != null + buildInstant = java.util.Optional.ofNullable(System.getenv("SOURCE_DATE_EPOCH")) + .filter(s -> !s.isEmpty()) + .map(Long::parseLong) + .map(Instant::ofEpochSecond) + .orElseGet(Instant::now) + formattedBuildDate = DateTimeFormatter.ISO_INSTANT.format(buildInstant) + buildDate = (buildInstant as Instant).atZone(ZoneOffset.UTC) // for reproducible builds + isCiBuild = System.getenv().get('CI') as Boolean +} allprojects { repositories { mavenCentral() - maven { url = 'https://repo.grails.org/grails/core' } - maven { url = 'https://repository.apache.org/content/repositories/snapshots' } + maven { url = 'https://repo.grails.org/grails/restricted' } + maven { url = 'https://repository.apache.org/content/groups/snapshots' } + maven { + url = 'https://repository.apache.org/content/groups/staging' + content { + includeGroupByRegex 'org[.]apache[.]grails.*' + } + } + } + + configurations.configureEach { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.seleniumhq.selenium') { + details.useVersion('4.25.0') + details.because('Temporary workaround because of https://issues.chromium.org/issues/42323769') + } + } } -} subprojects { - if (name == 'grails-redis') { - // This has to be applied here - apply plugin: 'org.apache.grails.gradle.grails-publish' + configurations.configureEach { + resolutionStrategy { + def cacheHours = isCiBuild || isReproducibleBuild ? 0 : 24 + cacheDynamicVersionsFor(cacheHours, 'hours') + cacheChangingModulesFor(cacheHours, 'hours') + } } } apply { - from rootProject.layout.projectDirectory.file('gradle/rat-root-config.gradle') + from layout.projectDirectory.file('gradle/publish-root-config.gradle') + from layout.projectDirectory.file('gradle/rat-root-config.gradle') } \ No newline at end of file diff --git c/buildSrc/build.gradle i/buildSrc/build.gradle index 97fdd9d..4d08e0c 100644 --- c/buildSrc/build.gradle +++ i/buildSrc/build.gradle @@ -28,13 +28,20 @@ file('../gradle.properties').withInputStream { repositories { mavenCentral() - maven { url = 'https://repo.grails.org/grails/core' } - maven { url = 'https://repository.apache.org/content/repositories/snapshots' } + maven { url = 'https://repo.grails.org/grails/restricted' } + maven { url = 'https://repository.apache.org/content/groups/snapshots' } + maven { + url = 'https://repository.apache.org/content/groups/staging' + content { + includeGroupByRegex 'org[.]apache[.]grails.*' + } + } } dependencies { implementation platform("org.apache.grails:grails-bom:${versions.get('grailsVersion')}") - implementation 'org.apache.grails:grails-gradle-plugins' implementation 'com.bertramlabs.plugins:asset-pipeline-gradle' + implementation 'org.apache.grails:grails-gradle-plugins' implementation "org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:${versions.get('ratVersion')}" + implementation "org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:${versions.get('gradleCryptoChecksumVersion')}" } \ No newline at end of file diff --git c/etc/bin/extract-build-artifact.sh i/etc/bin/extract-build-artifact.sh new file mode 100755 index 0000000..992392b --- /dev/null +++ i/etc/bin/extract-build-artifact.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +set -e + +ARTIFACT_NAME=$1 + +if [ -z "${ARTIFACT_NAME}" ]; then + echo "Usage: $0 <artifact-name>" + exit 1 +fi + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +if [ -z "${SCRIPT_DIR}/results/first/${ARTIFACT_NAME}" ]; then + echo "First Artifact Not found: $ARTIFACT_NAME could not be found under ${SCRIPT_DIR}/results/first/${ARTIFACT_NAME}" + exit 1; +else + echo "First Artifact Found @ ${SCRIPT_DIR}/results/first/${ARTIFACT_NAME}" +fi +if [ -z "${SCRIPT_DIR}/results/second/${ARTIFACT_NAME}" ]; then + echo "Second Artifact Not found: $ARTIFACT_NAME could not be found under ${SCRIPT_DIR}/results/second/${ARTIFACT_NAME}" + exit 1; +else + echo "Second Artifact Found @ ${SCRIPT_DIR}/results/first/${ARTIFACT_NAME}" +fi + +rm -rf "${SCRIPT_DIR}/results/firstArtifact" || true +rm -rf "${SCRIPT_DIR}/results/secondArtifact" || true + +unzip "${SCRIPT_DIR}/results/first/${ARTIFACT_NAME}" -d "${SCRIPT_DIR}/results/firstArtifact" +unzip "${SCRIPT_DIR}/results/second/${ARTIFACT_NAME}" -d "${SCRIPT_DIR}/results/secondArtifact" diff --git c/etc/bin/generate-build-artifact-hashes.groovy i/etc/bin/generate-build-artifact-hashes.groovy new file mode 100755 index 0000000..43a148f --- /dev/null +++ i/etc/bin/generate-build-artifact-hashes.groovy @@ -0,0 +1,84 @@ +#!/usr/bin/env groovy +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import java.nio.file.* +import java.security.MessageDigest + +// --------------------------------------------------------------------------- +String sha512(Path file) { + MessageDigest md = MessageDigest.getInstance('SHA-512') + file.withInputStream { is -> + byte[] buf = new byte[8192] + for (int r = is.read(buf); r > 0; r = is.read(buf)) + md.update(buf, 0, r) + } + md.digest().collect { String.format('%02x', it) }.join() +} + +Path scriptDir = Paths.get(getClass() + .protectionDomain + .codeSource + .location + .toURI()) + .toAbsolutePath() + .parent + + +Path root = scriptDir.resolve('..').resolve('..').normalize() +if(args && args.length > 0) { + System.out.println("Finding jars in: ${args[0]}" as String) + root = Paths.get(args[0]).toAbsolutePath().normalize() +} + +// --------------------------------------------------------------------------- +// Decide where to search: project root by default, or user-supplied path +// (absolute or relative to project root) when an argument is given. +Path scanRoot +if (this.args && this.args.length > 0) { + Path argPath = Paths.get(this.args[0]) + scanRoot = argPath.isAbsolute() ? argPath : root.resolve(argPath).normalize() + if (!Files.exists(scanRoot)) { + System.err.println "❌ Path '${scanRoot}' does not exist." + System.exit(1) + } +} else { + scanRoot = root +} +List<Path> artifacts = [] +Files.walk(scanRoot) + .filter { + Files.isRegularFile(it) && + !it.toString().contains("buildSrc") && + !it.toString().contains("etc") && + it.toString().endsWith('.jar') && + it.toString().contains("${File.separator}build${File.separator}libs${File.separator}" as String) + } + .forEach { artifacts << it } + +artifacts.findAll { + !it.toString().contains("${File.separator}buildSrc${File.separator}" as String) // build src jars aren't published + !it.toString().contains("${File.separator}examples${File.separator}" as String) // test examples aren't published +}.sort { a, b -> a.toString() <=> b.toString() +}.collect { Path jar -> + String hash = sha512(jar) + String relative = root.relativize(jar).toString() + "${relative} ${hash}" +}.sort().each { + println it +} \ No newline at end of file diff --git c/etc/bin/test-reproducible-builds.sh i/etc/bin/test-reproducible-builds.sh new file mode 100755 index 0000000..dca48ff --- /dev/null +++ i/etc/bin/test-reproducible-builds.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This file assumes the gnu version of coreutils is installed, which is not installed by default on a mac +set -e + +export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) + +CWD=$(pwd) +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "${SCRIPT_DIR}/../.." + +rm -rf "${SCRIPT_DIR}/results" || true +mkdir -p "${SCRIPT_DIR}/results" + +git clean -xdf --exclude='etc/bin' --exclude='.idea' --exclude='.gradle' +killall -e java || true +./gradlew build --rerun-tasks -PskipTests --no-build-cache +"${SCRIPT_DIR}/generate-build-artifact-hashes.groovy" > "${SCRIPT_DIR}/results/first.txt" +mkdir -p "${SCRIPT_DIR}/results/first" +find . -path ./etc -prune -o -type f -path '*/build/libs/*.jar' -print0 | xargs -0 cp --parents -t "${SCRIPT_DIR}/results/first/" + +git clean -xdf --exclude='etc/bin' --exclude='.idea' --exclude='.gradle' +killall -e java || true +./gradlew build --rerun-tasks -PskipTests --no-build-cache +"${SCRIPT_DIR}/generate-build-artifact-hashes.groovy" > "${SCRIPT_DIR}/results/second.txt" +mkdir -p "${SCRIPT_DIR}/results/second" +find . -path ./etc -prune -o -type f -path '*/build/libs/*.jar' -print0 | xargs -0 cp --parents -t "${SCRIPT_DIR}/results/second/" + +cd "${SCRIPT_DIR}/results" + +# diff -u first.txt second.txt +DIFF_RESULTS=$(comm -3 first.txt second.txt | cut -d' ' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | uniq | sort) +echo "Differing artifacts:" +echo "$DIFF_RESULTS" > diff.txt +cat diff.txt + +printf '%s\n' "$DIFF_RESULTS" | sed 's|^etc/bin/results/||' > toPurge.txt +find first -type f -name '*.jar' -print | sed 's|^first/||' | grep -F -x -v -f toPurge.txt | + while IFS= read -r f; do + rm -f "./first/$f" + done +find second -type f -name '*.jar' -print | sed 's|^second/||' | grep -F -x -v -f toPurge.txt | + while IFS= read -r f; do + rm -f "./second/$f" + done +rm toPurge.txt +find . -type d -empty -delete +cd "$CWD" diff --git c/gradle-bootstrap/build.gradle i/gradle-bootstrap/build.gradle new file mode 100644 index 0000000..38b2cea --- /dev/null +++ i/gradle-bootstrap/build.gradle @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +def props = new Properties() +project.rootProject.layout.projectDirectory.file('../.sdkmanrc').asFile.withInputStream { + props.load(it) +} +tasks.withType(Wrapper).configureEach { + gradleVersion = props.gradle +} + +defaultTasks 'bootstrap' +tasks.register('bootstrap') { + dependsOn 'wrapper' + doLast { + ant.copy file: "${projectDir}/gradlew", todir: "${projectDir}/../grails-forge" + ant.copy file: "${projectDir}/gradlew.bat", todir: "${projectDir}/../grails-forge" + ant.copy(todir: "${projectDir}/../grails-forge/gradle/wrapper") { + fileset(dir: "${projectDir}/gradle/wrapper") { + include(name: 'gradle-wrapper.jar') + include(name: 'gradle-wrapper.properties') + } + } + ant.chmod(file: "${projectDir}/../grails-forge/gradlew", perm: '755') + ant.chmod(file: "${projectDir}/../grails-forge/gradlew.bat", perm: '755') + + ant.copy file: "${projectDir}/gradlew", todir: "${projectDir}/../grails-gradle" + ant.copy file: "${projectDir}/gradlew.bat", todir: "${projectDir}/../grails-gradle" + ant.copy(todir: "${projectDir}/../grails-gradle/gradle/wrapper") { + fileset(dir: "${projectDir}/gradle/wrapper") { + include(name: 'gradle-wrapper.jar') + include(name: 'gradle-wrapper.properties') + } + } + ant.chmod(file: "${projectDir}/../grails-gradle/gradlew", perm: '755') + ant.chmod(file: "${projectDir}/../grails-gradle/gradlew.bat", perm: '755') + + ant.move file: "${projectDir}/gradlew", todir: "${projectDir}/../" + ant.move file: "${projectDir}/gradlew.bat", todir: "${projectDir}/../" + ant.move file: "${projectDir}/gradle/wrapper", todir: "${projectDir}/../gradle" + ant.chmod(file: "${projectDir}/../gradlew", perm: '755') + ant.chmod(file: "${projectDir}/../gradlew.bat", perm: '755') + } +} diff --git c/gradle-bootstrap/settings.gradle i/gradle-bootstrap/settings.gradle new file mode 100644 index 0000000..7b9c8b3 --- /dev/null +++ i/gradle-bootstrap/settings.gradle @@ -0,0 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + diff --git c/gradle.properties i/gradle.properties index af0a723..21220f7 100644 --- c/gradle.properties +++ i/gradle.properties @@ -15,8 +15,9 @@ projectVersion=5.0.0-SNAPSHOT -grailsVersion=7.0.0-SNAPSHOT +grailsVersion=7.0.0-M4 javaVersion=17 +gradleCryptoChecksumVersion=1.4.0 ratVersion=0.8.1 # This prevents the Grails Gradle Plugin from unnecessarily excluding slf4j-simple in the generated POMs diff --git c/gradle/docs-config.gradle i/gradle/docs-config.gradle index c07671a..14765a7 100644 --- c/gradle/docs-config.gradle +++ i/gradle/docs-config.gradle @@ -18,13 +18,31 @@ */ configurations.register('documentation') { - extendsFrom(configurations.compileClasspath) + canBeConsumed = false + canBeResolved = true + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY)) + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL)) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + } } dependencies { + add('documentation', platform("org.apache.grails:grails-bom:$grailsVersion")) add('documentation', 'com.github.javaparser:javaparser-core') + add('documentation', 'org.apache.groovy:groovy') + add('documentation', 'org.apache.groovy:groovy-groovydoc') + add('documentation', 'org.apache.groovy:groovy-ant') + add('documentation', 'org.apache.groovy:groovy-docgenerator') + add('documentation', 'org.apache.groovy:groovy-templates') } -tasks.named('groovydoc', Groovydoc) { - groovyClasspath += configurations.documentation -} \ No newline at end of file +tasks.withType(Groovydoc).configureEach { + it.access = GroovydocAccess.PROTECTED + it.processScripts = false + it.includeMainForScripts = false + it.includeAuthor = false + it.noTimestamp = true + it.noVersionStamp = false + it.groovyClasspath += configurations.documentation +} diff --git c/gradle/examples-config.gradle i/gradle/examples-config.gradle index 8e1169e..e256f6d 100644 --- c/gradle/examples-config.gradle +++ i/gradle/examples-config.gradle @@ -19,4 +19,16 @@ tasks.withType(Groovydoc).configureEach { enabled = false +} + +// Workaround for parallel builds due to https://github.com/bertramdev/asset-pipeline/issues/177 +if ('assetCompile' in tasks.names) { + tasks.named('assetCompile') { + outputs.dir(rootProject.layout.buildDirectory.dir('asset-serialize')) + } +} + +grails { + // turn off dependency management so we *know* we're testing with the right versions and not from a remote repository + springDependencyManagement = false } \ No newline at end of file diff --git c/gradle/java-config.gradle i/gradle/java-config.gradle index 297dbd1..d21715c 100644 --- c/gradle/java-config.gradle +++ i/gradle/java-config.gradle @@ -17,4 +17,25 @@ * under the License. */ -compileJava.options.release = javaVersion.toInteger() \ No newline at end of file +compileJava.options.release = javaVersion.toInteger() + +extensions.configure(JavaPluginExtension) { + it.withJavadocJar() + it.withSourcesJar() +} + +tasks.withType(Javadoc).configureEach { Javadoc it -> + it.options.noTimestamp true // prevent the file header with the date + it.options.bottom "Generated ${formattedBuildDate} (UTC)" +} + +// JavaCompile is not configured because we put java files inside of the groovy source sets + +tasks.withType(GroovyCompile).configureEach { + groovyOptions.encoding = 'UTF-8' // encoding needs to be the same since it's different across platforms + // Preserve method parameter names in Groovy classes for IDE parameter hints. + groovyOptions.parameters = true + options.encoding = 'UTF-8' // encoding needs to be the same since it's different across platforms + options.fork = true + options.forkOptions.jvmArgs = ['-Xms128M', '-Xmx2G'] +} \ No newline at end of file diff --git c/gradle/publish-config.gradle i/gradle/publish-config.gradle index 2c35d3f..bc2afc2 100644 --- c/gradle/publish-config.gradle +++ i/gradle/publish-config.gradle @@ -17,6 +17,7 @@ * under the License. */ +import org.gradle.crypto.checksum.Checksum import org.grails.gradle.plugin.publishing.GrailsPublishExtension extensions.configure(GrailsPublishExtension) { @@ -41,3 +42,82 @@ extensions.configure(GrailsPublishExtension) { puneetbehl: 'Puneet Behl' ] } + + +afterEvaluate { + if (project.plugins.hasPlugin('signing')) { + if(System.getenv('TEST_BUILD_REPRODUCIBLE')) { + project.logger.lifecycle("Signing is disabled for this build to test build reproducibility.") + project.tasks.withType(Sign).configureEach { + it.enabled = false + } + } + } + if (project.plugins.hasPlugin("maven-publish")) { + def checksumTask = tasks.register("publishedChecksums", Checksum) + checksumTask.configure { Checksum check -> + check.checksumAlgorithm = Checksum.Algorithm.SHA512 + check.outputDirectory.set(project.layout.buildDirectory.dir("checksums")) + check.dependsOn(tasks.withType(Jar)) + } + + def artifactsDir = project.layout.buildDirectory.dir("artifacts") + def artifactsTask = tasks.register("savePublishedArtifacts") { + it.outputs.dir(artifactsDir) + it.dependsOn(tasks.withType(Jar)) + } + + gradle.taskGraph.whenReady { + List filesToChecksum = [] + publishing.publications.withType(MavenPublication).all { MavenPublication publication -> + publication.artifacts.each { MavenArtifact artifact -> + if(artifact.file.name == 'grails-plugin.xml' || artifact.file.name == 'profile.yml') { + return + } + filesToChecksum << artifact.file + } + } + + checksumTask.configure { Checksum check -> + check.inputFiles.setFrom(filesToChecksum.unique()) + check.finalizedBy(artifactsTask) + + } + + artifactsTask.configure { + doLast { + Map<String, String> artifacts = [:] + project.publishing.publications.withType(MavenPublication).all { MavenPublication publication -> + publication.artifacts.each { MavenArtifact artifact -> + if(!artifact.file.exists() || artifact.file.name == 'grails-plugin.xml' || artifact.file.name == 'profile.yml') { + return + } + + if(artifact.classifier) { + artifacts[artifact.file.name] = "${publication.groupId}:${publication.artifactId}:${publication.version}:${artifact.classifier}" as String + } + else { + artifacts[artifact.file.name] = "${publication.groupId}:${publication.artifactId}:${publication.version}" as String + } + } + } + + File artifactsFile = artifactsDir.get().asFile + artifactsFile.mkdirs() + + artifacts.entrySet().each { Map.Entry<String, String> entry -> + File published = new File(artifactsFile, "${entry.key}.txt" as String) + published.text = entry.value + } + } + } + } + + Set<String> publishTasks = tasks.names.findAll { it.startsWith('publishMavenPublication') } + publishTasks.each { taskName -> + tasks.named(taskName).configure { publishTask -> + publishTask.finalizedBy checksumTask + } + } + } +} diff --git c/gradle/publish-root-config.gradle i/gradle/publish-root-config.gradle new file mode 100644 index 0000000..675e8ba --- /dev/null +++ i/gradle/publish-root-config.gradle @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Workaround needed for nexus publishing bug +// version and group must be specified in the root project +// https://github.com/gradle-nexus/publish-plugin/issues/310 +version = projectVersion +group = 'this.will.be.overridden' + +def publishedProjects = [ + 'grails-redis' +] +subprojects { + version = projectVersion + if (name in publishedProjects) { + apply plugin: 'org.apache.grails.gradle.grails-publish' + } +} + +def aggregatePublishedArtifacts = tasks.register('aggregatePublishedArtifacts') + +tasks.register("aggregateChecksums").configure { + group = "publishing" + description = "Aggregates all SHA-256 checksums from subprojects into a single file." + + def outputFileProvider = rootProject.layout.buildDirectory.file("CHECKSUMS.txt") + outputs.file(outputFileProvider) + + dependsOn(subprojects.findResults {it.tasks.names.contains('publishedChecksums') ? "${it.path}:publishedChecksums" : null }) + finalizedBy(aggregatePublishedArtifacts) + + outputs.upToDateWhen { false } // not worth caching + + doLast { + def outputFile = outputFileProvider.get().asFile + outputFile.withPrintWriter { writer -> + subprojects.each { sub -> + def checksumDir = sub.layout.buildDirectory.dir("checksums").get().asFile + if (checksumDir.exists()) { + checksumDir.listFiles(new FilenameFilter() { + boolean accept(File dir, String name) { + return name.endsWith(".sha512") + } + })?.each { checksumFile -> + def jarName = checksumFile.name - ".sha512" + def checksumLine = checksumFile.text.trim() + def checksum = checksumLine.tokenize()[0] + writer.println("${jarName} ${checksum}") + } + } + } + } + + println "Checksum manifest written to ${outputFile}" + } +} + +aggregatePublishedArtifacts.configure { + group = "publishing" + description = "Aggregates all published artifacts from subprojects into a single file." + + def outputFileProvider = rootProject.layout.buildDirectory.file("PUBLISHED_ARTIFACTS.txt") + outputs.file(outputFileProvider) + + outputs.upToDateWhen { false } // not worth caching + + dependsOn(subprojects.findResults {it.tasks.names.contains('savePublishedArtifacts') ? "${it.path}:savePublishedArtifacts" : null }) + + doLast { + def outputFile = outputFileProvider.get().asFile + outputFile.text = "" // clear previous + outputFile.withPrintWriter { writer -> + subprojects.each { sub -> + def artifactsDir = sub.layout.buildDirectory.dir("artifacts").get().asFile + if (artifactsDir.exists()) { + artifactsDir.listFiles(new FilenameFilter() { + boolean accept(File dir, String name) { + return name.endsWith(".txt") + } + })?.each { checksumFile -> + def artifactName = checksumFile.name - ".txt" + def coordinates = checksumFile.text.trim() + writer.println("${artifactName} ${coordinates}") + } + } + } + } + + println "Published artifacts written to ${outputFile}" + } +} \ No newline at end of file diff --git c/gradle/rat-root-config.gradle i/gradle/rat-root-config.gradle index 99be184..47e23a7 100644 --- c/gradle/rat-root-config.gradle +++ i/gradle/rat-root-config.gradle @@ -17,8 +17,8 @@ apply plugin: 'org.nosphere.apache.rat' -tasks.named('rat') { - excludes = [ +tasks.named('rat').configure { + it.excludes = [ '.asf.yaml', // ASF metadata for github integration excluded from src zip 'CODE_OF_CONDUCT.md', 'LICENSE', @@ -30,7 +30,6 @@ tasks.named('rat') { '**/build/**', // Gradle generated build directories '**/.gitattributes', // git configuration isn't code '**/.gradle/**', '**/wrapper/**', 'gradlew*', // gradle wrapper files excluded from src zip - '**/*.html', // html files are only in test '**/resources/*', // exclude test artifacts 'out/**', '*.ipr', '**/*.iml', '*.iws', '.idea/**', // Intellij generated files 'src/*', // exclude build artifacts @@ -39,6 +38,8 @@ tasks.named('rat') { '**/.gitkeep', // git configuration isn't code 'etc/bin/results/**', // exclude build directories '**/*.png', '**/*.svg', '**/*.ico', '**/*.eps', '**/*.icns', '**/*.jpg', '**/*.jpeg', '**/*.gif', // Image files + '**/*.db', // H2 database test files + '**/*.gitkeep', // Empty Gitkeep file ] // never cache license audits it.outputs.upToDateWhen { false } diff --git c/gradle/reproducible-config.gradle i/gradle/reproducible-config.gradle new file mode 100644 index 0000000..b0f891c --- /dev/null +++ i/gradle/reproducible-config.gradle @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Any jar, zip, or archive should be reproducible +// No longer needed after https://github.com/gradle/gradle/issues/30871 +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false // to prevent timestamp mismatches + reproducibleFileOrder = true // to keep the same ordering + // to avoid platform specific defaults, set the permissions consistently + filePermissions { permissions -> + permissions.unix(0644) + } + dirPermissions { permissions -> + permissions.unix(0755) + } +} \ No newline at end of file diff --git c/gradle/test-config.gradle i/gradle/test-config.gradle index 4d0eb56..b3c0a93 100644 --- c/gradle/test-config.gradle +++ i/gradle/test-config.gradle @@ -18,13 +18,24 @@ */ dependencies { - add('testRuntimeOnly', 'org.junit.platform:junit-platform-launcher') // Gradle 9+ requires this + // In Gradle 9, this needs to be declared + // https://docs.gradle.org/8.3/userguide/upgrading_version_8.html#test_framework_implementation_dependencies + add('testRuntimeOnly', 'org.junit.platform:junit-platform-launcher') } tasks.withType(Test).configureEach { + onlyIf { + ![ + 'skipTests' + ].find { + project.hasProperty(it) + } + } + useJUnitPlatform() testLogging { exceptionFormat = 'full' events 'passed', 'skipped', 'failed'//, 'standardOut', 'standardError' } + beforeTest { descriptor -> logger.quiet(" -- $descriptor") } } diff --git c/plugin/build.gradle i/plugin/build.gradle index ad3d455..e43bdcc 100644 --- c/plugin/build.gradle +++ i/plugin/build.gradle @@ -69,4 +69,5 @@ apply { from rootProject.layout.projectDirectory.file('gradle/java-config.gradle') from rootProject.layout.projectDirectory.file('gradle/publish-config.gradle') from rootProject.layout.projectDirectory.file('gradle/test-config.gradle') + from rootProject.layout.projectDirectory.file('gradle/reproducible-config.gradle') } \ No newline at end of file diff --git c/settings.gradle i/settings.gradle index 299c972..4d11dee 100644 --- c/settings.gradle +++ i/settings.gradle @@ -24,6 +24,13 @@ plugins { def isCI = System.getenv().containsKey('CI') def isLocal = !isCI +def isReproducibleBuild = System.getenv("SOURCE_DATE_EPOCH") != null +if(isReproducibleBuild) { + gradle.settingsEvaluated { + logger.warn("*************** Remote Build Cache Disabled due to Reproducible Build ********************") + logger.warn("Build date will be set to (SOURCE_DATE_EPOCH=${System.getenv("SOURCE_DATE_EPOCH")})") + } +} develocity { server = 'https://ge.grails.org' @@ -36,10 +43,10 @@ develocity { } buildCache { - local { enabled = isLocal } + local { enabled = (isLocal && !isReproducibleBuild) || (isCI && isReproducibleBuild) } remote(develocity.buildCache) { push = isCI - enabled = true + enabled = !isReproducibleBuild } }
# Conflicts: # .gitignore
jdaugherty
approved these changes
Jun 9, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
No description provided.