├── .github ├── dco.yml ├── release-artifacts.spec └── workflows │ ├── build-and-deploy-snapshot.yml │ ├── release.yml │ └── validate-gradle-wrapper.yml ├── .gitignore ├── .sdkmanrc ├── .springjavaformatconfig ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── develocity-conventions-core ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── spring │ │ └── develocity │ │ └── conventions │ │ └── core │ │ ├── BuildCacheConventions.java │ │ ├── BuildScanConventions.java │ │ ├── ConfigurableBuildCache.java │ │ ├── ConfigurableBuildScan.java │ │ ├── ConfigurableDevelocity.java │ │ ├── ContinuousIntegration.java │ │ ├── ProcessRunner.java │ │ └── package-info.java │ └── test │ └── java │ └── io │ └── spring │ └── develocity │ └── conventions │ └── core │ ├── BuildCacheConventionsTests.java │ ├── BuildScanConventionsTests.java │ └── TestProcessRunner.java ├── develocity-conventions-gradle-plugin ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── spring │ │ └── develocity │ │ └── conventions │ │ └── gradle │ │ ├── AnonymousPublicationBuildScanConventions.java │ │ ├── DevelocityConventionsPlugin.java │ │ ├── GradleConfigurableBuildCache.java │ │ ├── GradleConfigurableBuildScan.java │ │ ├── GradleConfigurableDevelocity.java │ │ ├── ProcessOperationsProcessRunner.java │ │ ├── WorkingDirectoryProcessOperations.java │ │ └── package-info.java │ └── test │ └── java │ └── io │ └── spring │ └── develocity │ └── conventions │ └── gradle │ ├── AnonymousPublicationBuildScanConventionsTests.java │ ├── DevelocityConventionsPluginIntegrationTests.java │ ├── GradleConfigurableBuildCacheTests.java │ ├── GradleConfigurableBuildScanTests.java │ ├── TestBuildScanConfiguration.java │ ├── TestDevelocityConfiguration.java │ ├── TestProcessRunner.java │ └── TestProperty.java ├── develocity-conventions-maven-extension ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── spring │ │ │ └── develocity │ │ │ └── conventions │ │ │ └── maven │ │ │ ├── ConventionsDevelocityListener.java │ │ │ ├── MavenConfigurableBuildCache.java │ │ │ ├── MavenConfigurableBuildScan.java │ │ │ ├── MavenConfigurableDevelocity.java │ │ │ ├── ProcessBuilderProcessRunner.java │ │ │ └── package-info.java │ └── resources │ │ └── META-INF │ │ └── plexus │ │ └── components.xml │ └── test │ └── java │ └── io │ └── spring │ └── develocity │ └── conventions │ └── maven │ ├── MavenConfigurableBuildCacheTests.java │ ├── MavenConfigurableBuildScanTests.java │ └── ProcessBuilderProcessRunnerTests.java ├── gradle.properties ├── gradle ├── plugins │ └── build-conventions │ │ ├── build.gradle │ │ ├── settings.gradle │ │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── spring │ │ └── develocity │ │ └── conventions │ │ └── build │ │ ├── BuildConventionsPlugin.java │ │ ├── JavaConventions.java │ │ ├── MavenPublishConventions.java │ │ └── package-info.java └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/dco.yml: -------------------------------------------------------------------------------- 1 | require: 2 | members: false 3 | -------------------------------------------------------------------------------- /.github/release-artifacts.spec: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "aql": { 5 | "items.find": { 6 | "$and": [ 7 | { 8 | "@build.name": "${buildName}", 9 | "@build.number": "${buildNumber}" 10 | } 11 | ] 12 | } 13 | }, 14 | "target": "nexus/" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy snapshot 2 | on: 3 | push: 4 | branches: 5 | - main 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | jobs: 9 | build: 10 | if: ${{ github.repository == 'spring-io/develocity-conventions' }} 11 | name: Build and deploy snapshot 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Java 15 | uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 16 | with: 17 | distribution: 'liberica' 18 | java-version: 8 19 | - name: Check out code 20 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 21 | - name: Set up Gradle 22 | uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 23 | - name: Configure Gradle properties 24 | shell: bash 25 | run: | 26 | mkdir -p $HOME/.gradle 27 | echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties 28 | echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties 29 | - name: Build and publish 30 | id: build 31 | run: ./gradlew -PdistributionRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository 32 | - name: Deploy 33 | uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 34 | with: 35 | uri: 'https://repo.spring.io' 36 | username: ${{ secrets.ARTIFACTORY_USERNAME }} 37 | password: ${{ secrets.ARTIFACTORY_PASSWORD }} 38 | build-name: develocity-conventions 39 | repository: 'libs-snapshot-local' 40 | folder: 'deployment-repository' 41 | signing-key: ${{ secrets.GPG_PRIVATE_KEY }} 42 | signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v[0-9]+.[0-9]+.[0-9]+ 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | jobs: 9 | get-version: 10 | name: Get version from tag 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Get version from tag 14 | id: version-from-tag 15 | run: | 16 | version=$(echo ${{ github.ref_name }} | cut -c 2-) 17 | echo "version=$version" >> $GITHUB_OUTPUT 18 | outputs: 19 | version: ${{ steps.version-from-tag.outputs.version }} 20 | build-and-stage-release: 21 | if: ${{ github.repository == 'spring-io/develocity-conventions' }} 22 | name: Build and stage release 23 | needs: get-version 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Set up Java 27 | uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 28 | with: 29 | distribution: 'liberica' 30 | java-version: 8 31 | - name: Check out code 32 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 33 | - name: Set up Gradle 34 | uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 35 | - name: Configure Gradle properties 36 | shell: bash 37 | run: | 38 | mkdir -p $HOME/.gradle 39 | echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties 40 | echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties 41 | - name: Build and publish release 42 | id: build 43 | run: ./gradlew -PdistributionRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository 44 | - name: Stage 45 | uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1 46 | with: 47 | uri: 'https://repo.spring.io' 48 | username: ${{ secrets.ARTIFACTORY_USERNAME }} 49 | password: ${{ secrets.ARTIFACTORY_PASSWORD }} 50 | build-name: ${{ format('develocity-conventions-{0}', needs.get-version.outputs.version)}} 51 | repository: 'libs-staging-local' 52 | folder: 'deployment-repository' 53 | signing-key: ${{ secrets.GPG_PRIVATE_KEY }} 54 | signing-passphrase: ${{ secrets.GPG_PASSPHRASE }} 55 | sync-to-maven-central: 56 | name: Sync to Maven Central 57 | needs: 58 | - get-version 59 | - build-and-stage-release 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: Check out code 63 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 64 | - name: Set up JFrog CLI 65 | uses: jfrog/setup-jfrog-cli@d82fe26823e1f25529250895d5673f65b02af085 # v4.0.1 66 | env: 67 | JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} 68 | - name: Download release artifacts 69 | run: jf rt download --spec .github/release-artifacts.spec --spec-vars 'buildName=${{ format('develocity-conventions-{0}', needs.get-version.outputs.version) }};buildNumber=${{ github.run_number }}' 70 | - name: Sync 71 | uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1 72 | with: 73 | username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} 74 | password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} 75 | staging-profile-name: ${{ secrets.OSSRH_S01_STAGING_PROFILE_NAME }} 76 | create: true 77 | upload: true 78 | close: true 79 | release: true 80 | generate-checksums: true 81 | - name: Await 82 | shell: bash 83 | run: | 84 | url=${{ format('https://repo.maven.apache.org/maven2/io/spring/develocity/conventions/develocity-conventions-core/{0}/develocity-conventions-core-{0}.jar', needs.get-version.outputs.version) }} 85 | echo "Waiting for $url" 86 | until curl --fail --head --silent $url > /dev/null 87 | do 88 | echo "." 89 | sleep 60 90 | done 91 | echo "$url is available" 92 | promote-release: 93 | name: Promote release 94 | needs: 95 | - build-and-stage-release 96 | - get-version 97 | - sync-to-maven-central 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Set up JFrog CLI 101 | uses: jfrog/setup-jfrog-cli@d82fe26823e1f25529250895d5673f65b02af085 # v4.0.1 102 | env: 103 | JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} 104 | - name: Promote build 105 | run: jfrog rt build-promote ${{ format('develocity-conventions-{0}', needs.get-version.outputs.version)}} ${{ github.run_number }} libs-release-local 106 | create-github-release: 107 | name: Create GitHub release 108 | needs: 109 | - promote-release 110 | - get-version 111 | runs-on: ubuntu-latest 112 | steps: 113 | - name: Generate changelog 114 | uses: spring-io/github-changelog-generator@052892c62af51f8af87a9da6de55e70864b7df12 115 | with: 116 | milestone: ${{ needs.get-version.outputs.version }} 117 | token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} 118 | - name: Create GitHub release 119 | env: 120 | GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} 121 | run: | 122 | gh release create ${{ github.ref_name }} --repo ${{ github.repository }} --notes-file changelog.md 123 | -------------------------------------------------------------------------------- /.github/workflows/validate-gradle-wrapper.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | validate-gradle-wrapper: 6 | name: Validate Gradle wrapper 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 10 | - uses: gradle/wrapper-validation-action@b231772637bb498f11fdbc86052b6e8a8dc9fc92 # v2.1.2 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .gradle 3 | .project 4 | .settings/ 5 | bin/ 6 | build/ -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=8.0.452-librca 4 | -------------------------------------------------------------------------------- /.springjavaformatconfig: -------------------------------------------------------------------------------- 1 | java-baseline=8 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open 4 | and welcoming community, we pledge to respect all people who contribute through reporting 5 | issues, posting feature requests, updating documentation, submitting pull requests or 6 | patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for 9 | everyone, regardless of level of experience, gender, gender identity and expression, 10 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 11 | religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 24 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 25 | Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors 26 | that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing this project. Project 30 | maintainers who do not follow or enforce the Code of Conduct may be permanently removed 31 | from the project team. 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting a project maintainer at spring-code-of-conduct@pivotal.io. All complaints will 38 | be reviewed and investigated and will result in a response that is deemed necessary and 39 | appropriate to the circumstances. Maintainers are obligated to maintain confidentiality 40 | with regard to the reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant][1], version 1.3.0, 43 | available at [contributor-covenant.org/version/1/3/0/][2]. 44 | 45 | [1]: https://contributor-covenant.org 46 | [2]: https://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Develocity Conventions Plugin 2 | 3 | Develocity Conventions Plugin is released under the Apache 2.0 license. 4 | If you would like to contribute something, or simply want to work with the code, this document should help you to get started. 5 | 6 | ## Code of conduct 7 | 8 | This project adheres to the Contributor Covenant [code of conduct][1]. 9 | By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. 10 | 11 | ## Include a Signed-off-by trailer 12 | 13 | All commits must include a _Signed-off-by_ trailer at the end of each commit message to indicate that the contributor agrees to the [Developer Certificate of Origin (DCO)][2]. 14 | For additional details, please refer to the ["Hello DCO, Goodbye CLA: Simplifying Contributions to Spring"][3] blog post. 15 | 16 | ## Code conventions and housekeeping 17 | 18 | None of these is essential for a pull request, but they will all help 19 | 20 | - Make sure all new `.java` files to have a simple Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is for. 21 | - Add the ASF license header comment to all new `.java` files (copy from existing files in the project) 22 | - Add yourself as an `@author` to the `.java` files that you modify substantially (more than cosmetic changes). 23 | - Add some Javadocs 24 | - Add unit tests that cover any new or modified functionality 25 | - Whenever possible, please rebase your branch against the current main (or other target branch in the main project). 26 | - When writing a commit message please follow [these conventions][4]. 27 | Also, if you are fixing an existing issue please add `Fixes gh-nnn` at the end of the commit message (where nnn is the issue number). 28 | 29 | ## Working with the code 30 | 31 | ### Building from source 32 | 33 | The code is built with Gradle: 34 | 35 | ``` 36 | $ ./gradlew build 37 | ``` 38 | 39 | [1]: CODE_OF_CONDUCT.md 40 | [2]: https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin 41 | [3]: https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring 42 | [4]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Develocity Conventions 2 | 3 | Conventions for Maven and Gradle projects that use the Develocity instance hosted at [ge.spring.io](https://ge.spring.io). 4 | 5 | ## Build cache conventions 6 | 7 | When applied, the conventions will configure the build cache to: 8 | 9 | - Enable local caching. 10 | - Use https://ge.spring.io as the remote cache server. 11 | - Enable pulling from the remote cache. 12 | - Enable pushing to the remote cache when a CI environment is detected and the required access token is available. 13 | 14 | ### Remote cache 15 | 16 | #### URL 17 | 18 | By default, https://ge.spring.io will be used as the remote cache server. 19 | The server can be configured using the `DEVELOCITY_CACHE_SERVER` environment variable. 20 | For backwards compatibility, `GRADLE_ENTERPRISE_CACHE_URL` is also supported for a limited time. 21 | `/cache/` is removed from the end of the URL and the remainder is used to configure the remote cache server. 22 | 23 | ## Build scan conventions 24 | 25 | When applied alongside the [Develocity Plugin](https://plugins.gradle.org/plugin/com.gradle.develocity), the plugin will configure publishing of build scans to [ge.spring.io](https://ge.spring.io) when authenticated. 26 | The build scans will be customized to: 27 | 28 | - Add tags: 29 | - `JDK-`. 30 | When using Maven, `` is the specification version of the JDK running the build. 31 | When using Gradle, `` is the value of the `toolchainVersion` project property or, when not set, it's the specification version of the JDK running the build. 32 | - `CI` or `Local` depending on where the build is executing. 33 | - `dirty` if the git working copy is dirty. 34 | - Name of the git branch being built. 35 | - Add custom key-value pairs: 36 | - `Git branch` with a value of the name of the git branch being built. 37 | - `Git commit` with a value of the commit ID `HEAD` 38 | - `Git status` when the working copy is dirty. 39 | The value is the output of `git status --porcelain`. 40 | - `Docker` when the `docker` CLI is available. 41 | The value is the output of `docker --version`. 42 | - `Docker Compose` when `docker compose` CLI is available. 43 | The value is the output of `docker compose version`. 44 | - Add links: 45 | - `CI build` when building on Bamboo, GitHub Actions, or Jenkins, linking to the build on the CI server. 46 | - `Git commit build scans`, linking to scans for other builds of the same git commit. 47 | - Enable capturing of file fingerprints 48 | - Upload build scans in the foreground when running on CI 49 | 50 | ### Git branch names 51 | 52 | `git rev-parse --abbrev-ref HEAD` is used to determine the name of the current branch. 53 | This does not work on Concourse as its git resource places the repository in a detached head state. 54 | To work around this, an environment variable named `BRANCH` can be set on the task to provide the name of the branch. 55 | 56 | ### Anonymous publication 57 | 58 | When using Gradle, build scans can be published anonymously to scans.gradle.com by running the build with `--scan`. 59 | 60 | ## Authentication 61 | 62 | :rotating_light: **Credentials must not be configured in environments where pull requests are built.** :rotating_light: 63 | 64 | Publishing build scans and pushing to the remote cache requires authentication via an access key. 65 | Additionally, pushing to the remote cache also requires that a CI environment be detected. 66 | When running on CI, the access key should be made available via the `DEVELOCITY_ACCESS_KEY` environment variable. 67 | `GRADLE_ENTERPRISE_ACCESS_KEY` can also be used although it will result in a deprecation warning from Gradle. 68 | 69 | #### Bamboo 70 | 71 | The environment variable should be set to `${bamboo.gradle_enterprise_secret_access_key}`. 72 | 73 | #### Concourse 74 | 75 | The environment variable should be set using `((gradle_enterprise_secret_access_key))` from Vault. 76 | 77 | #### GitHub Actions 78 | 79 | The environment variable should be set using the `GRADLE_ENTERPRISE_SECRET_ACCESS_KEY` organization secret. 80 | 81 | #### Jenkins 82 | 83 | The environment variable should be set using the `gradle_enterprise_secret_access_key` secret text credential. 84 | 85 | #### Local 86 | 87 | An access key can be provisioned by running `./gradlew provisionDevelocityAccessKey` once the project has been configured to use this plugin. 88 | 89 | ## Detecting CI 90 | 91 | Bamboo is detected by looking for an environment variable named `bamboo_resultsUrl`. 92 | 93 | Concourse does not automatically set any environment variables in the build's container that allow its use to be detected. 94 | To work around this, an environment variable named `CI` can be set on the task. 95 | 96 | GitHub Actions is detected by looking for an environment variable named `GITHUB_ACTIONS`. 97 | 98 | Jenkins is detected by looking for an environment variable named `JENKINS_URL`. 99 | 100 | ## Using the conventions 101 | 102 | Releases of the conventions are published to Maven Central. 103 | Snapshots are published to https://repo.spring.io/snapshot. 104 | 105 | ### Gradle 106 | 107 | The conventions support Gradle 7.4 and later. 108 | 109 | The first step in using the conventions is to make the necessary repository available for plugin resolution. 110 | This is done by configuring a plugin management repository in `settings.gradle`, as shown in the following example: 111 | 112 | ```groovy 113 | pluginManagement { 114 | repositories { 115 | gradlePluginPortal() 116 | mavenCentral() 117 | } 118 | } 119 | ``` 120 | 121 | In the example above, `gradlePluginPortal()` is declared to allow other plugins to continue to be resolved from the portal. 122 | 123 | Now apply the plugin in `settings.gradle`: 124 | 125 | ```groovy 126 | plugins { 127 | // … 128 | id "io.spring.develocity.conventions" version "<>" 129 | // … 130 | } 131 | ``` 132 | 133 | The behavior of the conventions can be controlled with a `spring.build-type` property in `gradle.properties`. 134 | If the property is present, it must have a value of `oss` in order to publish build scans and use the build cache. 135 | Any other value will disable both build scan publishing and the build cache. 136 | For example, you may want to update `gradle.properties` and set `spring.build-type` to `cve` when working on a fix for a security vulnerability. 137 | 138 | ### Maven 139 | 140 | To use the conventions, create a `.mvn/extensions.xml` file in the root of the project: 141 | 142 | ```xml 143 | 144 | 145 | 146 | io.spring.develocity.conventions 147 | develocity-conventions-maven-extension 148 | <> 149 | 150 | 151 | ``` 152 | 153 | Any existing `.mvn/gradle-enterprise.xml` file should be deleted in favor of the configuration that's provided by the conventions. 154 | Lastly, add `.mvn/.develocity/` to the project's `.gitignore` file. 155 | The conventions are ready to use. 156 | -------------------------------------------------------------------------------- /develocity-conventions-core/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "build-conventions" 3 | id "java" 4 | id "maven-publish" 5 | } 6 | 7 | description = "Develocity Conventions Core" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | testImplementation("org.assertj:assertj-core:3.27.2") 15 | testImplementation("org.junit.jupiter:junit-jupiter:5.13.0") 16 | testImplementation("org.mockito:mockito-core:4.11.0") 17 | 18 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0") 19 | } 20 | 21 | publishing { 22 | publications { 23 | maven(MavenPublication) { 24 | from components.java 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/BuildCacheConventions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.util.Map; 20 | 21 | /** 22 | * Conventions that are applied to the build cache. 23 | * 24 | * @author Andy Wilkinson 25 | */ 26 | public class BuildCacheConventions { 27 | 28 | private final Map env; 29 | 30 | public BuildCacheConventions() { 31 | this(System.getenv()); 32 | } 33 | 34 | BuildCacheConventions(Map env) { 35 | this.env = env; 36 | } 37 | 38 | /** 39 | * Applies the conventions to the given {@code buildCache}. 40 | * @param buildCache build cache to be configured 41 | */ 42 | public void execute(ConfigurableBuildCache buildCache) { 43 | buildCache.local((local) -> local.enable()); 44 | buildCache.remote((remote) -> { 45 | remote.enable(); 46 | String cacheServer = this.env.get("DEVELOCITY_CACHE_SERVER"); 47 | if (cacheServer == null) { 48 | cacheServer = serverOfCacheUrl(this.env.get("GRADLE_ENTERPRISE_CACHE_URL")); 49 | if (cacheServer == null) { 50 | cacheServer = "https://ge.spring.io"; 51 | } 52 | } 53 | remote.setServer(cacheServer); 54 | String accessKey = this.env.get("DEVELOCITY_ACCESS_KEY"); 55 | if (accessKey == null) { 56 | accessKey = this.env.get("GRADLE_ENTERPRISE_ACCESS_KEY"); 57 | } 58 | if (hasText(accessKey) && ContinuousIntegration.detect(this.env) != null) { 59 | remote.enablePush(); 60 | } 61 | }); 62 | } 63 | 64 | private String serverOfCacheUrl(String cacheUrl) { 65 | if (cacheUrl != null) { 66 | if (cacheUrl.endsWith("/cache/")) { 67 | return cacheUrl.substring(0, cacheUrl.length() - 7); 68 | } 69 | if (cacheUrl.endsWith("/cache")) { 70 | return cacheUrl.substring(0, cacheUrl.length() - 6); 71 | } 72 | } 73 | return null; 74 | } 75 | 76 | private boolean hasText(String string) { 77 | return string != null && string.length() > 0; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/BuildScanConventions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.UnsupportedEncodingException; 21 | import java.net.URLEncoder; 22 | import java.util.Map; 23 | import java.util.function.Consumer; 24 | import java.util.stream.Collectors; 25 | 26 | import io.spring.develocity.conventions.core.ProcessRunner.RunFailedException; 27 | 28 | /** 29 | * Conventions that are applied to build scans for Maven and Gradle builds. Spring 30 | * conventions. 31 | * 32 | * @author Andy Wilkinson 33 | */ 34 | public class BuildScanConventions { 35 | 36 | private final ProcessRunner processRunner; 37 | 38 | private final Map env; 39 | 40 | public BuildScanConventions(ProcessRunner processRunner) { 41 | this(processRunner, System.getenv()); 42 | } 43 | 44 | protected BuildScanConventions(ProcessRunner processRunner, Map env) { 45 | this.processRunner = processRunner; 46 | this.env = env; 47 | } 48 | 49 | /** 50 | * Applies the conventions to the given {@code develocity} and {@code buildScan}. 51 | * @param develocity develocity to be configured 52 | * @param buildScan build scan to be configured 53 | */ 54 | public void execute(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { 55 | buildScan.obfuscation((obfuscation) -> obfuscation 56 | .ipAddresses((addresses) -> addresses.stream().map((address) -> "0.0.0.0").collect(Collectors.toList()))); 57 | configurePublishing(develocity, buildScan); 58 | ContinuousIntegration ci = ContinuousIntegration.detect(this.env); 59 | tagBuildScan(buildScan, ci); 60 | buildScan.background((backgrounded) -> addGitMetadata(develocity, backgrounded)); 61 | buildScan.background(this::addDockerMetadata); 62 | buildScan.background(this::addDockerComposeMetadata); 63 | addCiMetadata(buildScan, ci); 64 | buildScan.uploadInBackground(ci == null); 65 | buildScan.captureInputFiles(true); 66 | } 67 | 68 | /** 69 | * Configures publishing of the build scan. The default implementation always 70 | * publishes scans when authenticated and publishes them to 71 | * {@code https://ge.spring.io}. 72 | * @param develocity develocity to configure 73 | * @param buildScan build scan to configure 74 | * 75 | */ 76 | protected void configurePublishing(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { 77 | buildScan.publishIfAuthenticated(); 78 | develocity.setServer("https://ge.spring.io"); 79 | } 80 | 81 | private void tagBuildScan(ConfigurableBuildScan buildScan, ContinuousIntegration ci) { 82 | tagCiOrLocal(buildScan, ci); 83 | tagJdk(buildScan); 84 | tagOperatingSystem(buildScan); 85 | } 86 | 87 | private void tagCiOrLocal(ConfigurableBuildScan buildScan, ContinuousIntegration ci) { 88 | buildScan.tag((ci != null) ? "CI" : "Local"); 89 | } 90 | 91 | private void tagJdk(ConfigurableBuildScan buildScan) { 92 | buildScan.tag("JDK-" + getJdkVersion()); 93 | } 94 | 95 | protected String getJdkVersion() { 96 | return System.getProperty("java.specification.version"); 97 | } 98 | 99 | private void tagOperatingSystem(ConfigurableBuildScan buildScan) { 100 | buildScan.tag(System.getProperty("os.name")); 101 | } 102 | 103 | private void addGitMetadata(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { 104 | run("git", "rev-parse", "--short=8", "--verify", "HEAD").standardOut((gitCommitId) -> { 105 | String commitIdLabel = "Git commit"; 106 | buildScan.value(commitIdLabel, gitCommitId); 107 | 108 | String server = develocity.getServer(); 109 | if (server != null) { 110 | buildScan.link("Git commit build scans", server + createSearchUrl(commitIdLabel, gitCommitId)); 111 | } 112 | }); 113 | getBranch().standardOut((gitBranchName) -> { 114 | buildScan.tag(gitBranchName); 115 | buildScan.value("Git branch", gitBranchName); 116 | }); 117 | run("git", "status", "--porcelain").standardOut((gitStatus) -> { 118 | buildScan.tag("dirty"); 119 | buildScan.value("Git status", gitStatus); 120 | }); 121 | } 122 | 123 | private void addDockerMetadata(ConfigurableBuildScan buildScan) { 124 | run("docker", "--version").standardOut((dockerVersion) -> buildScan.value("Docker", dockerVersion)); 125 | } 126 | 127 | private void addDockerComposeMetadata(ConfigurableBuildScan buildScan) { 128 | run("docker", "compose", "version") 129 | .standardOut((dockerComposeVersion) -> buildScan.value("Docker Compose", dockerComposeVersion)); 130 | } 131 | 132 | private void addCiMetadata(ConfigurableBuildScan buildScan, ContinuousIntegration ci) { 133 | if (ci == null) { 134 | return; 135 | } 136 | String buildUrl = ci.buildUrlFrom(this.env); 137 | if (hasText(buildUrl)) { 138 | buildScan.link("CI build", buildUrl); 139 | } 140 | buildScan.value("CI provider", ci.toString()); 141 | } 142 | 143 | private RunResult getBranch() { 144 | String branch = this.env.get("BRANCH"); 145 | if (branch != null) { 146 | return new RunResult(branch); 147 | } 148 | return run("git", "rev-parse", "--abbrev-ref", "HEAD"); 149 | } 150 | 151 | private String createSearchUrl(String name, String value) { 152 | return "/scans?search.names=" + encodeURL(name) + "&search.values=" + encodeURL(value); 153 | } 154 | 155 | private String encodeURL(String url) { 156 | try { 157 | return URLEncoder.encode(url, "UTF-8"); 158 | } 159 | catch (UnsupportedEncodingException ex) { 160 | throw new RuntimeException(ex); 161 | } 162 | } 163 | 164 | private RunResult run(Object... commandLine) { 165 | ByteArrayOutputStream standardOutput = new ByteArrayOutputStream(); 166 | try { 167 | this.processRunner.run((spec) -> { 168 | spec.commandLine(commandLine); 169 | spec.standardOutput(standardOutput); 170 | }); 171 | return new RunResult(standardOutput.toString().trim()); 172 | } 173 | catch (RunFailedException ex) { 174 | return new RunResult(); 175 | } 176 | } 177 | 178 | private boolean hasText(String string) { 179 | return string != null && string.length() > 0; 180 | } 181 | 182 | private static final class RunResult { 183 | 184 | private final String standardOutput; 185 | 186 | private RunResult() { 187 | this.standardOutput = null; 188 | } 189 | 190 | private RunResult(String standardOutput) { 191 | this.standardOutput = standardOutput; 192 | } 193 | 194 | private void standardOut(Consumer consumer) { 195 | if (this.standardOutput != null && this.standardOutput.length() > 0) { 196 | consumer.accept(this.standardOutput); 197 | } 198 | } 199 | 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/ConfigurableBuildCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.util.function.Consumer; 20 | 21 | /** 22 | * A build cache that can be configured. Provides a build-system agnostic API that can be 23 | * used with both Gradle and Maven builds. 24 | * 25 | * @author Andy Wilkinson 26 | */ 27 | public interface ConfigurableBuildCache { 28 | 29 | /** 30 | * Configures the local build cache. 31 | * @param local a consumer that is called to configure the {@link LocalBuildCache} 32 | */ 33 | void local(Consumer local); 34 | 35 | /** 36 | * Configures the remote build cache. 37 | * @param remote a consumer that is called to configure the {@link RemoteBuildCache} 38 | */ 39 | void remote(Consumer remote); 40 | 41 | /** 42 | * Configuration for the local build cache. 43 | */ 44 | interface LocalBuildCache { 45 | 46 | /** 47 | * Enables the local build cache. 48 | */ 49 | void enable(); 50 | 51 | } 52 | 53 | /** 54 | * Configuration for the remote build cache. 55 | */ 56 | interface RemoteBuildCache { 57 | 58 | /** 59 | * Enables the remote build cache. 60 | */ 61 | void enable(); 62 | 63 | /** 64 | * Enables the pushing of entries to the remote build cache. 65 | */ 66 | void enablePush(); 67 | 68 | /** 69 | * Sets the server that's hosting the remote build cache. 70 | * @param server remote cache's server 71 | */ 72 | void setServer(String server); 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/ConfigurableBuildScan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.net.InetAddress; 20 | import java.util.List; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | /** 25 | * A build scan that can be configured. Provides a build-system agnostic API that can be 26 | * used with both Gradle and Maven builds. 27 | * 28 | * @author Andy Wilkinson 29 | */ 30 | public interface ConfigurableBuildScan { 31 | 32 | /** 33 | * Configures whether to capture input files. 34 | * @param capture {@code true} if input files for Gradle tasks or Maven goals should 35 | * be captured, otherwise {@code false}. 36 | */ 37 | void captureInputFiles(boolean capture); 38 | 39 | /** 40 | * Configures obfuscation of data in the build scan. 41 | * @param configurer called to configure the obfuscation 42 | */ 43 | void obfuscation(Consumer configurer); 44 | 45 | /** 46 | * Configures the build scan to only be published when authenticated. 47 | */ 48 | void publishIfAuthenticated(); 49 | 50 | /** 51 | * Configures whether to upload the build scan in the background. 52 | * @param enabled {@code true} to use background uploads, otherwise {@code false}. 53 | */ 54 | void uploadInBackground(boolean enabled); 55 | 56 | /** 57 | * Adds a link with the given {@code name} and {@code url} to the build scan. 58 | * @param name the name of the link 59 | * @param url the URL of the link 60 | */ 61 | void link(String name, String url); 62 | 63 | /** 64 | * Adds a tag to the build scan. 65 | * @param tag the tag 66 | */ 67 | void tag(String tag); 68 | 69 | /** 70 | * Adds a name-value pair to the build scan. 71 | * @param name the name 72 | * @param value the value 73 | */ 74 | void value(String name, String value); 75 | 76 | /** 77 | * Configures the build scan in the background. 78 | * @param backgroundConfigurer called in the background to configure the build scan 79 | */ 80 | void background(Consumer backgroundConfigurer); 81 | 82 | /** 83 | * Configures the obfuscation of data in the build scan. 84 | */ 85 | interface ObfuscationConfigurer { 86 | 87 | /** 88 | * Obfuscates IP addresses in the build scan by applying the given 89 | * {@code obfuscator} function to them. 90 | * @param obfuscator function to obfuscate IP addresses 91 | */ 92 | void ipAddresses(Function, ? extends List> obfuscator); 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/ConfigurableDevelocity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | /** 20 | * Configuration for Develocity. 21 | * 22 | * @author Andy Wilkinson 23 | */ 24 | public interface ConfigurableDevelocity { 25 | 26 | /** 27 | * Returns the Develocity server. 28 | * @return the server 29 | */ 30 | String getServer(); 31 | 32 | /** 33 | * Sets the Develocity server. 34 | * @param server the server 35 | */ 36 | void setServer(String server); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/ContinuousIntegration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.util.Map; 20 | import java.util.function.Function; 21 | 22 | /** 23 | * Known continuous integration environments. 24 | * 25 | * @author Andy Wilkinson 26 | */ 27 | enum ContinuousIntegration { 28 | 29 | BAMBOO("Bamboo", "bamboo_resultsUrl"), 30 | 31 | CIRCLE_CI("CircleCI", "CIRCLE_BUILD_URL"), 32 | 33 | GITHUB_ACTIONS("GitHub Actions", "GITHUB_ACTIONS", (env) -> { 34 | String server = env.get("GITHUB_SERVER_URL"); 35 | String repository = env.get("GITHUB_REPOSITORY"); 36 | String runId = env.get("GITHUB_RUN_ID"); 37 | return server + "/" + repository + "/actions/runs/" + runId; 38 | }), 39 | 40 | JENKINS("Jenkins", "JENKINS_URL", (env) -> env.get("BUILD_URL")), 41 | 42 | CONCOURSE("Concourse", "CI", (env) -> null); 43 | 44 | private final String name; 45 | 46 | private final String environmentVariable; 47 | 48 | private final Function, String> buildUrl; 49 | 50 | ContinuousIntegration(String name, String environmentVariable) { 51 | this(name, environmentVariable, (env) -> env.get(environmentVariable)); 52 | } 53 | 54 | ContinuousIntegration(String name, String environmentVariable, Function, String> buildUrl) { 55 | this.name = name; 56 | this.environmentVariable = environmentVariable; 57 | this.buildUrl = buildUrl; 58 | } 59 | 60 | String buildUrlFrom(Map env) { 61 | return this.buildUrl.apply(env); 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return this.name; 67 | } 68 | 69 | static ContinuousIntegration detect(Map env) { 70 | for (ContinuousIntegration ci : values()) { 71 | if (env.containsKey(ci.environmentVariable)) { 72 | return ci; 73 | } 74 | } 75 | return null; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/ProcessRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.io.OutputStream; 20 | import java.util.function.Consumer; 21 | 22 | /** 23 | * Minimal API for running a process. 24 | * 25 | * @author Andy Wilkinson 26 | */ 27 | public interface ProcessRunner { 28 | 29 | /** 30 | * Runs the process described by the process spec. 31 | * @param configurer configures the {@link ProcessSpec} 32 | * @throws RunFailedException if the process could not be run 33 | */ 34 | void run(Consumer configurer); 35 | 36 | /** 37 | * A spec for running a process. 38 | */ 39 | interface ProcessSpec { 40 | 41 | /** 42 | * The command line to be used to run the process. 43 | * @param commandLine the command line 44 | */ 45 | void commandLine(Object... commandLine); 46 | 47 | /** 48 | * Configures the stream to which the process's standard output should be written. 49 | * @param standardOutput the stream for standard output 50 | */ 51 | void standardOutput(OutputStream standardOutput); 52 | 53 | } 54 | 55 | /** 56 | * Exception indicating a run failure. 57 | * 58 | * @see ProcessRunner#run 59 | */ 60 | class RunFailedException extends RuntimeException { 61 | 62 | public RunFailedException(Exception cause) { 63 | super(cause); 64 | } 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/main/java/io/spring/develocity/conventions/core/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Core classes for the Develocity conventions for Spring projects. 19 | */ 20 | package io.spring.develocity.conventions.core; 21 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/test/java/io/spring/develocity/conventions/core/BuildCacheConventionsTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.function.Consumer; 23 | 24 | import org.junit.jupiter.api.Test; 25 | import org.junit.jupiter.params.ParameterizedTest; 26 | import org.junit.jupiter.params.provider.ValueSource; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | /** 31 | * Tests for {@link BuildCacheConventions}. 32 | * 33 | * @author Andy Wilkinson 34 | */ 35 | class BuildCacheConventionsTests { 36 | 37 | private final TestConfigurableBuildCache buildCache = new TestConfigurableBuildCache(); 38 | 39 | @Test 40 | void localCacheIsEnabled() { 41 | new BuildCacheConventions().execute(this.buildCache); 42 | assertThat(this.buildCache.local.enabled).isTrue(); 43 | } 44 | 45 | @Test 46 | void remoteCacheIsEnabled() { 47 | new BuildCacheConventions().execute(this.buildCache); 48 | assertThat(this.buildCache.remote.enabled).isTrue(); 49 | assertThat(this.buildCache.remote.server).isEqualTo("https://ge.spring.io"); 50 | assertThat(this.buildCache.remote.push).isFalse(); 51 | } 52 | 53 | @ParameterizedTest 54 | @ValueSource(strings = { "https://ge.example.com/cache/", "https://ge.example.com/cache" }) 55 | void remoteCacheUrlCanBeConfigured(String cacheUrl) { 56 | Map env = new HashMap<>(); 57 | env.put("GRADLE_ENTERPRISE_CACHE_URL", cacheUrl); 58 | new BuildCacheConventions(env).execute(this.buildCache); 59 | assertThat(this.buildCache.remote.enabled).isTrue(); 60 | assertThat(this.buildCache.remote.server).isEqualTo("https://ge.example.com"); 61 | assertThat(this.buildCache.remote.push).isFalse(); 62 | } 63 | 64 | @Test 65 | void remoteCacheServerCanBeConfigured() { 66 | Map env = new HashMap<>(); 67 | env.put("DEVELOCITY_CACHE_SERVER", "https://ge.example.com"); 68 | new BuildCacheConventions(env).execute(this.buildCache); 69 | assertThat(this.buildCache.remote.enabled).isTrue(); 70 | assertThat(this.buildCache.remote.server).isEqualTo("https://ge.example.com"); 71 | assertThat(this.buildCache.remote.push).isFalse(); 72 | } 73 | 74 | @Test 75 | void remoteCacheServerHasPrecedenceOverRemoteCacheUrl() { 76 | Map env = new HashMap<>(); 77 | env.put("GRADLE_ENTERPRISE_CACHE_URL", "https://ge-cache.example.com/cache/"); 78 | env.put("DEVELOCITY_CACHE_SERVER", "https://ge.example.com"); 79 | new BuildCacheConventions(env).execute(this.buildCache); 80 | assertThat(this.buildCache.remote.enabled).isTrue(); 81 | assertThat(this.buildCache.remote.server).isEqualTo("https://ge.example.com"); 82 | assertThat(this.buildCache.remote.push).isFalse(); 83 | } 84 | 85 | @Test 86 | void whenAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() { 87 | new BuildCacheConventions(Collections.singletonMap("DEVELOCITY_ACCESS_KEY", "ge.example.com=a1b2c3d4")) 88 | .execute(this.buildCache); 89 | assertThat(this.buildCache.remote.push).isFalse(); 90 | } 91 | 92 | @Test 93 | void whenAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsEnabled() { 94 | Map env = new HashMap<>(); 95 | env.put("DEVELOCITY_ACCESS_KEY", "ge.example.com=a1b2c3d4"); 96 | env.put("CI", "true"); 97 | new BuildCacheConventions(env).execute(this.buildCache); 98 | assertThat(this.buildCache.remote.push).isTrue(); 99 | } 100 | 101 | @Test 102 | void whenLegacyAccessTokenIsProvidedInALocalEnvironmentThenPushingToTheRemoteCacheIsNotEnabled() { 103 | new BuildCacheConventions(Collections.singletonMap("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4")) 104 | .execute(this.buildCache); 105 | assertThat(this.buildCache.remote.push).isFalse(); 106 | } 107 | 108 | @Test 109 | void whenLegacyAccessTokenIsProvidedInACiEnvironmentThenPushingToTheRemoteCacheIsEnabled() { 110 | Map env = new HashMap<>(); 111 | env.put("GRADLE_ENTERPRISE_ACCESS_KEY", "ge.example.com=a1b2c3d4"); 112 | env.put("CI", "true"); 113 | new BuildCacheConventions(env).execute(this.buildCache); 114 | assertThat(this.buildCache.remote.push).isTrue(); 115 | } 116 | 117 | private static final class TestConfigurableBuildCache implements ConfigurableBuildCache { 118 | 119 | private final TestLocalBuildCache local = new TestLocalBuildCache(); 120 | 121 | private final TestRemoteBuildCache remote = new TestRemoteBuildCache(); 122 | 123 | @Override 124 | public void local(Consumer local) { 125 | local.accept(this.local); 126 | } 127 | 128 | @Override 129 | public void remote(Consumer remote) { 130 | remote.accept(this.remote); 131 | } 132 | 133 | private static final class TestLocalBuildCache implements LocalBuildCache { 134 | 135 | private boolean enabled = false; 136 | 137 | @Override 138 | public void enable() { 139 | this.enabled = true; 140 | } 141 | 142 | } 143 | 144 | private static final class TestRemoteBuildCache implements RemoteBuildCache { 145 | 146 | private boolean enabled = false; 147 | 148 | private boolean push = false; 149 | 150 | private String server = null; 151 | 152 | @Override 153 | public void enable() { 154 | this.enabled = true; 155 | } 156 | 157 | @Override 158 | public void enablePush() { 159 | this.push = true; 160 | } 161 | 162 | @Override 163 | public void setServer(String server) { 164 | this.server = server; 165 | } 166 | 167 | } 168 | 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/test/java/io/spring/develocity/conventions/core/BuildScanConventionsTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.net.InetAddress; 20 | import java.net.UnknownHostException; 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.Collections; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.function.Consumer; 28 | import java.util.function.Function; 29 | 30 | import io.spring.develocity.conventions.core.ConfigurableBuildScan.ObfuscationConfigurer; 31 | import org.junit.jupiter.api.Test; 32 | 33 | import static org.assertj.core.api.Assertions.assertThat; 34 | import static org.assertj.core.api.Assertions.assertThatNoException; 35 | 36 | /** 37 | * Tests for {@link BuildScanConventions}. 38 | * 39 | * @author Andy Wilkinson 40 | */ 41 | class BuildScanConventionsTests { 42 | 43 | private final TestProcessRunner processRunner = new TestProcessRunner(); 44 | 45 | private final TestConfigurableBuildScan buildScan = new TestConfigurableBuildScan(); 46 | 47 | private final TestConfigurableDevelocity develocity = new TestConfigurableDevelocity(); 48 | 49 | @Test 50 | void capturingOfFileFingerprintsIsEnabled() { 51 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 52 | assertThat(this.buildScan.captureTaskInputFiles).isTrue(); 53 | } 54 | 55 | @Test 56 | void ipAddressesAreObfuscated() throws UnknownHostException { 57 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 58 | assertThat(this.buildScan.obfuscation.ipAddressesObfuscator).isNotNull(); 59 | List obfuscatedAddresses = this.buildScan.obfuscation.ipAddressesObfuscator 60 | .apply(Arrays.asList(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"))); 61 | assertThat(obfuscatedAddresses).containsExactly("0.0.0.0", "0.0.0.0"); 62 | } 63 | 64 | @Test 65 | void buildScansAreConfiguredToAlwaysPublishWhenAuthenticated() { 66 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 67 | assertThat(this.buildScan.publishIfAuthenticated).isTrue(); 68 | } 69 | 70 | @Test 71 | void buildScansAreConfiguredToPublishToGeSpringIo() { 72 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 73 | assertThat(this.develocity.getServer()).isEqualTo("https://ge.spring.io"); 74 | } 75 | 76 | @Test 77 | void whenBambooResultEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { 78 | new BuildScanConventions(this.processRunner, 79 | Collections.singletonMap("bamboo_resultsUrl", "https://bamboo.exampl.com")) 80 | .execute(this.develocity, this.buildScan); 81 | assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); 82 | } 83 | 84 | @Test 85 | void whenBambooResultEnvVarIsPresentThenBuildScanHasACiBuildLinkToIt() { 86 | new BuildScanConventions(this.processRunner, 87 | Collections.singletonMap("bamboo_resultsUrl", "https://bamboo.example.com")) 88 | .execute(this.develocity, this.buildScan); 89 | assertThat(this.buildScan.links).containsEntry("CI build", "https://bamboo.example.com"); 90 | } 91 | 92 | @Test 93 | void whenBambooResultEnvVarIsPresentThenBuildScanHasBambooAsTheCiProviderValue() { 94 | new BuildScanConventions(this.processRunner, 95 | Collections.singletonMap("bamboo_resultsUrl", "https://bamboo.example.com")) 96 | .execute(this.develocity, this.buildScan); 97 | assertThat(this.buildScan.values).containsEntry("CI provider", "Bamboo"); 98 | } 99 | 100 | @Test 101 | void whenCircleBuildUrlEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { 102 | new BuildScanConventions(this.processRunner, 103 | Collections.singletonMap("CIRCLE_BUILD_URL", "https://circleci.example.com/gh/org/project/123")) 104 | .execute(this.develocity, this.buildScan); 105 | assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); 106 | } 107 | 108 | @Test 109 | void whenCircleBuildUrlEnvVarIsPresentThenBuildScanHasACiBuildLinkToIt() { 110 | new BuildScanConventions(this.processRunner, 111 | Collections.singletonMap("CIRCLE_BUILD_URL", "https://circleci.example.com/gh/org/project/123")) 112 | .execute(this.develocity, this.buildScan); 113 | assertThat(this.buildScan.links).containsEntry("CI build", "https://circleci.example.com/gh/org/project/123"); 114 | } 115 | 116 | @Test 117 | void whenCircleBuildUrlEnvVarIsPresentThenBuildScanHasCircleCiAsTheCiProviderValue() { 118 | new BuildScanConventions(this.processRunner, 119 | Collections.singletonMap("CIRCLE_BUILD_URL", "https://circleci.example.com/gh/org/project/123")) 120 | .execute(this.develocity, this.buildScan); 121 | assertThat(this.buildScan.values).containsEntry("CI provider", "CircleCI"); 122 | } 123 | 124 | @Test 125 | void whenJenkinsUrlEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { 126 | new BuildScanConventions(this.processRunner, 127 | Collections.singletonMap("JENKINS_URL", "https://jenkins.example.com")) 128 | .execute(this.develocity, this.buildScan); 129 | assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); 130 | } 131 | 132 | @Test 133 | void whenJenkinsUrlAndBuildUrlEnvVarsArePresentThenBuildScanHasACiBuildLinkToBuildUrl() { 134 | Map env = new HashMap<>(); 135 | env.put("JENKINS_URL", "https://jenkins.example.com"); 136 | env.put("BUILD_URL", "https://jenkins.example.com/builds/123"); 137 | new BuildScanConventions(this.processRunner, env).execute(this.develocity, this.buildScan); 138 | assertThat(this.buildScan.links).containsEntry("CI build", "https://jenkins.example.com/builds/123"); 139 | } 140 | 141 | @Test 142 | void whenJenkinsUrlEnvVarIsPresentThenBuildScanHasJenkinsAsTheCiProviderValue() { 143 | new BuildScanConventions(this.processRunner, 144 | Collections.singletonMap("JENKINS_URL", "https://jenkins.example.com")) 145 | .execute(this.develocity, this.buildScan); 146 | assertThat(this.buildScan.values).containsEntry("CI provider", "Jenkins"); 147 | } 148 | 149 | @Test 150 | void whenCiEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { 151 | new BuildScanConventions(this.processRunner, Collections.singletonMap("CI", null)).execute(this.develocity, 152 | this.buildScan); 153 | assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); 154 | } 155 | 156 | @Test 157 | void whenCiEnvVarIsPresentThenBuildScanHasConcourseAsTheCiProviderValue() { 158 | new BuildScanConventions(this.processRunner, Collections.singletonMap("CI", null)).execute(this.develocity, 159 | this.buildScan); 160 | assertThat(this.buildScan.values).containsEntry("CI provider", "Concourse"); 161 | } 162 | 163 | @Test 164 | void whenGitHubActionsEnvVarIsPresentThenBuildScanIsTaggedWithCiNotLocal() { 165 | new BuildScanConventions(this.processRunner, Collections.singletonMap("GITHUB_ACTIONS", "true")) 166 | .execute(this.develocity, this.buildScan); 167 | assertThat(this.buildScan.tags).contains("CI").doesNotContain("Local"); 168 | } 169 | 170 | @Test 171 | void whenGitHubActionsEnvVarsArePresentThenBuildScanHasACiBuildLinkToIt() { 172 | Map env = new HashMap<>(); 173 | env.put("GITHUB_ACTIONS", "true"); 174 | env.put("GITHUB_SERVER_URL", "https://github.com"); 175 | env.put("GITHUB_REPOSITORY", "spring-projects/spring-boot"); 176 | env.put("GITHUB_RUN_ID", "1234567890"); 177 | new BuildScanConventions(this.processRunner, env).execute(this.develocity, this.buildScan); 178 | assertThat(this.buildScan.links).containsEntry("CI build", 179 | "https://github.com/spring-projects/spring-boot/actions/runs/1234567890"); 180 | } 181 | 182 | @Test 183 | void whenGitHubActionsEnvVarIsPresentThenBuildScanHasGitHubActionsAsTheCiProviderValue() { 184 | new BuildScanConventions(this.processRunner, Collections.singletonMap("GITHUB_ACTIONS", "true")) 185 | .execute(this.develocity, this.buildScan); 186 | assertThat(this.buildScan.values).containsEntry("CI provider", "GitHub Actions"); 187 | } 188 | 189 | @Test 190 | void whenNoCiIndicatorsArePresentThenBuildScanIsTaggedWithLocalNotCi() { 191 | new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); 192 | assertThat(this.buildScan.tags).contains("Local").doesNotContain("CI"); 193 | } 194 | 195 | @Test 196 | void whenNoCiIndicatorsArePresentThenBuildScanHasNoCiBuildLink() { 197 | new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); 198 | assertThat(this.buildScan.links).doesNotContainKey("CI build"); 199 | } 200 | 201 | @Test 202 | void whenNoCiIndicatorsArePresentThenBuildScanHasNoCiProviderValue() { 203 | new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); 204 | assertThat(this.buildScan.values).doesNotContainKey("CI provider"); 205 | } 206 | 207 | @Test 208 | void buildScanIsTaggedWithJdkVersion() { 209 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 210 | assertThat(this.buildScan.tags).contains("JDK-" + System.getProperty("java.specification.version")); 211 | } 212 | 213 | @Test 214 | void buildScanIsTaggedWithOperatingSystem() { 215 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 216 | assertThat(this.buildScan.tags).contains(System.getProperty("os.name")); 217 | } 218 | 219 | @Test 220 | void whenBranchEnvVarIsPresentThenBuildScanIsTaggedAndConfiguredWithCustomValue() { 221 | new BuildScanConventions(this.processRunner, Collections.singletonMap("BRANCH", "1.1.x")) 222 | .execute(this.develocity, this.buildScan); 223 | assertThat(this.buildScan.tags).contains("1.1.x"); 224 | assertThat(this.buildScan.values).containsEntry("Git branch", "1.1.x"); 225 | } 226 | 227 | @Test 228 | void whenBranchEnvVarIsNotPresentThenBuildScanIsTaggedWithBranchFromGit() { 229 | this.processRunner.commandLineOutput.put(Arrays.asList("git", "rev-parse", "--abbrev-ref", "HEAD"), "1.2.x"); 230 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 231 | assertThat(this.buildScan.tags).contains("1.2.x"); 232 | assertThat(this.buildScan.values).containsEntry("Git branch", "1.2.x"); 233 | } 234 | 235 | @Test 236 | void buildScanHasGitCommitIdCustomValueAndLinkToBuildScansForTheSameCommit() { 237 | this.processRunner.commandLineOutput.put(Arrays.asList("git", "rev-parse", "--short=8", "--verify", "HEAD"), 238 | "79ce52f8"); 239 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 240 | assertThat(this.buildScan.values).containsEntry("Git commit", "79ce52f8"); 241 | assertThat(this.buildScan.links).containsEntry("Git commit build scans", 242 | "https://ge.spring.io/scans?search.names=Git+commit&search.values=79ce52f8"); 243 | } 244 | 245 | @Test 246 | void whenGitStatusIsCleanThenBuildScanIsNotTaggedDirtyAndHasNotGitStatusCustomValue() { 247 | this.processRunner.commandLineOutput.put(Arrays.asList("git", "status", "--porcelain"), ""); 248 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 249 | assertThat(this.buildScan.tags).doesNotContain("dirty"); 250 | assertThat(this.buildScan.values).doesNotContainKey("Git status"); 251 | } 252 | 253 | @Test 254 | void whenGitStatusIsDirtyThenBuildScanIsTaggedDirtyAndHasGitStatusCustomValue() { 255 | this.processRunner.commandLineOutput.put(Arrays.asList("git", "status", "--porcelain"), " M build.gradle"); 256 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 257 | assertThat(this.buildScan.tags).contains("dirty"); 258 | assertThat(this.buildScan.values).containsEntry("Git status", "M build.gradle"); 259 | } 260 | 261 | @Test 262 | void whenGitIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { 263 | this.processRunner.failures.put(Arrays.asList("git", "status", "--porcelain"), 264 | new RuntimeException("git is not available")); 265 | assertThatNoException() 266 | .isThrownBy(() -> new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan)); 267 | assertThat(this.buildScan.values).doesNotContainKey("Git status"); 268 | } 269 | 270 | @Test 271 | void buildScanHasDockerCustomValue() { 272 | this.processRunner.commandLineOutput.put(Arrays.asList("docker", "--version"), 273 | "Docker version 20.10.24, build 297e128"); 274 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 275 | assertThat(this.buildScan.values).containsEntry("Docker", "Docker version 20.10.24, build 297e128"); 276 | } 277 | 278 | @Test 279 | void whenDockerIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { 280 | this.processRunner.failures.put(Arrays.asList("docker", "--version"), 281 | new RuntimeException("docker is not available")); 282 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 283 | assertThatNoException() 284 | .isThrownBy(() -> new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan)); 285 | assertThat(this.buildScan.values).doesNotContainKey("Docker"); 286 | } 287 | 288 | @Test 289 | void buildScanHasDockerComposeCustomValue() { 290 | this.processRunner.commandLineOutput.put(Arrays.asList("docker", "compose", "version"), 291 | "Docker Compose version v2.17.2"); 292 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 293 | assertThat(this.buildScan.values).containsEntry("Docker Compose", "Docker Compose version v2.17.2"); 294 | } 295 | 296 | @Test 297 | void whenDockerComposeIsNotAvailableThenConventionsCanBeAppliedWithoutFailure() { 298 | this.processRunner.failures.put(Arrays.asList("docker", "compose", "version"), 299 | new RuntimeException("docker compose is not available")); 300 | new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan); 301 | assertThatNoException() 302 | .isThrownBy(() -> new BuildScanConventions(this.processRunner).execute(this.develocity, this.buildScan)); 303 | assertThat(this.buildScan.values).doesNotContainKey("Docker Compose"); 304 | } 305 | 306 | @Test 307 | void whenBuildingLocallyThenBackgroundUploadIsEnabled() { 308 | new BuildScanConventions(this.processRunner, Collections.emptyMap()).execute(this.develocity, this.buildScan); 309 | assertThat(this.buildScan.uploadInBackground).isTrue(); 310 | } 311 | 312 | @Test 313 | void whenBuildingOnCiThenBackgroundUploadIsDisabled() { 314 | new BuildScanConventions(this.processRunner, Collections.singletonMap("CI", null)).execute(this.develocity, 315 | this.buildScan); 316 | assertThat(this.buildScan.uploadInBackground).isFalse(); 317 | } 318 | 319 | public static final class TestConfigurableBuildScan implements ConfigurableBuildScan { 320 | 321 | private final TestObfuscationConfigurer obfuscation = new TestObfuscationConfigurer(); 322 | 323 | private final List tags = new ArrayList<>(); 324 | 325 | private final Map values = new HashMap<>(); 326 | 327 | private final Map links = new HashMap<>(); 328 | 329 | private boolean captureTaskInputFiles; 330 | 331 | private boolean publishIfAuthenticated; 332 | 333 | private boolean uploadInBackground = true; 334 | 335 | @Override 336 | public void background(Consumer action) { 337 | action.accept(this); 338 | } 339 | 340 | @Override 341 | public void link(String name, String url) { 342 | this.links.put(name, url); 343 | } 344 | 345 | @Override 346 | public void publishIfAuthenticated() { 347 | this.publishIfAuthenticated = true; 348 | } 349 | 350 | @Override 351 | public void captureInputFiles(boolean capture) { 352 | this.captureTaskInputFiles = capture; 353 | } 354 | 355 | @Override 356 | public void tag(String tag) { 357 | this.tags.add(tag); 358 | } 359 | 360 | @Override 361 | public void value(String name, String value) { 362 | this.values.put(name, value); 363 | } 364 | 365 | @Override 366 | public void uploadInBackground(boolean uploadInBackground) { 367 | this.uploadInBackground = uploadInBackground; 368 | } 369 | 370 | @Override 371 | public void obfuscation(Consumer configurer) { 372 | configurer.accept(this.obfuscation); 373 | } 374 | 375 | } 376 | 377 | private static final class TestObfuscationConfigurer implements ObfuscationConfigurer { 378 | 379 | private Function, ? extends List> ipAddressesObfuscator; 380 | 381 | @Override 382 | public void ipAddresses(Function, ? extends List> obfuscator) { 383 | this.ipAddressesObfuscator = obfuscator; 384 | } 385 | 386 | } 387 | 388 | private static final class TestConfigurableDevelocity implements ConfigurableDevelocity { 389 | 390 | private String server; 391 | 392 | @Override 393 | public String getServer() { 394 | return this.server; 395 | } 396 | 397 | @Override 398 | public void setServer(String server) { 399 | this.server = server; 400 | } 401 | 402 | } 403 | 404 | } 405 | -------------------------------------------------------------------------------- /develocity-conventions-core/src/test/java/io/spring/develocity/conventions/core/TestProcessRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.core; 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.function.Consumer; 25 | 26 | import org.mockito.ArgumentCaptor; 27 | 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.verify; 30 | 31 | /** 32 | * A {@link ProcessRunner} for unit testing. 33 | * 34 | * @author Andy Wilkinson 35 | */ 36 | final class TestProcessRunner implements ProcessRunner { 37 | 38 | final Map, String> commandLineOutput = new HashMap<>(); 39 | 40 | final Map, RuntimeException> failures = new HashMap<>(); 41 | 42 | @Override 43 | public void run(Consumer configurer) { 44 | ProcessSpec processSpec = mock(ProcessSpec.class); 45 | configurer.accept(processSpec); 46 | ArgumentCaptor commandLineCaptor = ArgumentCaptor.forClass(Object.class); 47 | verify(processSpec).commandLine(commandLineCaptor.capture()); 48 | ArgumentCaptor standardOut = ArgumentCaptor.forClass(OutputStream.class); 49 | verify(processSpec).standardOutput(standardOut.capture()); 50 | List commandLine = commandLineCaptor.getAllValues(); 51 | RuntimeException failure = this.failures.get(commandLine); 52 | if (failure != null) { 53 | failure.fillInStackTrace(); 54 | throw new RunFailedException(failure); 55 | } 56 | String output = this.commandLineOutput.get(commandLine); 57 | if (output != null) { 58 | try { 59 | standardOut.getValue().write(output.getBytes()); 60 | } 61 | catch (IOException ex) { 62 | throw new RuntimeException(ex); 63 | } 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "build-conventions" 3 | id "java-gradle-plugin" 4 | id "maven-publish" 5 | } 6 | 7 | description = "Develocity Conventions Gradle plugin" 8 | 9 | repositories { 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | 14 | dependencies { 15 | implementation(project(":develocity-conventions-core")) 16 | implementation("com.gradle:develocity-gradle-plugin:3.19.2") 17 | 18 | testImplementation("org.assertj:assertj-core:3.27.2") 19 | testImplementation("org.junit.jupiter:junit-jupiter:5.13.0") 20 | testImplementation("org.mockito:mockito-core:4.11.0") 21 | 22 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0") 23 | } 24 | 25 | gradlePlugin { 26 | plugins { 27 | develocityConventionsPlugin { 28 | displayName = project.description 29 | description = project.description 30 | id = 'io.spring.develocity.conventions' 31 | implementationClass = 'io.spring.develocity.conventions.gradle.DevelocityConventionsPlugin' 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/AnonymousPublicationBuildScanConventions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.util.Map; 20 | 21 | import io.spring.develocity.conventions.core.BuildScanConventions; 22 | import io.spring.develocity.conventions.core.ConfigurableBuildScan; 23 | import io.spring.develocity.conventions.core.ConfigurableDevelocity; 24 | import io.spring.develocity.conventions.core.ProcessRunner; 25 | 26 | /** 27 | * Conventions for build scans that are published anonymously to 28 | * {@code https://scans.gradle.com}. 29 | * 30 | * @author Andy Wilkinson 31 | */ 32 | class AnonymousPublicationBuildScanConventions extends BuildScanConventions { 33 | 34 | AnonymousPublicationBuildScanConventions(ProcessRunner processRunner) { 35 | super(processRunner); 36 | } 37 | 38 | AnonymousPublicationBuildScanConventions(ProcessRunner processRunner, Map env) { 39 | super(processRunner, env); 40 | } 41 | 42 | @Override 43 | protected void configurePublishing(ConfigurableDevelocity develocity, ConfigurableBuildScan buildScan) { 44 | // Use Gradle's defaults 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/DevelocityConventionsPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.io.File; 20 | import java.io.FileReader; 21 | import java.io.IOException; 22 | import java.io.Reader; 23 | import java.io.UncheckedIOException; 24 | import java.util.Properties; 25 | 26 | import javax.inject.Inject; 27 | 28 | import com.gradle.develocity.agent.gradle.DevelocityConfiguration; 29 | import com.gradle.develocity.agent.gradle.DevelocityPlugin; 30 | import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; 31 | import io.spring.develocity.conventions.core.BuildCacheConventions; 32 | import io.spring.develocity.conventions.core.BuildScanConventions; 33 | import org.gradle.StartParameter; 34 | import org.gradle.api.Plugin; 35 | import org.gradle.api.initialization.Settings; 36 | import org.gradle.api.internal.ProcessOperations; 37 | 38 | /** 39 | * {@link Plugin plugin} for configuring the use of Develocity hosted at 40 | * ge.spring.io. 41 | * 42 | * @author Andy Wilkinson 43 | */ 44 | public class DevelocityConventionsPlugin implements Plugin { 45 | 46 | private final ProcessOperations processOperations; 47 | 48 | @Inject 49 | public DevelocityConventionsPlugin(ProcessOperations processOperations) { 50 | this.processOperations = processOperations; 51 | } 52 | 53 | @Override 54 | public void apply(Settings settings) { 55 | settings.getPlugins().apply(DevelocityPlugin.class); 56 | DevelocityConfiguration extension = settings.getExtensions().getByType(DevelocityConfiguration.class); 57 | if (!isOssBuild(settings)) { 58 | extension 59 | .buildScan((buildScan) -> buildScan.publishing((publishing) -> publishing.onlyIf((context) -> false))); 60 | return; 61 | } 62 | if (isBuildScanEnabled(settings)) { 63 | configureBuildScanConventions(extension, extension.getBuildScan(), settings.getStartParameter(), 64 | settings.getRootDir()); 65 | } 66 | if (settings.getStartParameter().isBuildCacheEnabled()) { 67 | settings.buildCache((buildCacheConfiguration) -> new BuildCacheConventions() 68 | .execute(new GradleConfigurableBuildCache(extension.getBuildCache(), buildCacheConfiguration))); 69 | } 70 | } 71 | 72 | private boolean isOssBuild(Settings settings) { 73 | Properties properties = new Properties(); 74 | File propertiesFile = new File(settings.getRootDir(), "gradle.properties"); 75 | if (propertiesFile.exists()) { 76 | try (Reader reader = new FileReader(propertiesFile)) { 77 | properties.load(reader); 78 | } 79 | catch (IOException ex) { 80 | throw new UncheckedIOException(ex); 81 | } 82 | } 83 | String buildType = properties.getProperty("spring.build-type"); 84 | return buildType == null || "oss".equals(buildType); 85 | } 86 | 87 | private boolean isBuildScanEnabled(Settings settings) { 88 | StartParameter startParameter = settings.getStartParameter(); 89 | return !startParameter.isNoBuildScan() && !containsPropertiesTask(startParameter); 90 | } 91 | 92 | private boolean containsPropertiesTask(StartParameter startParameter) { 93 | for (String taskName : startParameter.getTaskNames()) { 94 | if (taskName.equals("properties") || taskName.endsWith(":properties")) { 95 | return true; 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | private void configureBuildScanConventions(DevelocityConfiguration develocity, BuildScanConfiguration buildScan, 102 | StartParameter startParameter, File rootDir) { 103 | ProcessOperationsProcessRunner processRunner = new ProcessOperationsProcessRunner( 104 | new WorkingDirectoryProcessOperations(this.processOperations, rootDir)); 105 | if (startParameter.isBuildScan()) { 106 | new AnonymousPublicationBuildScanConventions(processRunner) { 107 | 108 | @Override 109 | protected String getJdkVersion() { 110 | String toolchainVersion = startParameter.getProjectProperties().get("toolchainVersion"); 111 | return (toolchainVersion != null) ? toolchainVersion : super.getJdkVersion(); 112 | } 113 | 114 | }.execute(new GradleConfigurableDevelocity(develocity), new GradleConfigurableBuildScan(buildScan)); 115 | } 116 | else { 117 | new BuildScanConventions(processRunner) { 118 | 119 | @Override 120 | protected String getJdkVersion() { 121 | String toolchainVersion = startParameter.getProjectProperties().get("toolchainVersion"); 122 | return (toolchainVersion != null) ? toolchainVersion : super.getJdkVersion(); 123 | } 124 | 125 | }.execute(new GradleConfigurableDevelocity(develocity), new GradleConfigurableBuildScan(buildScan)); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/GradleConfigurableBuildCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; 22 | import io.spring.develocity.conventions.core.ConfigurableBuildCache; 23 | import org.gradle.caching.configuration.BuildCacheConfiguration; 24 | import org.gradle.caching.local.DirectoryBuildCache; 25 | 26 | /** 27 | * A {@link ConfigurableBuildCache} for Gradle builds. 28 | * 29 | * @author Andy Wilkinson 30 | */ 31 | class GradleConfigurableBuildCache implements ConfigurableBuildCache { 32 | 33 | private final Class buildCacheType; 34 | 35 | private final BuildCacheConfiguration buildCache; 36 | 37 | GradleConfigurableBuildCache(Class buildCacheType, 38 | BuildCacheConfiguration buildCache) { 39 | this.buildCacheType = buildCacheType; 40 | this.buildCache = buildCache; 41 | } 42 | 43 | @Override 44 | public void local(Consumer local) { 45 | local.accept(new GradleLocalBuildCache(this.buildCache.getLocal())); 46 | } 47 | 48 | @Override 49 | public void remote(Consumer remote) { 50 | remote.accept(new GradleRemoteBuildCache(this.buildCache.remote(this.buildCacheType))); 51 | } 52 | 53 | private static final class GradleLocalBuildCache implements LocalBuildCache { 54 | 55 | private final DirectoryBuildCache localBuildCache; 56 | 57 | private GradleLocalBuildCache(DirectoryBuildCache localBuildCache) { 58 | this.localBuildCache = localBuildCache; 59 | } 60 | 61 | @Override 62 | public void enable() { 63 | this.localBuildCache.setEnabled(true); 64 | } 65 | 66 | } 67 | 68 | private static final class GradleRemoteBuildCache implements RemoteBuildCache { 69 | 70 | private final DevelocityBuildCache remoteBuildCache; 71 | 72 | private GradleRemoteBuildCache(DevelocityBuildCache buildCache) { 73 | this.remoteBuildCache = buildCache; 74 | } 75 | 76 | @Override 77 | public void enable() { 78 | this.remoteBuildCache.setEnabled(true); 79 | } 80 | 81 | @Override 82 | public void enablePush() { 83 | this.remoteBuildCache.setPush(true); 84 | } 85 | 86 | @Override 87 | public void setServer(String server) { 88 | this.remoteBuildCache.setServer(server); 89 | } 90 | 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/GradleConfigurableBuildScan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.net.InetAddress; 20 | import java.util.List; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; 25 | import com.gradle.develocity.agent.gradle.scan.BuildScanDataObfuscationConfiguration; 26 | import com.gradle.develocity.agent.gradle.scan.BuildScanPublishingConfiguration.PublishingContext; 27 | import io.spring.develocity.conventions.core.ConfigurableBuildScan; 28 | 29 | /** 30 | * A {@link ConfigurableBuildScan} for Gradle builds. 31 | * 32 | * @author Andy Wilkinson 33 | */ 34 | class GradleConfigurableBuildScan implements ConfigurableBuildScan { 35 | 36 | private final BuildScanConfiguration buildScan; 37 | 38 | GradleConfigurableBuildScan(BuildScanConfiguration buildScan) { 39 | this.buildScan = buildScan; 40 | } 41 | 42 | @Override 43 | public void captureInputFiles(boolean capture) { 44 | this.buildScan.capture((settings) -> settings.getFileFingerprints().set(capture)); 45 | } 46 | 47 | @Override 48 | public void obfuscation(Consumer configurer) { 49 | configurer.accept(new GradleObfuscationConfigurer(this.buildScan.getObfuscation())); 50 | } 51 | 52 | @Override 53 | public void publishIfAuthenticated() { 54 | this.buildScan.publishing((publishing) -> publishing.onlyIf(PublishingContext::isAuthenticated)); 55 | } 56 | 57 | @Override 58 | public void uploadInBackground(boolean enabled) { 59 | this.buildScan.getUploadInBackground().set(enabled); 60 | } 61 | 62 | @Override 63 | public void link(String name, String url) { 64 | this.buildScan.link(name, url); 65 | } 66 | 67 | @Override 68 | public void tag(String tag) { 69 | this.buildScan.tag(tag); 70 | } 71 | 72 | @Override 73 | public void value(String name, String value) { 74 | this.buildScan.value(name, value); 75 | } 76 | 77 | @Override 78 | public void background(Consumer backgroundConfigurer) { 79 | this.buildScan 80 | .background((buildScan) -> backgroundConfigurer.accept(new GradleConfigurableBuildScan(buildScan))); 81 | } 82 | 83 | private static final class GradleObfuscationConfigurer implements ObfuscationConfigurer { 84 | 85 | private final BuildScanDataObfuscationConfiguration obfuscation; 86 | 87 | private GradleObfuscationConfigurer(BuildScanDataObfuscationConfiguration obfuscation) { 88 | this.obfuscation = obfuscation; 89 | } 90 | 91 | @Override 92 | public void ipAddresses(Function, ? extends List> obfuscator) { 93 | this.obfuscation.ipAddresses(obfuscator); 94 | } 95 | 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/GradleConfigurableDevelocity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import com.gradle.develocity.agent.gradle.DevelocityConfiguration; 20 | import io.spring.develocity.conventions.core.ConfigurableDevelocity; 21 | 22 | /** 23 | * {@link ConfigurableDevelocity} implementation for Gradle builds. 24 | * 25 | * @author Andy Wilkinson 26 | */ 27 | class GradleConfigurableDevelocity implements ConfigurableDevelocity { 28 | 29 | private final DevelocityConfiguration develocity; 30 | 31 | GradleConfigurableDevelocity(DevelocityConfiguration develocity) { 32 | this.develocity = develocity; 33 | } 34 | 35 | @Override 36 | public String getServer() { 37 | return this.develocity.getServer().getOrNull(); 38 | } 39 | 40 | @Override 41 | public void setServer(String server) { 42 | this.develocity.getServer().set(server); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/ProcessOperationsProcessRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.io.OutputStream; 20 | import java.util.function.Consumer; 21 | 22 | import io.spring.develocity.conventions.core.ProcessRunner; 23 | import org.gradle.api.internal.ProcessOperations; 24 | import org.gradle.process.ExecSpec; 25 | 26 | /** 27 | * A {@link ProcessRunner} that delegates to Gradle's internal {@link ProcessOperations}. 28 | * 29 | * @author Andy Wilkinson 30 | */ 31 | class ProcessOperationsProcessRunner implements ProcessRunner { 32 | 33 | private final ProcessOperations processOperations; 34 | 35 | ProcessOperationsProcessRunner(ProcessOperations processOperations) { 36 | this.processOperations = processOperations; 37 | } 38 | 39 | @Override 40 | public void run(Consumer configurer) { 41 | try { 42 | this.processOperations.exec((spec) -> configurer.accept(new ExecSpecProcessSpec(spec))); 43 | } 44 | catch (Exception ex) { 45 | throw new RunFailedException(ex); 46 | } 47 | } 48 | 49 | private final class ExecSpecProcessSpec implements ProcessSpec { 50 | 51 | private final ExecSpec execSpec; 52 | 53 | private ExecSpecProcessSpec(ExecSpec execSpec) { 54 | this.execSpec = execSpec; 55 | } 56 | 57 | @Override 58 | public void commandLine(Object... commandLine) { 59 | this.execSpec.commandLine(commandLine); 60 | } 61 | 62 | @Override 63 | public void standardOutput(OutputStream standardOutput) { 64 | this.execSpec.setStandardOutput(standardOutput); 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/WorkingDirectoryProcessOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.io.File; 20 | 21 | import org.gradle.api.Action; 22 | import org.gradle.api.internal.ProcessOperations; 23 | import org.gradle.process.ExecResult; 24 | import org.gradle.process.ExecSpec; 25 | import org.gradle.process.JavaExecSpec; 26 | 27 | /** 28 | * {@link ProcessOperations} decorator that configures the working directory. 29 | * 30 | * @author Andy Wilkinson 31 | */ 32 | class WorkingDirectoryProcessOperations implements ProcessOperations { 33 | 34 | private final ProcessOperations delegate; 35 | 36 | private final File workingDir; 37 | 38 | WorkingDirectoryProcessOperations(ProcessOperations delegate, File workingDir) { 39 | this.delegate = delegate; 40 | this.workingDir = workingDir; 41 | } 42 | 43 | @Override 44 | public ExecResult exec(Action action) { 45 | return this.delegate.exec((spec) -> { 46 | spec.setWorkingDir(this.workingDir); 47 | action.execute(spec); 48 | }); 49 | } 50 | 51 | @Override 52 | public ExecResult javaexec(Action action) { 53 | return this.delegate.javaexec((spec) -> { 54 | spec.setWorkingDir(this.workingDir); 55 | action.execute(spec); 56 | }); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/main/java/io/spring/develocity/conventions/gradle/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Develocity conventions for Spring projects built with Gradle. 19 | */ 20 | package io.spring.develocity.conventions.gradle; 21 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/AnonymousPublicationBuildScanConventionsTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | /** 24 | * Tests for {@link AnonymousPublicationBuildScanConventions}. 25 | * 26 | * @author Andy Wilkinson 27 | */ 28 | class AnonymousPublicationBuildScanConventionsTests { 29 | 30 | private final TestProcessRunner processRunner = new TestProcessRunner(); 31 | 32 | private final TestDevelocityConfiguration develocity = new TestDevelocityConfiguration(); 33 | 34 | private final TestBuildScanConfiguration buildScan = new TestBuildScanConfiguration(); 35 | 36 | @Test 37 | void buildScansAreConfiguredToUseDefaultPublicationBehaviour() { 38 | new AnonymousPublicationBuildScanConventions(this.processRunner).execute( 39 | new GradleConfigurableDevelocity(this.develocity), new GradleConfigurableBuildScan(this.buildScan)); 40 | assertThat(this.buildScan.publishing.predicate).isNull(); 41 | } 42 | 43 | @Test 44 | void buildScansAreConfiguredToPublishToDefaultServer() { 45 | new AnonymousPublicationBuildScanConventions(this.processRunner).execute( 46 | new GradleConfigurableDevelocity(this.develocity), new GradleConfigurableBuildScan(this.buildScan)); 47 | assertThat(this.develocity.getServer().getOrNull()).isNull(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/DevelocityConventionsPluginIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.io.File; 20 | import java.io.FileReader; 21 | import java.io.FileWriter; 22 | import java.io.IOException; 23 | import java.io.PrintWriter; 24 | import java.io.Reader; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import java.util.Properties; 29 | import java.util.function.Consumer; 30 | 31 | import com.gradle.develocity.agent.gradle.DevelocityPlugin; 32 | import io.spring.develocity.conventions.core.ConfigurableBuildScan; 33 | import org.gradle.testkit.runner.BuildResult; 34 | import org.gradle.testkit.runner.GradleRunner; 35 | import org.junit.jupiter.api.Test; 36 | import org.junit.jupiter.api.io.TempDir; 37 | 38 | import static org.assertj.core.api.Assertions.assertThat; 39 | 40 | /** 41 | * Integrations tests for {@link DevelocityConventionsPlugin}. 42 | * 43 | * @author Andy Wilkinson 44 | */ 45 | class DevelocityConventionsPluginIntegrationTests { 46 | 47 | @Test 48 | void whenThePluginIsAppliedThenBuildScanConventionsAreApplied(@TempDir File projectDir) { 49 | prepareProject(projectDir); 50 | BuildResult result = build(projectDir, "verifyBuildScanConfig"); 51 | assertThat(result.getOutput()).contains("Build scan server: https://ge.spring.io"); 52 | assertThat(result.getOutput()).contains("Capture task input files: true"); 53 | } 54 | 55 | @Test 56 | void whenThePluginIsAppliedThenBuildCacheConventionsAreApplied(@TempDir File projectDir) { 57 | prepareProject(projectDir); 58 | BuildResult result = build(projectDir, "verifyBuildCacheConfig"); 59 | assertThat(result.getOutput()).contains("Build cache server: https://ge.spring.io"); 60 | } 61 | 62 | @Test 63 | void whenThePluginIsAppliedAndBuildScansAreDisabledThenBuildScanConventionsAreNotApplied(@TempDir File projectDir) { 64 | prepareProject(projectDir); 65 | BuildResult result = build(projectDir, "verifyBuildScanConfig", "--no-scan"); 66 | assertThat(result.getOutput()).contains("Build scan server: null"); 67 | assertThat(result.getOutput()).contains("Capture task input files: false"); 68 | } 69 | 70 | @Test 71 | void whenThePluginIsAppliedAndTheSpringBuildTypeIsNotOssThenBuildScanConventionsAreNotApplied( 72 | @TempDir File projectDir) { 73 | prepareProject(projectDir); 74 | write(new File(projectDir, "gradle.properties"), (writer) -> writer.println("spring.build-type=other")); 75 | BuildResult result = build(projectDir, "verifyBuildScanConfig"); 76 | assertThat(result.getOutput()).contains("Build scan server: null"); 77 | assertThat(result.getOutput()).contains("Capture task input files: false"); 78 | } 79 | 80 | @Test 81 | void whenThePluginIsAppliedAndTheSpringBuildTypeIsOssThenBuildScanConventionsAreApplied(@TempDir File projectDir) { 82 | prepareProject(projectDir); 83 | write(new File(projectDir, "gradle.properties"), (writer) -> writer.println("spring.build-type=oss")); 84 | BuildResult result = build(projectDir, "verifyBuildScanConfig"); 85 | assertThat(result.getOutput()).contains("Build scan server: https://ge.spring.io"); 86 | assertThat(result.getOutput()).contains("Capture task input files: true"); 87 | } 88 | 89 | @Test 90 | void whenThePluginIsAppliedAndPropertiesTaskIsExecutedThenBuildScanConventionsAreNotApplied( 91 | @TempDir File projectDir) { 92 | prepareProject(projectDir); 93 | BuildResult result = build(projectDir, "properties", "verifyBuildScanConfig", "--no-scan"); 94 | assertThat(result.getOutput()).contains("Build scan server: null"); 95 | assertThat(result.getOutput()).contains("Capture task input files: false"); 96 | } 97 | 98 | @Test 99 | void givenMultiProjectBuildWhenThePluginIsAppliedAndPropertiesTaskIsExecutedThenBuildScanConventionsAreNotApplied( 100 | @TempDir File projectDir) { 101 | prepareMultiModuleProject(projectDir); 102 | BuildResult result = build(projectDir, "sub:properties", "sub:verifyBuildScanConfig", "--no-scan"); 103 | assertThat(result.getOutput()).contains("Build scan server: null"); 104 | assertThat(result.getOutput()).contains("Capture task input files: false"); 105 | } 106 | 107 | @Test 108 | void whenThePluginIsAppliedAndScanIsSpecifiedThenServerIsNotCustomized(@TempDir File projectDir) { 109 | prepareProject(projectDir); 110 | BuildResult result = build(projectDir, "verifyBuildScanConfig", "--scan"); 111 | assertThat(result.getOutput()).contains("Build scan server: null"); 112 | assertThat(result.getOutput()).contains("Capture task input files: true"); 113 | } 114 | 115 | @Test 116 | void whenThePluginIsAppliedAndBuildCacheIsDisabledThenBuildCacheConventionsAreNotApplied(@TempDir File projectDir) { 117 | prepareProject(projectDir); 118 | BuildResult result = build(projectDir, "verifyBuildCacheConfig", "--no-build-cache"); 119 | assertThat(result.getOutput()).contains("Build cache server: null"); 120 | } 121 | 122 | @Test 123 | void whenThePluginIsAppliedAndTheSpringBuildTypeIsNotOssThenBuildCacheConventionsAreNotApplied( 124 | @TempDir File projectDir) { 125 | prepareProject(projectDir); 126 | write(new File(projectDir, "gradle.properties"), (writer) -> writer.println("spring.build-type=other")); 127 | BuildResult result = build(projectDir, "verifyBuildCacheConfig"); 128 | assertThat(result.getOutput()).contains("Build cache server: null"); 129 | } 130 | 131 | @Test 132 | void whenThePluginIsAppliedAndTheSpringBuildTypeIsOssThenBuildCacheConventionsAreApplied(@TempDir File projectDir) { 133 | prepareProject(projectDir); 134 | write(new File(projectDir, "gradle.properties"), (writer) -> writer.println("spring.build-type=oss")); 135 | BuildResult result = build(projectDir, "verifyBuildCacheConfig"); 136 | assertThat(result.getOutput()).contains("Build cache server: https://ge.spring.io"); 137 | } 138 | 139 | private void prepareProject(File projectDir) { 140 | write(new File(projectDir, "gradle.properties"), (writer) -> writer.println("org.gradle.caching=true")); 141 | write(new File(projectDir, "settings.gradle"), (writer) -> { 142 | writer.println("plugins {"); 143 | writer.println(" id 'io.spring.develocity.conventions' version '" + version() + "'"); 144 | writer.println("}"); 145 | writer.println("gradle.afterProject { project -> project.ext['settings'] = settings }"); 146 | }); 147 | write(new File(projectDir, "build.gradle"), (writer) -> { 148 | writer.println("task verifyBuildScanConfig {"); 149 | writer.println(" doFirst {"); 150 | writer.println(" println \"Build scan server: ${buildScan.server}\""); 151 | writer.println(" println \"Capture task input files: ${buildScan.captureTaskInputFiles}\""); 152 | writer.println(" }"); 153 | writer.println("}"); 154 | writer.println("task verifyBuildCacheConfig {"); 155 | writer.println(" doFirst {"); 156 | writer.println( 157 | " println \"Build cache server: ${project.ext['settings'].buildCache?.remote?.server}\""); 158 | writer.println(" }"); 159 | writer.println("}"); 160 | }); 161 | } 162 | 163 | private void prepareMultiModuleProject(File projectDir) { 164 | write(new File(projectDir, "gradle.properties"), (writer) -> writer.println("org.gradle.caching=true")); 165 | write(new File(projectDir, "settings.gradle"), (writer) -> { 166 | writer.println("plugins {"); 167 | writer.println(" id 'io.spring.develocity.conventions' version '" + version() + "'"); 168 | writer.println("}"); 169 | writer.println("include 'sub'"); 170 | writer.println("gradle.afterProject { project -> project.ext['settings'] = settings }"); 171 | }); 172 | write(new File(new File(projectDir, "sub"), "build.gradle"), (writer) -> { 173 | writer.println("task verifyBuildScanConfig {"); 174 | writer.println(" doFirst {"); 175 | writer.println(" println \"Build scan server: ${buildScan.server}\""); 176 | writer.println(" println \"Capture task input files: ${buildScan.captureTaskInputFiles}\""); 177 | writer.println(" }"); 178 | writer.println("}"); 179 | writer.println("task verifyBuildCacheConfig {"); 180 | writer.println(" doFirst {"); 181 | writer.println( 182 | " println \"Build cache server: ${project.ext['settings'].buildCache?.remote?.server}\""); 183 | writer.println(" }"); 184 | writer.println("}"); 185 | }); 186 | } 187 | 188 | private void write(File file, Consumer consumer) { 189 | file.getParentFile().mkdirs(); 190 | try (PrintWriter writer = new PrintWriter(new FileWriter(file, true))) { 191 | consumer.accept(writer); 192 | } 193 | catch (IOException ex) { 194 | throw new RuntimeException(ex); 195 | } 196 | } 197 | 198 | private String version() { 199 | Properties properties = new Properties(); 200 | try (Reader reader = new FileReader("../gradle.properties")) { 201 | properties.load(reader); 202 | return properties.getProperty("version"); 203 | } 204 | catch (IOException ex) { 205 | throw new RuntimeException(ex); 206 | } 207 | } 208 | 209 | private BuildResult build(File projectDir, String... arguments) { 210 | List classpath = Arrays.asList(new File("bin/main"), new File("build/classes/java/main"), 211 | new File("build/resources/main"), 212 | new File(DevelocityPlugin.class.getProtectionDomain().getCodeSource().getLocation().getFile()), 213 | new File(ConfigurableBuildScan.class.getProtectionDomain().getCodeSource().getLocation().getFile())); 214 | List augmentedArguments = new ArrayList<>(Arrays.asList(arguments)); 215 | augmentedArguments.add("--stacktrace"); 216 | return GradleRunner.create() 217 | .withProjectDir(projectDir) 218 | .withPluginClasspath(classpath) 219 | .withArguments(augmentedArguments) 220 | .build(); 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/GradleConfigurableBuildCacheTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; 20 | import org.gradle.api.Action; 21 | import org.gradle.caching.BuildCacheServiceFactory; 22 | import org.gradle.caching.configuration.BuildCache; 23 | import org.gradle.caching.configuration.BuildCacheConfiguration; 24 | import org.gradle.caching.local.DirectoryBuildCache; 25 | import org.junit.jupiter.api.Test; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | /** 30 | * Tests for {@link GradleConfigurableBuildCache}. 31 | * 32 | * @author Andy Wilkinson 33 | */ 34 | class GradleConfigurableBuildCacheTests { 35 | 36 | private final TestBuildCacheConfiguration buildCache = new TestBuildCacheConfiguration(); 37 | 38 | @Test 39 | void localCacheCanBeEnabled() { 40 | new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache).local((local) -> local.enable()); 41 | assertThat(this.buildCache.local.isEnabled()).isTrue(); 42 | } 43 | 44 | @Test 45 | void remoteCacheCanBeEnabled() { 46 | new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache) 47 | .remote((remote) -> remote.enable()); 48 | } 49 | 50 | @Test 51 | void pushToRemoteCacheCanBeEnabled() { 52 | new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache) 53 | .remote((remote) -> remote.enablePush()); 54 | assertThat(this.buildCache.remote.isPush()).isTrue(); 55 | } 56 | 57 | @Test 58 | void remoteServerCanBeConfigured() { 59 | new GradleConfigurableBuildCache(DevelocityBuildCache.class, this.buildCache) 60 | .remote((remote) -> remote.setServer("https://ge.spring.io")); 61 | assertThat(this.buildCache.remote.getServer()).isEqualTo("https://ge.spring.io"); 62 | } 63 | 64 | private static final class TestBuildCacheConfiguration implements BuildCacheConfiguration { 65 | 66 | private final DirectoryBuildCache local = new DirectoryBuildCache(); 67 | 68 | private final DevelocityBuildCache remote = new DevelocityBuildCache() { 69 | }; 70 | 71 | @Override 72 | public DirectoryBuildCache getLocal() { 73 | return this.local; 74 | } 75 | 76 | @Override 77 | public BuildCache getRemote() { 78 | throw new UnsupportedOperationException(); 79 | } 80 | 81 | @Override 82 | public void local(Action action) { 83 | action.execute(this.local); 84 | } 85 | 86 | @Override 87 | public void registerBuildCacheService(Class cacheType, 88 | Class> factory) { 89 | throw new UnsupportedOperationException(); 90 | } 91 | 92 | @Override 93 | @SuppressWarnings("unchecked") 94 | public T remote(Class cacheType) { 95 | return (T) this.remote; 96 | } 97 | 98 | @Override 99 | public void remote(Action action) { 100 | throw new UnsupportedOperationException(); 101 | } 102 | 103 | @Override 104 | public T remote(Class type, Action action) { 105 | T cache = remote(type); 106 | action.execute(cache); 107 | return cache; 108 | } 109 | 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/GradleConfigurableBuildScanTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.net.InetAddress; 20 | import java.net.UnknownHostException; 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.stream.Collectors; 24 | 25 | import com.gradle.develocity.agent.gradle.scan.BuildScanPublishingConfiguration.PublishingContext; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.mockito.BDDMockito.given; 30 | import static org.mockito.Mockito.mock; 31 | 32 | /** 33 | * Tests for {@link GradleConfigurableBuildScan}. 34 | * 35 | * @author Andy Wilkinson 36 | */ 37 | class GradleConfigurableBuildScanTests { 38 | 39 | private final TestBuildScanConfiguration buildScan = new TestBuildScanConfiguration(); 40 | 41 | @Test 42 | void captureOfInputFilesCanBeEnabled() { 43 | new GradleConfigurableBuildScan(this.buildScan).captureInputFiles(true); 44 | assertThat(this.buildScan.captureSettings.getFileFingerprints().get()).isTrue(); 45 | } 46 | 47 | @Test 48 | void ipAddressesCanBeObfuscated() throws UnknownHostException { 49 | new GradleConfigurableBuildScan(this.buildScan).obfuscation((obfuscation) -> obfuscation.ipAddresses( 50 | (inetAddresses) -> inetAddresses.stream().map((address) -> "127.0.0.1").collect(Collectors.toList()))); 51 | assertThat(this.buildScan.obfuscation.ipAddressesObfuscator).isNotNull(); 52 | List obfuscatedAddresses = this.buildScan.obfuscation.ipAddressesObfuscator 53 | .apply(Arrays.asList(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"))); 54 | assertThat(obfuscatedAddresses).containsExactly("127.0.0.1", "127.0.0.1"); 55 | } 56 | 57 | @Test 58 | void buildScansCanBeConfiguredToPublishIfAuthenticated() { 59 | new GradleConfigurableBuildScan(this.buildScan).publishIfAuthenticated(); 60 | PublishingContext context = mock(PublishingContext.class); 61 | given(context.isAuthenticated()).willReturn(true); 62 | assertThat(this.buildScan.publishing.predicate.isSatisfiedBy(context)).isTrue(); 63 | given(context.isAuthenticated()).willReturn(false); 64 | assertThat(this.buildScan.publishing.predicate.isSatisfiedBy(context)).isFalse(); 65 | } 66 | 67 | @Test 68 | void whenTagsAreAddedThenBuildScanHasTags() { 69 | GradleConfigurableBuildScan configurableBuildScan = new GradleConfigurableBuildScan(this.buildScan); 70 | configurableBuildScan.tag("some-tag"); 71 | configurableBuildScan.tag("another-tag"); 72 | assertThat(this.buildScan.tags).containsExactly("some-tag", "another-tag"); 73 | } 74 | 75 | @Test 76 | void whenLinkIsAddedThenBuildScanHasLink() { 77 | new GradleConfigurableBuildScan(this.buildScan).link("CI Server", "ci.example.com"); 78 | assertThat(this.buildScan.links).containsEntry("CI Server", "ci.example.com"); 79 | } 80 | 81 | @Test 82 | void whenValueIsAddedThenBuildScanHasValue() { 83 | new GradleConfigurableBuildScan(this.buildScan).value("Branch", "1.2.x"); 84 | assertThat(this.buildScan.values).containsEntry("Branch", "1.2.x"); 85 | } 86 | 87 | @Test 88 | void whenBuildingLocallyThenBackgroundUploadIsEnabled() { 89 | new GradleConfigurableBuildScan(this.buildScan).uploadInBackground(true); 90 | assertThat(this.buildScan.uploadInBackground.get()).isTrue(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/TestBuildScanConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.net.InetAddress; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.function.Function; 25 | 26 | import com.gradle.develocity.agent.gradle.scan.BuildResult; 27 | import com.gradle.develocity.agent.gradle.scan.BuildScanCaptureConfiguration; 28 | import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; 29 | import com.gradle.develocity.agent.gradle.scan.BuildScanDataObfuscationConfiguration; 30 | import com.gradle.develocity.agent.gradle.scan.BuildScanPublishingConfiguration; 31 | import com.gradle.develocity.agent.gradle.scan.PublishedBuildScan; 32 | import org.gradle.api.Action; 33 | import org.gradle.api.provider.Property; 34 | import org.gradle.api.specs.Spec; 35 | 36 | /** 37 | * A {@link BuildScanConfiguration} used for unit testing. 38 | * 39 | * @author Andy Wilkinson 40 | */ 41 | class TestBuildScanConfiguration implements BuildScanConfiguration { 42 | 43 | final TestBuildScanDataObfuscation obfuscation = new TestBuildScanDataObfuscation(); 44 | 45 | final List tags = new ArrayList<>(); 46 | 47 | final Map values = new HashMap<>(); 48 | 49 | final Map links = new HashMap<>(); 50 | 51 | final TestBuildScanCaptureSettings captureSettings = new TestBuildScanCaptureSettings(); 52 | 53 | final TestBuildScanPublishingConfiguration publishing = new TestBuildScanPublishingConfiguration(); 54 | 55 | final Property uploadInBackground = new TestProperty<>(); 56 | 57 | @Override 58 | public void link(String name, String url) { 59 | this.links.put(name, url); 60 | } 61 | 62 | @Override 63 | public void tag(String tag) { 64 | this.tags.add(tag); 65 | } 66 | 67 | @Override 68 | public void value(String name, String value) { 69 | this.values.put(name, value); 70 | } 71 | 72 | @Override 73 | public void background(Action action) { 74 | action.execute(this); 75 | } 76 | 77 | @Override 78 | public void buildFinished(Action action) { 79 | throw new UnsupportedOperationException(); 80 | } 81 | 82 | @Override 83 | public void buildScanPublished(Action action) { 84 | throw new UnsupportedOperationException(); 85 | } 86 | 87 | @Override 88 | public void capture(Action action) { 89 | action.execute(this.captureSettings); 90 | } 91 | 92 | @Override 93 | public BuildScanCaptureConfiguration getCapture() { 94 | throw new UnsupportedOperationException(); 95 | } 96 | 97 | @Override 98 | public BuildScanDataObfuscationConfiguration getObfuscation() { 99 | return this.obfuscation; 100 | } 101 | 102 | @Override 103 | public TestBuildScanPublishingConfiguration getPublishing() { 104 | return this.publishing; 105 | } 106 | 107 | @Override 108 | public Property getTermsOfUseAgree() { 109 | throw new UnsupportedOperationException(); 110 | } 111 | 112 | @Override 113 | public Property getTermsOfUseUrl() { 114 | throw new UnsupportedOperationException(); 115 | } 116 | 117 | @Override 118 | public Property getUploadInBackground() { 119 | return this.uploadInBackground; 120 | } 121 | 122 | @Override 123 | public void obfuscation(Action action) { 124 | throw new UnsupportedOperationException(); 125 | } 126 | 127 | @Override 128 | public void publishing(Action action) { 129 | action.execute(this.publishing); 130 | } 131 | 132 | static final class TestBuildScanDataObfuscation implements BuildScanDataObfuscationConfiguration { 133 | 134 | Function, ? extends List> ipAddressesObfuscator; 135 | 136 | @Override 137 | public void hostname(Function obfuscator) { 138 | throw new UnsupportedOperationException(); 139 | } 140 | 141 | @Override 142 | public void ipAddresses(Function, ? extends List> obfuscator) { 143 | this.ipAddressesObfuscator = obfuscator; 144 | } 145 | 146 | @Override 147 | public void username(Function obfuscator) { 148 | throw new UnsupportedOperationException(); 149 | } 150 | 151 | @Override 152 | public void externalProcessName(Function obfuscator) { 153 | throw new UnsupportedOperationException(); 154 | } 155 | 156 | } 157 | 158 | static final class TestBuildScanCaptureSettings implements BuildScanCaptureConfiguration { 159 | 160 | final Property fileFingerprints = new TestProperty<>(); 161 | 162 | @Override 163 | public Property getBuildLogging() { 164 | throw new UnsupportedOperationException(); 165 | } 166 | 167 | @Override 168 | public Property getFileFingerprints() { 169 | return this.fileFingerprints; 170 | } 171 | 172 | @Override 173 | public Property getTestLogging() { 174 | throw new UnsupportedOperationException(); 175 | } 176 | 177 | @Override 178 | public Property getResourceUsage() { 179 | throw new UnsupportedOperationException(); 180 | } 181 | 182 | } 183 | 184 | static final class TestBuildScanPublishingConfiguration implements BuildScanPublishingConfiguration { 185 | 186 | Spec predicate; 187 | 188 | @Override 189 | public void onlyIf(Spec predicate) { 190 | this.predicate = predicate; 191 | } 192 | 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/TestDevelocityConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import com.gradle.develocity.agent.gradle.DevelocityConfiguration; 20 | import com.gradle.develocity.agent.gradle.buildcache.DevelocityBuildCache; 21 | import com.gradle.develocity.agent.gradle.scan.BuildScanConfiguration; 22 | import org.gradle.api.Action; 23 | import org.gradle.api.provider.Property; 24 | 25 | /** 26 | * {@link DevelocityConfiguration} implementation used for unit testing. 27 | * 28 | * @author Andy Wilkinson 29 | */ 30 | class TestDevelocityConfiguration implements DevelocityConfiguration { 31 | 32 | final TestProperty server = new TestProperty<>(); 33 | 34 | @Override 35 | public void buildScan(Action action) { 36 | throw new UnsupportedOperationException(); 37 | } 38 | 39 | @Override 40 | public Property getAccessKey() { 41 | throw new UnsupportedOperationException(); 42 | } 43 | 44 | @Override 45 | public Property getAllowUntrustedServer() { 46 | throw new UnsupportedOperationException(); 47 | } 48 | 49 | @Override 50 | public Class getBuildCache() { 51 | throw new UnsupportedOperationException(); 52 | } 53 | 54 | @Override 55 | public BuildScanConfiguration getBuildScan() { 56 | throw new UnsupportedOperationException(); 57 | } 58 | 59 | @Override 60 | public Property getProjectId() { 61 | throw new UnsupportedOperationException(); 62 | } 63 | 64 | @Override 65 | public Property getServer() { 66 | return this.server; 67 | 68 | } 69 | 70 | @Override 71 | public Property getEdgeDiscovery() { 72 | throw new UnsupportedOperationException(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/TestProcessRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.function.Consumer; 25 | 26 | import io.spring.develocity.conventions.core.ProcessRunner; 27 | import org.mockito.ArgumentCaptor; 28 | 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.verify; 31 | 32 | /** 33 | * A {@link ProcessRunner} for unit testing. 34 | * 35 | * @author Andy Wilkinson 36 | */ 37 | class TestProcessRunner implements ProcessRunner { 38 | 39 | final Map, String> commandLineOutput = new HashMap<>(); 40 | 41 | final Map, RuntimeException> failures = new HashMap<>(); 42 | 43 | @Override 44 | public void run(Consumer configurer) { 45 | ProcessSpec processSpec = mock(ProcessSpec.class); 46 | configurer.accept(processSpec); 47 | ArgumentCaptor commandLineCaptor = ArgumentCaptor.forClass(Object.class); 48 | verify(processSpec).commandLine(commandLineCaptor.capture()); 49 | ArgumentCaptor standardOut = ArgumentCaptor.forClass(OutputStream.class); 50 | verify(processSpec).standardOutput(standardOut.capture()); 51 | List commandLine = commandLineCaptor.getAllValues(); 52 | RuntimeException failure = this.failures.get(commandLine); 53 | if (failure != null) { 54 | failure.fillInStackTrace(); 55 | throw new RunFailedException(failure); 56 | } 57 | String output = this.commandLineOutput.get(commandLine); 58 | if (output != null) { 59 | try { 60 | standardOut.getValue().write(output.getBytes()); 61 | } 62 | catch (IOException ex) { 63 | throw new RuntimeException(ex); 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /develocity-conventions-gradle-plugin/src/test/java/io/spring/develocity/conventions/gradle/TestProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.gradle; 18 | 19 | import java.util.function.BiFunction; 20 | 21 | import org.gradle.api.Transformer; 22 | import org.gradle.api.provider.Property; 23 | import org.gradle.api.provider.Provider; 24 | 25 | /** 26 | * {@link Property} implementation for use in unit tests. 27 | * 28 | * @param type of the value of the property 29 | * @author Andy Wilkinson 30 | */ 31 | class TestProperty implements Property { 32 | 33 | private T value; 34 | 35 | @Override 36 | public Provider flatMap(Transformer, ? super T> arg0) { 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | @Override 41 | public T get() { 42 | if (this.value != null) { 43 | return this.value; 44 | } 45 | throw new IllegalStateException(); 46 | } 47 | 48 | @Override 49 | public T getOrElse(T fallback) { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | @Override 54 | public T getOrNull() { 55 | return this.value; 56 | } 57 | 58 | @Override 59 | public boolean isPresent() { 60 | throw new UnsupportedOperationException(); 61 | } 62 | 63 | @Override 64 | public Provider map(Transformer transformer) { 65 | throw new UnsupportedOperationException(); 66 | } 67 | 68 | @Override 69 | public Provider orElse(T fallback) { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | @Override 74 | public Provider orElse(Provider fallback) { 75 | throw new UnsupportedOperationException(); 76 | } 77 | 78 | @Override 79 | public void disallowChanges() { 80 | throw new UnsupportedOperationException(); 81 | } 82 | 83 | @Override 84 | public Property convention(T convention) { 85 | throw new UnsupportedOperationException(); 86 | } 87 | 88 | @Override 89 | public Property convention(Provider convention) { 90 | throw new UnsupportedOperationException(); 91 | } 92 | 93 | @Override 94 | public void finalizeValue() { 95 | throw new UnsupportedOperationException(); 96 | } 97 | 98 | @Override 99 | public void set(T value) { 100 | this.value = value; 101 | } 102 | 103 | @Override 104 | public void set(Provider value) { 105 | throw new UnsupportedOperationException(); 106 | } 107 | 108 | @Override 109 | public Property value(T value) { 110 | throw new UnsupportedOperationException(); 111 | } 112 | 113 | @Override 114 | public Property value(Provider value) { 115 | throw new UnsupportedOperationException(); 116 | } 117 | 118 | @Override 119 | @Deprecated 120 | public Provider forUseAtConfigurationTime() { 121 | throw new UnsupportedOperationException(); 122 | } 123 | 124 | @Override 125 | public Provider zip(Provider arg0, BiFunction arg1) { 126 | throw new UnsupportedOperationException(); 127 | } 128 | 129 | @Override 130 | public void disallowUnsafeRead() { 131 | throw new UnsupportedOperationException(); 132 | } 133 | 134 | @Override 135 | public void finalizeValueOnRead() { 136 | throw new UnsupportedOperationException(); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "build-conventions" 3 | id "java" 4 | id "maven-publish" 5 | } 6 | 7 | description = "Develocity Conventions Maven extension" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | compileOnly("org.apache.maven:maven-core:3.6.3") 15 | compileOnly("org.codehaus.plexus:plexus-component-annotations:1.7.1") 16 | 17 | implementation("com.gradle:develocity-maven-extension:1.23.2") 18 | implementation(project(":develocity-conventions-core")) 19 | 20 | testImplementation("org.assertj:assertj-core:3.27.2") 21 | testImplementation("org.junit.jupiter:junit-jupiter:5.13.0") 22 | testImplementation("org.mockito:mockito-core:4.11.0") 23 | 24 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0") 25 | } 26 | 27 | publishing { 28 | publications { 29 | maven(MavenPublication) { 30 | from components.java 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/java/io/spring/develocity/conventions/maven/ConventionsDevelocityListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import com.gradle.develocity.agent.maven.api.DevelocityApi; 20 | import com.gradle.develocity.agent.maven.api.DevelocityListener; 21 | import io.spring.develocity.conventions.core.BuildCacheConventions; 22 | import io.spring.develocity.conventions.core.BuildScanConventions; 23 | import org.apache.maven.execution.MavenSession; 24 | import org.codehaus.plexus.component.annotations.Component; 25 | 26 | /** 27 | * {@link DevelocityListener} for configuring the use of Develocity hosted at 28 | * ge.spring.io. 29 | * 30 | * @author Andy Wilkinson 31 | */ 32 | @Component(role = DevelocityListener.class, hint = "convention-develocity-maven-extension", 33 | description = "Develocity conventions Maven extension") 34 | public class ConventionsDevelocityListener implements DevelocityListener { 35 | 36 | @Override 37 | public void configure(DevelocityApi develocity, MavenSession mavenSession) throws Exception { 38 | new BuildScanConventions(new ProcessBuilderProcessRunner()).execute(new MavenConfigurableDevelocity(develocity), 39 | new MavenConfigurableBuildScan(develocity.getBuildScan())); 40 | new BuildCacheConventions().execute(new MavenConfigurableBuildCache(develocity.getBuildCache())); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/java/io/spring/develocity/conventions/maven/MavenConfigurableBuildCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import com.gradle.develocity.agent.maven.api.cache.BuildCacheApi; 22 | import io.spring.develocity.conventions.core.ConfigurableBuildCache; 23 | 24 | /** 25 | * {@link ConfigurableBuildCache} for Maven builds. 26 | * 27 | * @author Andy Wilkinson 28 | */ 29 | class MavenConfigurableBuildCache implements ConfigurableBuildCache { 30 | 31 | private final BuildCacheApi buildCache; 32 | 33 | MavenConfigurableBuildCache(BuildCacheApi buildCache) { 34 | this.buildCache = buildCache; 35 | } 36 | 37 | @Override 38 | public void local(Consumer local) { 39 | local.accept(new MavenLocalBuildCache(this.buildCache.getLocal())); 40 | } 41 | 42 | @Override 43 | public void remote(Consumer remote) { 44 | remote.accept(new MavenRemoteBuildCache(this.buildCache.getRemote())); 45 | } 46 | 47 | private static final class MavenLocalBuildCache implements LocalBuildCache { 48 | 49 | private final com.gradle.develocity.agent.maven.api.cache.LocalBuildCache localBuildCache; 50 | 51 | private MavenLocalBuildCache(com.gradle.develocity.agent.maven.api.cache.LocalBuildCache localBuildCache) { 52 | this.localBuildCache = localBuildCache; 53 | } 54 | 55 | @Override 56 | public void enable() { 57 | this.localBuildCache.setEnabled(true); 58 | } 59 | 60 | } 61 | 62 | private static final class MavenRemoteBuildCache implements RemoteBuildCache { 63 | 64 | private final com.gradle.develocity.agent.maven.api.cache.RemoteBuildCache remoteBuildCache; 65 | 66 | private MavenRemoteBuildCache(com.gradle.develocity.agent.maven.api.cache.RemoteBuildCache remoteBuildCache) { 67 | this.remoteBuildCache = remoteBuildCache; 68 | } 69 | 70 | @Override 71 | public void enable() { 72 | this.remoteBuildCache.setEnabled(true); 73 | } 74 | 75 | @Override 76 | public void enablePush() { 77 | this.remoteBuildCache.setStoreEnabled(true); 78 | } 79 | 80 | @Override 81 | public void setServer(String server) { 82 | String url = server; 83 | if (!url.endsWith("/cache/")) { 84 | url = url + "/cache/"; 85 | } 86 | this.remoteBuildCache.getServer().setUrl(url); 87 | } 88 | 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/java/io/spring/develocity/conventions/maven/MavenConfigurableBuildScan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import java.net.InetAddress; 20 | import java.util.List; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | import com.gradle.develocity.agent.maven.api.scan.BuildScanApi; 25 | import com.gradle.develocity.agent.maven.api.scan.BuildScanPublishing.PublishingContext; 26 | import io.spring.develocity.conventions.core.ConfigurableBuildScan; 27 | 28 | /** 29 | * {@link ConfigurableBuildScan} for Maven builds. 30 | * 31 | * @author Andy Wilkinson 32 | */ 33 | class MavenConfigurableBuildScan implements ConfigurableBuildScan { 34 | 35 | private final BuildScanApi buildScan; 36 | 37 | MavenConfigurableBuildScan(BuildScanApi buildScan) { 38 | this.buildScan = buildScan; 39 | } 40 | 41 | @Override 42 | public void captureInputFiles(boolean capture) { 43 | this.buildScan.getCapture().setFileFingerprints(capture); 44 | } 45 | 46 | @Override 47 | public void obfuscation(Consumer configurer) { 48 | configurer.accept(new ObfuscationConfigurer() { 49 | 50 | @Override 51 | public void ipAddresses(Function, ? extends List> obfuscator) { 52 | MavenConfigurableBuildScan.this.buildScan.getObfuscation().ipAddresses(obfuscator); 53 | } 54 | 55 | }); 56 | } 57 | 58 | @Override 59 | public void publishIfAuthenticated() { 60 | this.buildScan.getPublishing().onlyIf(PublishingContext::isAuthenticated); 61 | } 62 | 63 | @Override 64 | public void uploadInBackground(boolean enabled) { 65 | this.buildScan.setUploadInBackground(enabled); 66 | } 67 | 68 | @Override 69 | public void link(String name, String url) { 70 | this.buildScan.link(name, url); 71 | } 72 | 73 | @Override 74 | public void tag(String tag) { 75 | this.buildScan.tag(tag); 76 | } 77 | 78 | @Override 79 | public void value(String name, String value) { 80 | this.buildScan.value(name, value); 81 | } 82 | 83 | @Override 84 | public void background(Consumer backgroundConfigurer) { 85 | this.buildScan 86 | .background((backgrounded) -> backgroundConfigurer.accept(new MavenConfigurableBuildScan(backgrounded))); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/java/io/spring/develocity/conventions/maven/MavenConfigurableDevelocity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import com.gradle.develocity.agent.maven.api.DevelocityApi; 20 | import io.spring.develocity.conventions.core.ConfigurableDevelocity; 21 | 22 | /** 23 | * {@link ConfigurableDevelocity} for Maven builds. 24 | * 25 | * @author Andy Wilkinson 26 | */ 27 | class MavenConfigurableDevelocity implements ConfigurableDevelocity { 28 | 29 | private final DevelocityApi develocity; 30 | 31 | MavenConfigurableDevelocity(DevelocityApi develocity) { 32 | this.develocity = develocity; 33 | } 34 | 35 | @Override 36 | public String getServer() { 37 | return this.develocity.getServer(); 38 | } 39 | 40 | @Override 41 | public void setServer(String server) { 42 | this.develocity.setServer(server); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/java/io/spring/develocity/conventions/maven/ProcessBuilderProcessRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.nio.file.Files; 22 | import java.nio.file.Path; 23 | import java.util.List; 24 | import java.util.function.Consumer; 25 | import java.util.stream.Collectors; 26 | import java.util.stream.Stream; 27 | 28 | import io.spring.develocity.conventions.core.ProcessRunner; 29 | 30 | /** 31 | * {@link ProcessRunner} implementation that uses {@link ProcessBuilder}. 32 | * 33 | * @author Andy Wilkinson 34 | */ 35 | class ProcessBuilderProcessRunner implements ProcessRunner { 36 | 37 | @Override 38 | public void run(Consumer configurer) { 39 | ProcessBuilder processBuilder = new ProcessBuilder(); 40 | ProcessBuilderProcessSpec spec = new ProcessBuilderProcessSpec(processBuilder); 41 | configurer.accept(spec); 42 | try { 43 | processBuilder.start().waitFor(); 44 | Files.copy(spec.output, spec.outputStream); 45 | } 46 | catch (Exception ex) { 47 | throw new RunFailedException(ex); 48 | } 49 | } 50 | 51 | private static final class ProcessBuilderProcessSpec implements ProcessSpec { 52 | 53 | private final ProcessBuilder processBuilder; 54 | 55 | private final Path output; 56 | 57 | private OutputStream outputStream; 58 | 59 | private ProcessBuilderProcessSpec(ProcessBuilder processBuilder) { 60 | this.processBuilder = processBuilder; 61 | try { 62 | this.output = Files.createTempFile("output", "txt"); 63 | } 64 | catch (IOException ex) { 65 | throw new IllegalStateException(ex); 66 | } 67 | } 68 | 69 | @Override 70 | public void commandLine(Object... commandLine) { 71 | List command = Stream.of(commandLine).map(Object::toString).collect(Collectors.toList()); 72 | this.processBuilder.command(command); 73 | } 74 | 75 | @Override 76 | public void standardOutput(OutputStream standardOutput) { 77 | this.processBuilder.redirectOutput(this.output.toFile()); 78 | this.outputStream = standardOutput; 79 | } 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/java/io/spring/develocity/conventions/maven/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Develocity conventions for Spring projects built with Maven. 19 | */ 20 | package io.spring.develocity.conventions.maven; 21 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/main/resources/META-INF/plexus/components.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.gradle.develocity.agent.maven.api.DevelocityListener 6 | conventions-develocity-listener 7 | io.spring.develocity.conventions.maven.ConventionsDevelocityListener 8 | 9 | 10 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/test/java/io/spring/develocity/conventions/maven/MavenConfigurableBuildCacheTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import java.io.File; 20 | import java.net.URI; 21 | 22 | import com.gradle.develocity.agent.maven.api.cache.BuildCacheApi; 23 | import com.gradle.develocity.agent.maven.api.cache.CleanupPolicy; 24 | import com.gradle.develocity.agent.maven.api.cache.Credentials; 25 | import com.gradle.develocity.agent.maven.api.cache.LocalBuildCache; 26 | import com.gradle.develocity.agent.maven.api.cache.MojoMetadataProvider; 27 | import com.gradle.develocity.agent.maven.api.cache.NormalizationProvider; 28 | import com.gradle.develocity.agent.maven.api.cache.RemoteBuildCache; 29 | import com.gradle.develocity.agent.maven.api.cache.Server; 30 | import org.junit.jupiter.api.Test; 31 | 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | 34 | /** 35 | * Tests for {@link MavenConfigurableBuildCache}. 36 | * 37 | * @author Andy Wilkinson 38 | */ 39 | class MavenConfigurableBuildCacheTests { 40 | 41 | private final BuildCacheApi buildCacheApi = new TestBuildCacheApi(); 42 | 43 | private final MavenConfigurableBuildCache buildCache = new MavenConfigurableBuildCache(this.buildCacheApi); 44 | 45 | @Test 46 | void localBuildCacheCanBeEnabled() { 47 | this.buildCache.local((local) -> local.enable()); 48 | assertThat(this.buildCacheApi.getLocal().isEnabled()).isTrue(); 49 | } 50 | 51 | @Test 52 | void remoteBuildCacheCanBeEnabled() { 53 | this.buildCache.remote((remote) -> remote.enable()); 54 | assertThat(this.buildCacheApi.getRemote().isEnabled()).isTrue(); 55 | } 56 | 57 | @Test 58 | void remoteBuildCacheCanHavePushEnabled() { 59 | this.buildCache.remote((remote) -> remote.enablePush()); 60 | assertThat(this.buildCacheApi.getRemote().isStoreEnabled()).isTrue(); 61 | } 62 | 63 | @Test 64 | void remoteBuildCacheCanHaveItsServerConfigured() { 65 | this.buildCache.remote((remote) -> remote.setServer("https://ge.spring.io")); 66 | assertThat(this.buildCacheApi.getRemote().getServer().getUrl()) 67 | .isEqualTo(URI.create("https://ge.spring.io/cache/")); 68 | } 69 | 70 | private static final class TestBuildCacheApi implements BuildCacheApi { 71 | 72 | private final LocalBuildCache local = new TestLocalBuildCache(); 73 | 74 | private final RemoteBuildCache remote = new TestRemoteBuildCache(); 75 | 76 | @Override 77 | public LocalBuildCache getLocal() { 78 | return this.local; 79 | } 80 | 81 | @Override 82 | public RemoteBuildCache getRemote() { 83 | return this.remote; 84 | } 85 | 86 | @Override 87 | public boolean isRequireClean() { 88 | throw new UnsupportedOperationException(); 89 | } 90 | 91 | @Override 92 | public void registerMojoMetadataProvider(MojoMetadataProvider metadataProvider) { 93 | throw new UnsupportedOperationException(); 94 | } 95 | 96 | @Override 97 | public void registerNormalizationProvider(NormalizationProvider normalizationProvider) { 98 | throw new UnsupportedOperationException(); 99 | } 100 | 101 | @Override 102 | public void setRequireClean(boolean requireClean) { 103 | throw new UnsupportedOperationException(); 104 | } 105 | 106 | private static final class TestLocalBuildCache implements LocalBuildCache { 107 | 108 | private boolean enabled; 109 | 110 | private boolean storeEnabled; 111 | 112 | @Override 113 | public CleanupPolicy getCleanupPolicy() { 114 | throw new UnsupportedOperationException(); 115 | } 116 | 117 | @Override 118 | public File getDirectory() { 119 | throw new UnsupportedOperationException(); 120 | } 121 | 122 | @Override 123 | public boolean isEnabled() { 124 | return this.enabled; 125 | } 126 | 127 | @Override 128 | public boolean isStoreEnabled() { 129 | return this.storeEnabled; 130 | } 131 | 132 | @Override 133 | public void setDirectory(File directory) { 134 | throw new UnsupportedOperationException(); 135 | } 136 | 137 | @Override 138 | public void setEnabled(boolean enabled) { 139 | this.enabled = enabled; 140 | } 141 | 142 | @Override 143 | public void setStoreEnabled(boolean storeEnabled) { 144 | this.storeEnabled = storeEnabled; 145 | } 146 | 147 | } 148 | 149 | private static final class TestRemoteBuildCache implements RemoteBuildCache { 150 | 151 | private final Server server = new TestServer(); 152 | 153 | private boolean enabled; 154 | 155 | private boolean storeEnabled; 156 | 157 | @Override 158 | public Server getServer() { 159 | return this.server; 160 | } 161 | 162 | @Override 163 | public boolean isEnabled() { 164 | return this.enabled; 165 | } 166 | 167 | @Override 168 | public boolean isStoreEnabled() { 169 | return this.storeEnabled; 170 | } 171 | 172 | @Override 173 | public void setEnabled(boolean enabled) { 174 | this.enabled = enabled; 175 | } 176 | 177 | @Override 178 | public void setStoreEnabled(boolean storeEnabled) { 179 | this.storeEnabled = storeEnabled; 180 | } 181 | 182 | private static final class TestServer implements Server { 183 | 184 | private URI url; 185 | 186 | @Override 187 | public Credentials getCredentials() { 188 | throw new UnsupportedOperationException(); 189 | } 190 | 191 | @Override 192 | public String getServerId() { 193 | throw new UnsupportedOperationException(); 194 | } 195 | 196 | @Override 197 | public URI getUrl() { 198 | return this.url; 199 | } 200 | 201 | @Override 202 | public boolean isAllowInsecureProtocol() { 203 | throw new UnsupportedOperationException(); 204 | } 205 | 206 | @Override 207 | public boolean isAllowUntrusted() { 208 | throw new UnsupportedOperationException(); 209 | } 210 | 211 | @Override 212 | public boolean isUseExpectContinue() { 213 | throw new UnsupportedOperationException(); 214 | } 215 | 216 | @Override 217 | public void setAllowInsecureProtocol(boolean allowInsecureProtocol) { 218 | throw new UnsupportedOperationException(); 219 | } 220 | 221 | @Override 222 | public void setAllowUntrusted(boolean allowUntrusted) { 223 | throw new UnsupportedOperationException(); 224 | } 225 | 226 | @Override 227 | public void setServerId(String serverId) { 228 | throw new UnsupportedOperationException(); 229 | } 230 | 231 | @Override 232 | public void setUrl(URI url) { 233 | this.url = url; 234 | } 235 | 236 | @Override 237 | public void setUseExpectContinue(boolean useExpectContinue) { 238 | throw new UnsupportedOperationException(); 239 | } 240 | 241 | } 242 | 243 | } 244 | 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/test/java/io/spring/develocity/conventions/maven/MavenConfigurableBuildScanTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import java.net.InetAddress; 20 | import java.net.URI; 21 | import java.net.UnknownHostException; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.concurrent.atomic.AtomicBoolean; 28 | import java.util.function.Consumer; 29 | import java.util.function.Function; 30 | import java.util.function.Predicate; 31 | import java.util.stream.Collectors; 32 | 33 | import com.gradle.develocity.agent.maven.api.scan.BuildResult; 34 | import com.gradle.develocity.agent.maven.api.scan.BuildScanApi; 35 | import com.gradle.develocity.agent.maven.api.scan.BuildScanCaptureSettings; 36 | import com.gradle.develocity.agent.maven.api.scan.BuildScanDataObfuscation; 37 | import com.gradle.develocity.agent.maven.api.scan.BuildScanPublishing; 38 | import com.gradle.develocity.agent.maven.api.scan.BuildScanPublishing.PublishingContext; 39 | import com.gradle.develocity.agent.maven.api.scan.PublishedBuildScan; 40 | import io.spring.develocity.conventions.core.ConfigurableBuildScan; 41 | import org.junit.jupiter.api.Test; 42 | 43 | import static org.assertj.core.api.Assertions.assertThat; 44 | import static org.mockito.BDDMockito.given; 45 | import static org.mockito.Mockito.mock; 46 | 47 | /** 48 | * Tests for {@link MavenConfigurableBuildScan}. 49 | * 50 | * @author Andy Wilkinson 51 | */ 52 | class MavenConfigurableBuildScanTests { 53 | 54 | private final TestBuildScanApi buildScanApi = new TestBuildScanApi(); 55 | 56 | private final MavenConfigurableBuildScan buildScan = new MavenConfigurableBuildScan(this.buildScanApi); 57 | 58 | @Test 59 | void captureOfInputFilesCanBeEnabled() { 60 | this.buildScan.captureInputFiles(true); 61 | assertThat(this.buildScanApi.capture.isFileFingerprints()).isTrue(); 62 | } 63 | 64 | @Test 65 | void ipAddressesCanBeObfuscated() throws UnknownHostException { 66 | this.buildScan.obfuscation((obfuscation) -> obfuscation.ipAddresses( 67 | (inetAddresses) -> inetAddresses.stream().map((address) -> "127.0.0.1").collect(Collectors.toList()))); 68 | assertThat(this.buildScanApi.obfuscation.ipAddressesObfuscator).isNotNull(); 69 | List obfuscatedAddresses = this.buildScanApi.obfuscation.ipAddressesObfuscator 70 | .apply(Arrays.asList(InetAddress.getByName("10.0.0.1"), InetAddress.getByName("10.0.0.2"))); 71 | assertThat(obfuscatedAddresses).containsExactly("127.0.0.1", "127.0.0.1"); 72 | } 73 | 74 | @Test 75 | void buildScansCanBeConfiguredToPublishIfAuthenticated() { 76 | this.buildScan.publishIfAuthenticated(); 77 | PublishingContext context = mock(PublishingContext.class); 78 | given(context.isAuthenticated()).willReturn(true); 79 | assertThat(this.buildScanApi.publishing.onlyIf.test(context)).isTrue(); 80 | given(context.isAuthenticated()).willReturn(false); 81 | assertThat(this.buildScanApi.publishing.onlyIf.test(context)).isFalse(); 82 | } 83 | 84 | @Test 85 | void whenTagsAreAddedThenBuildScanHasTags() { 86 | this.buildScan.tag("some-tag"); 87 | this.buildScan.tag("another-tag"); 88 | assertThat(this.buildScanApi.tags).containsExactly("some-tag", "another-tag"); 89 | } 90 | 91 | @Test 92 | void whenLinkIsAddedThenBuildScanHasLink() { 93 | this.buildScan.link("CI Server", "ci.example.com"); 94 | assertThat(this.buildScanApi.links).containsEntry("CI Server", "ci.example.com"); 95 | } 96 | 97 | @Test 98 | void whenValueIsAddedThenBuildScanHasValue() { 99 | this.buildScan.value("Branch", "1.2.x"); 100 | assertThat(this.buildScanApi.values).containsEntry("Branch", "1.2.x"); 101 | } 102 | 103 | @Test 104 | void whenBuildingLocallyThenBackgroundUploadIsEnabled() { 105 | this.buildScan.uploadInBackground(true); 106 | assertThat(this.buildScanApi.uploadInBackground).isTrue(); 107 | } 108 | 109 | @Test 110 | void backgroundTasksAreInvoked() { 111 | AtomicBoolean invoked = new AtomicBoolean(); 112 | Consumer consumer = (buildScan) -> invoked.set(true); 113 | this.buildScan.background(consumer); 114 | assertThat(invoked.get()).isTrue(); 115 | } 116 | 117 | private static final class TestBuildScanApi implements BuildScanApi { 118 | 119 | private final TestBuildScanCaptureSettings capture = new TestBuildScanCaptureSettings(); 120 | 121 | private final TestBuildScanDataObfuscation obfuscation = new TestBuildScanDataObfuscation(); 122 | 123 | private final TestBuildScanPublishing publishing = new TestBuildScanPublishing(); 124 | 125 | private final List tags = new ArrayList<>(); 126 | 127 | private final Map links = new HashMap<>(); 128 | 129 | private final Map values = new HashMap<>(); 130 | 131 | private boolean uploadInBackground; 132 | 133 | @Override 134 | public void background(Consumer action) { 135 | action.accept(this); 136 | } 137 | 138 | @Override 139 | public void buildFinished(Consumer action) { 140 | throw new UnsupportedOperationException(); 141 | } 142 | 143 | @Override 144 | public void buildScanPublished(Consumer action) { 145 | throw new UnsupportedOperationException(); 146 | } 147 | 148 | @Override 149 | public void capture(Consumer action) { 150 | throw new UnsupportedOperationException(); 151 | } 152 | 153 | @Override 154 | public void executeOnce(String identifier, Consumer action) { 155 | throw new UnsupportedOperationException(); 156 | } 157 | 158 | @Override 159 | public boolean getAllowUntrustedServer() { 160 | throw new UnsupportedOperationException(); 161 | } 162 | 163 | @Override 164 | public BuildScanCaptureSettings getCapture() { 165 | return this.capture; 166 | } 167 | 168 | @Override 169 | public BuildScanDataObfuscation getObfuscation() { 170 | return this.obfuscation; 171 | } 172 | 173 | @Override 174 | public BuildScanPublishing getPublishing() { 175 | return this.publishing; 176 | } 177 | 178 | @Override 179 | public String getServer() { 180 | throw new UnsupportedOperationException(); 181 | } 182 | 183 | @Override 184 | public String getTermsOfUseAgree() { 185 | throw new UnsupportedOperationException(); 186 | } 187 | 188 | @Override 189 | public String getTermsOfUseUrl() { 190 | throw new UnsupportedOperationException(); 191 | } 192 | 193 | @Override 194 | public boolean isUploadInBackground() { 195 | throw new UnsupportedOperationException(); 196 | } 197 | 198 | @Override 199 | public void link(String name, String url) { 200 | this.links.put(name, url); 201 | } 202 | 203 | @Override 204 | public void obfuscation(Consumer action) { 205 | throw new UnsupportedOperationException(); 206 | } 207 | 208 | @Override 209 | public void publishing(Consumer action) { 210 | throw new UnsupportedOperationException(); 211 | } 212 | 213 | @Override 214 | public void setAllowUntrustedServer(boolean allow) { 215 | throw new UnsupportedOperationException(); 216 | } 217 | 218 | @Override 219 | public void setServer(URI url) { 220 | throw new UnsupportedOperationException(); 221 | } 222 | 223 | @Override 224 | public void setTermsOfUseAgree(String agree) { 225 | throw new UnsupportedOperationException(); 226 | } 227 | 228 | @Override 229 | public void setTermsOfUseUrl(String termsOfUseUrl) { 230 | throw new UnsupportedOperationException(); 231 | } 232 | 233 | @Override 234 | public void setUploadInBackground(boolean uploadInBackground) { 235 | this.uploadInBackground = uploadInBackground; 236 | } 237 | 238 | @Override 239 | public void tag(String tag) { 240 | this.tags.add(tag); 241 | } 242 | 243 | @Override 244 | public void value(String name, String value) { 245 | this.values.put(name, value); 246 | } 247 | 248 | static class TestBuildScanCaptureSettings implements BuildScanCaptureSettings { 249 | 250 | private boolean fileFingerprints; 251 | 252 | @Override 253 | public boolean isBuildLogging() { 254 | throw new UnsupportedOperationException(); 255 | } 256 | 257 | @Override 258 | public void setBuildLogging(boolean capture) { 259 | throw new UnsupportedOperationException(); 260 | } 261 | 262 | @Override 263 | public boolean isFileFingerprints() { 264 | return this.fileFingerprints; 265 | } 266 | 267 | @Override 268 | public boolean isTestLogging() { 269 | throw new UnsupportedOperationException(); 270 | } 271 | 272 | @Override 273 | public void setTestLogging(boolean capture) { 274 | throw new UnsupportedOperationException(); 275 | } 276 | 277 | @Override 278 | public void setFileFingerprints(boolean capture) { 279 | this.fileFingerprints = capture; 280 | } 281 | 282 | @Override 283 | public boolean isResourceUsage() { 284 | throw new UnsupportedOperationException(); 285 | } 286 | 287 | @Override 288 | public void setResourceUsage(boolean capture) { 289 | throw new UnsupportedOperationException(); 290 | } 291 | 292 | } 293 | 294 | static final class TestBuildScanDataObfuscation implements BuildScanDataObfuscation { 295 | 296 | Function, ? extends List> ipAddressesObfuscator; 297 | 298 | @Override 299 | public void hostname(Function obfuscator) { 300 | throw new UnsupportedOperationException(); 301 | } 302 | 303 | @Override 304 | public void ipAddresses(Function, ? extends List> obfuscator) { 305 | this.ipAddressesObfuscator = obfuscator; 306 | } 307 | 308 | @Override 309 | public void username(Function obfuscator) { 310 | throw new UnsupportedOperationException(); 311 | } 312 | 313 | @Override 314 | public void externalProcessName(Function obfuscator) { 315 | throw new UnsupportedOperationException(); 316 | } 317 | 318 | } 319 | 320 | static final class TestBuildScanPublishing implements BuildScanPublishing { 321 | 322 | private Predicate onlyIf; 323 | 324 | @Override 325 | public BuildScanPublishing onlyIf(Predicate onlyIf) { 326 | this.onlyIf = onlyIf; 327 | return this; 328 | } 329 | 330 | } 331 | 332 | } 333 | 334 | } 335 | -------------------------------------------------------------------------------- /develocity-conventions-maven-extension/src/test/java/io/spring/develocity/conventions/maven/ProcessBuilderProcessRunnerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.maven; 18 | 19 | import io.spring.develocity.conventions.core.ProcessRunner; 20 | import io.spring.develocity.conventions.core.ProcessRunner.RunFailedException; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 24 | 25 | /** 26 | * Tests for {@link ProcessBuilderProcessRunner}. 27 | * 28 | * @author Andy Wilkinson 29 | */ 30 | class ProcessBuilderProcessRunnerTests { 31 | 32 | private final ProcessRunner processRunner = new ProcessBuilderProcessRunner(); 33 | 34 | @Test 35 | void whenRunFailsThenRunFailedExceptionIsThrown() { 36 | assertThatExceptionOfType(RunFailedException.class) 37 | .isThrownBy(() -> this.processRunner.run((spec) -> spec.commandLine("does-not-exist"))); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=0.0.24-SNAPSHOT 2 | -------------------------------------------------------------------------------- /gradle/plugins/build-conventions/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "checkstyle" 3 | id "java-gradle-plugin" 4 | id "io.spring.javaformat" version "0.0.46" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | gradlePlugin { 12 | plugins { 13 | conventionsPlugin { 14 | id = "build-conventions" 15 | implementationClass = "io.spring.develocity.conventions.build.BuildConventionsPlugin" 16 | } 17 | } 18 | } 19 | 20 | sourceCompatibility = "1.8" 21 | targetCompatibility = "1.8" 22 | 23 | dependencies { 24 | checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.46") 25 | implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.46") 26 | } 27 | 28 | checkstyle { 29 | def archive = configurations.checkstyle.filter { it.name.startsWith("spring-javaformat-checkstyle")} 30 | config = resources.text.fromArchiveEntry(archive, "io/spring/javaformat/checkstyle/checkstyle.xml") 31 | toolVersion = 9.3 32 | } 33 | -------------------------------------------------------------------------------- /gradle/plugins/build-conventions/settings.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-io/develocity-conventions/f2681c36b7002820ae576e535f05ee507526034b/gradle/plugins/build-conventions/settings.gradle -------------------------------------------------------------------------------- /gradle/plugins/build-conventions/src/main/java/io/spring/develocity/conventions/build/BuildConventionsPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.build; 18 | 19 | import org.gradle.api.Plugin; 20 | import org.gradle.api.Project; 21 | import org.gradle.api.plugins.JavaPlugin; 22 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; 23 | 24 | public class BuildConventionsPlugin implements Plugin { 25 | 26 | @Override 27 | public void apply(Project target) { 28 | target.getPlugins().withType(JavaPlugin.class, (plugin) -> new JavaConventions().apply(target)); 29 | target.getPlugins().withType(MavenPublishPlugin.class, (plugin) -> new MavenPublishConventions().apply(target)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /gradle/plugins/build-conventions/src/main/java/io/spring/develocity/conventions/build/JavaConventions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.build; 18 | 19 | import java.util.Arrays; 20 | 21 | import io.spring.javaformat.gradle.SpringJavaFormatPlugin; 22 | import org.gradle.api.JavaVersion; 23 | import org.gradle.api.Project; 24 | import org.gradle.api.plugins.JavaPluginExtension; 25 | import org.gradle.api.plugins.quality.CheckstyleExtension; 26 | import org.gradle.api.plugins.quality.CheckstylePlugin; 27 | import org.gradle.api.tasks.TaskContainer; 28 | import org.gradle.api.tasks.compile.JavaCompile; 29 | import org.gradle.api.tasks.testing.Test; 30 | 31 | public class JavaConventions { 32 | 33 | void apply(Project project) { 34 | configureJavaCompilation(project); 35 | configureSourceAndJavadocJars(project); 36 | configureSpringJavaFormat(project); 37 | configureCheckstyle(project); 38 | configureTestTasks(project); 39 | } 40 | 41 | private void configureJavaCompilation(Project project) { 42 | JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); 43 | javaPluginExtension.setSourceCompatibility(JavaVersion.VERSION_1_8); 44 | javaPluginExtension.setTargetCompatibility(JavaVersion.VERSION_1_8); 45 | TaskContainer tasks = project.getTasks(); 46 | tasks.withType(JavaCompile.class, 47 | (javaCompile) -> javaCompile.getOptions() 48 | .getCompilerArgs() 49 | .addAll(Arrays.asList("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", 50 | "-Xlint:varargs"))); 51 | } 52 | 53 | private void configureSourceAndJavadocJars(Project project) { 54 | JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); 55 | javaPluginExtension.withJavadocJar(); 56 | javaPluginExtension.withSourcesJar(); 57 | } 58 | 59 | private void configureSpringJavaFormat(Project project) { 60 | project.getPlugins().apply(SpringJavaFormatPlugin.class); 61 | } 62 | 63 | private void configureCheckstyle(Project project) { 64 | String javaFormatVersion = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion(); 65 | project.getPlugins().apply(CheckstylePlugin.class); 66 | project.getDependencies() 67 | .add("checkstyle", "io.spring.javaformat:spring-javaformat-checkstyle:" + javaFormatVersion); 68 | CheckstyleExtension checkstyleExtension = project.getExtensions().getByType(CheckstyleExtension.class); 69 | checkstyleExtension.setToolVersion("9.3"); 70 | checkstyleExtension.setConfig(project.getResources() 71 | .getText() 72 | .fromArchiveEntry( 73 | project.getConfigurations() 74 | .getByName("checkstyle") 75 | .filter((file) -> file.getName().startsWith("spring-javaformat-checkstyle")), 76 | "io/spring/javaformat/checkstyle/checkstyle.xml")); 77 | } 78 | 79 | private void configureTestTasks(Project project) { 80 | project.getTasks().withType(Test.class, (test) -> test.useJUnitPlatform()); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /gradle/plugins/build-conventions/src/main/java/io/spring/develocity/conventions/build/MavenPublishConventions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.develocity.conventions.build; 18 | 19 | import java.util.Arrays; 20 | 21 | import org.gradle.api.Project; 22 | import org.gradle.api.publish.PublicationContainer; 23 | import org.gradle.api.publish.PublishingExtension; 24 | import org.gradle.api.publish.maven.MavenPublication; 25 | import org.gradle.api.publish.tasks.GenerateModuleMetadata; 26 | 27 | public class MavenPublishConventions { 28 | 29 | void apply(Project project) { 30 | project.setGroup("io.spring.develocity.conventions"); 31 | configureDeploymentRepository(project); 32 | configurePom(project); 33 | disableModuleMetadataGeneration(project); 34 | } 35 | 36 | private void configureDeploymentRepository(Project project) { 37 | if (project.hasProperty("distributionRepository")) { 38 | PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); 39 | publishing.getRepositories().maven((maven) -> { 40 | maven.setUrl(project.property("distributionRepository")); 41 | maven.setName("deployment"); 42 | }); 43 | } 44 | } 45 | 46 | private void configurePom(Project project) { 47 | PublicationContainer publications = project.getExtensions() 48 | .getByType(PublishingExtension.class) 49 | .getPublications(); 50 | publications.withType(MavenPublication.class).configureEach((mavenPublication) -> { 51 | mavenPublication.pom((pom) -> { 52 | pom.getName().set(project.getDescription()); 53 | pom.getDescription().set(project.getDescription()); 54 | pom.getUrl().set("https://github.com/spring-io/develocity-conventions"); 55 | pom.organization((organization) -> { 56 | organization.getName().set("Pivotal Software, Inc."); 57 | organization.getUrl().set("https://spring.io"); 58 | }); 59 | pom.licenses((licenses) -> licenses.license((license) -> { 60 | license.getName().set("The Apache Software License, Version 2.0"); 61 | license.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0.txt"); 62 | })); 63 | pom.scm((scm) -> { 64 | scm.getUrl().set("https://github.com/spring-io/develocity-conventions"); 65 | scm.getConnection().set("scm:git:https://github.com/spring-io/develocity-conventions"); 66 | }); 67 | pom.developers((developers) -> { 68 | developers.developer((developer) -> { 69 | developer.getId().set("wilkinsona"); 70 | developer.getName().set("Andy Wilkinson"); 71 | developer.getEmail().set("awilkinson@pivotal.io"); 72 | developer.getRoles().set(Arrays.asList("Project Lead")); 73 | }); 74 | }); 75 | }); 76 | }); 77 | } 78 | 79 | private void disableModuleMetadataGeneration(Project project) { 80 | project.getTasks() 81 | .withType(GenerateModuleMetadata.class, 82 | (generateModuleMetadata) -> generateModuleMetadata.setEnabled(false)); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /gradle/plugins/build-conventions/src/main/java/io/spring/develocity/conventions/build/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Conventions for the project's build. 19 | */ 20 | package io.spring.develocity.conventions.build; 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-io/develocity-conventions/f2681c36b7002820ae576e535f05ee507526034b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild "gradle/plugins/build-conventions" 3 | } 4 | 5 | rootProject.name = 'develocity-conventions' 6 | 7 | include("develocity-conventions-core") 8 | include("develocity-conventions-gradle-plugin") 9 | include("develocity-conventions-maven-extension") 10 | --------------------------------------------------------------------------------