├── .github ├── release-drafter.yml └── workflows │ ├── build-verification.yml │ └── release-drafter.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── develocity-gradle.yml ├── develocity-maven.yml ├── images ├── build-scan.png └── links.png ├── src └── gradle │ ├── develocity-gradle.template.yml │ └── init-scripts │ ├── build-scan-collector.groovy │ └── develocity-injection.init.gradle └── test_maven.sh /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Release Drafter: https://github.com/toolmantim/release-drafter 2 | name-template: $NEXT_PATCH_VERSION 3 | tag-template: $NEXT_PATCH_VERSION 4 | 5 | # Emoji reference: https://gitmoji.carloscuesta.me/ 6 | categories: 7 | - title: 🎉 New Features 8 | labels: 9 | - feature 10 | - title: 🚀 Improvements 11 | labels: 12 | - enhancement 13 | - title: 🐛 Bug fixes 14 | labels: 15 | - bug 16 | - title: 🚦 Tests 17 | labels: 18 | - tests 19 | - title: 📝 Documentation 20 | labels: 21 | - documentation 22 | - title: ✍ Other changes 23 | # Default label used by Dependabot 24 | - title: 📦 Dependency updates 25 | labels: 26 | - dependencies 27 | collapse-after: 15 28 | 29 | template: | 30 | 31 | $CHANGES 32 | -------------------------------------------------------------------------------- /.github/workflows/build-verification.yml: -------------------------------------------------------------------------------- 1 | name: Verify Build 2 | 3 | on: [ push, pull_request, workflow_dispatch ] 4 | 5 | env: 6 | # https://gitlab.com/gradle7272134/gradle-enterprise-smoke-tests 7 | GITLAB_PROJECT_ID: 48766853 8 | 9 | jobs: 10 | verify-outputs: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Run the build 17 | run: ./build.sh 18 | 19 | - name: Verify Changed files 20 | uses: tj-actions/verify-changed-files@v20 21 | id: verify-changed-files 22 | 23 | - name: Fail if generated files are not up-to-date 24 | if: steps.verify-changed-files.outputs.files_changed == 'true' 25 | env: 26 | CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }} 27 | run: | 28 | echo "Generated files are not up-to-date: $CHANGED_FILES" 29 | exit 1 30 | 31 | unit-test: 32 | needs: verify-outputs 33 | name: Unit test scripts 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | - name: Unit test 39 | id: test 40 | run: ./test_maven.sh 41 | 42 | verification: 43 | needs: verify-outputs 44 | name: Smoke test on GitLab 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: Extract branch name 48 | id: extract_branch 49 | run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT 50 | - name: Run GitLab Build 51 | id: run-gitlab-build 52 | run: | 53 | echo "Trigger GitLab Pipeline at https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/trigger/pipeline" 54 | pipelineId=$(curl -s -S -X POST --fail -F token=${{ secrets.GITLAB_TRIGGER_TOKEN }} -F ref=main -F variables[GITHUB_BRANCH]="${{ steps.extract_branch.outputs.branch }}" "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/trigger/pipeline" | jq .id) 55 | if [ $? -ne 0 ]; then 56 | echo "Failed creating build" 57 | exit 1 58 | fi 59 | echo "GITLAB_PIPELINE_ID=${pipelineId}" >> "${GITHUB_OUTPUT}" 60 | - name: Check GitLab Status 61 | run: | 62 | pipelineId=${{ steps.run-gitlab-build.outputs.GITLAB_PIPELINE_ID }} 63 | status="" 64 | until [[ $status =~ (success|failed|canceled|skipped) ]]; do 65 | status=$(curl -s -S -H 'PRIVATE-TOKEN: ${{ secrets.GITLAB_TOKEN }}' https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/${pipelineId} | jq .status) 66 | echo "Checking GitLab Pipeline status at https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/${pipelineId}: ${status}" 67 | sleep 5 68 | done 69 | if [[ ! $status =~ (success) ]]; then 70 | echo "::error::GitLab Pipeline https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/${pipelineId} did not succeed, status: ${status}" 71 | exit 1 72 | fi 73 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Automates creation of Release Drafts using Release Drafter 2 | name: Release Management 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | update_draft_release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "main" 14 | - uses: release-drafter/release-drafter@v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | build/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Develocity GitLab Templates 2 | 3 | ## Overview 4 | These GitLab templates integrate with Develocity for Gradle and Maven builds run via GitLab. Build scans are available as a free service on [scans.gradle.com](https://scans.gradle.com/) and commercially via [Develocity](https://gradle.com/). 5 | 6 | ![build-scan.png](images/build-scan.png) 7 | 8 | For each Gradle and Maven build that is run from GitLab, these templates exposes the links to the created Build Scan® in the CI job logs. 9 | The templates can also be configured to ad-hoc connect Gradle and Maven builds to an existing Develocity instance such that a Build Scan® is published each time a build is run from GitLab. 10 | 11 | 12 | ## Requirements 13 | > [!IMPORTANT] 14 | > Develocity 2024.1 or above is required starting from version `1.3.0` and above. See [here](#short-lived-access-tokens) for more infos. 15 | 16 | - GitLab 15.11 since they use [inputs](https://docs.gitlab.com/ee/ci/yaml/inputs.html). 17 | - Shell with curl should be available on the executor 18 | - Network access to download from Maven central and from GitHub (those URLs can be customized, see [Configuration](#Configuration) 19 | 20 | ## Configuration 21 | ### Gradle Auto-instrumentation 22 | Include the remote template and optionally pass inputs. 23 | To enable Build Scan publishing for Gradle builds, the configuration would look something like presented below (using https://develocity.mycompany.com as an example of Develocity server URL. 24 | 25 | ```yml 26 | include: 27 | - remote: 'https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/1.3.3/develocity-gradle.yml' 28 | inputs: 29 | url: https://develocity.mycompany.com 30 | 31 | build-gradle-job: 32 | stage: build 33 | script: 34 | - !reference [.injectDevelocityForGradle] 35 | - ./gradlew check -I $DEVELOCITY_INIT_SCRIPT_PATH # Will publish a build scan to https://develocity.mycompany.com 36 | ``` 37 | The `.injectDevelocityForGradle` creates an init script with the instrumentation logic and exports the path as `$DEVELOCITY_INIT_SCRIPT_PATH` environment variable. 38 | For all other options see `inputs` section in [develocity-gradle.yml](develocity-gradle.yml). 39 | 40 | > **_NOTE:_** The build is also instrumented with our [Common Custom User Data Gradle plugin](https://github.com/gradle/common-custom-user-data-gradle-plugin) as well, as it will provide more details about your build. 41 | 42 | #### Build Scan links 43 | An optional init script can be used to generate a report containing Build Scan links. 44 | This report can then be [attached](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsannotations) to the job. 45 | Here's an example: 46 | 47 | ```yml 48 | include: 49 | - remote: 'https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/main/develocity-gradle.yml' 50 | inputs: 51 | url: https://develocity.mycompany.com 52 | 53 | build-gradle-job: 54 | stage: build 55 | script: 56 | - !reference [.injectDevelocityForGradle] 57 | - ./gradlew clean -I $DEVELOCITY_INIT_SCRIPT_PATH 58 | - ./gradlew check -I $DEVELOCITY_INIT_SCRIPT_PATH 59 | # Attach the report 60 | artifacts: 61 | !reference [ .build_scan_links_report, artifacts ] 62 | ``` 63 | 64 | This shows the Build Scan links on the job details right panel: 65 | 66 | ![links.png](images/links.png) 67 | 68 | Using GitLab templating, that can be factored and applied to multiple jobs: 69 | 70 | ```yml 71 | .gradle-inject-job: 72 | before_script: 73 | - !reference [ .injectDevelocityForGradle ] 74 | artifacts: 75 | !reference [ .build_scan_links_report, artifacts ] 76 | 77 | build-gradle-job: 78 | stage: build 79 | extends: .gradle-inject-job 80 | script: 81 | - ./gradlew build -I $DEVELOCITY_INIT_SCRIPT_PATH 82 | 83 | test-gradle-job: 84 | stage: build 85 | extends: .gradle-inject-job 86 | script: 87 | - ./gradlew test -I $DEVELOCITY_INIT_SCRIPT_PATH 88 | ``` 89 | 90 | ### Maven Auto-instrumentation 91 | Include the remote template and optionally pass inputs. 92 | To enable Build Scan publishing for Maven builds, the configuration would look something like presented below (using https://develocity.mycompany.com as an example of Develocity server URL. 93 | 94 | ```yml 95 | include: 96 | - remote: 'https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/1.3.3/develocity-maven.yml' 97 | inputs: 98 | url: https://develocity.mycompany.com 99 | 100 | build-maven-job: 101 | stage: build 102 | script: 103 | - !reference [.injectDevelocityForMaven] 104 | - ./mvnw clean verify # Will publish a build scan to https://develocity.mycompany.com 105 | ``` 106 | 107 | The `.injectDevelocityForMaven` downloads the extensions and references them in `MAVEN_OPTS`. 108 | For all other options see `inputs` section in [develocity-maven.yml](develocity-maven.yml). 109 | 110 | > **_NOTE:_** This instrumentation defines the environment variable `MAVEN_OPTS` taken into account by Maven builds. If `MAVEN_OPTS` is redefined, the instrumentation won't work 111 | 112 | > **_NOTE:_** The build is also instrumented with our [Common Custom User Data Maven extension](https://github.com/gradle/common-custom-user-data-maven-extension) as well, as it will provide more details about your build 113 | 114 | ### Gradle and Maven Auto-instrumentation 115 | If you have both Gradle and Maven builds in a pipeline, you can simply just include both templates: 116 | 117 | ```yml 118 | include: 119 | - remote: "https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/1.3.3/develocity-gradle.yml" 120 | inputs: 121 | url: https://develocity.mycompany.com 122 | - remote: "https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/1.3.3/develocity-maven.yml" 123 | inputs: 124 | url: https://develocity.mycompany.com 125 | 126 | build-maven-job: 127 | stage: build 128 | script: 129 | - !reference [.injectDevelocityForMaven] 130 | - ./mvnw clean verify # Will publish a build scan to https://develocity.mycompany.com 131 | 132 | build-gradle-job: 133 | stage: build 134 | script: 135 | - !reference [.injectDevelocityForGradle] 136 | - ./gradlew check -I $DEVELOCITY_INIT_SCRIPT_PATH # Will publish a build scan to https://develocity.mycompany.com 137 | ``` 138 | 139 | ### Auto-instrumentation compatibility 140 | The following sections list the compatibility of the instrumented Develocity Gradle plugin and Develocity Maven extension with the Develocity version based on the given build tool in use. 141 | #### For Gradle builds 142 | For Gradle builds the version used for the Develocity Gradle plugin can be defined with the `gradlePluginVersion` input. The compatibility of the specified version with Develocity can be found [here](https://docs.gradle.com/enterprise/compatibility/#develocity_gradle_plugin). 143 | For the Common Custom User Data Gradle plugin which is defined with the `ccudPluginVersion` input, you can see the compatibility of the specified version with the Develocity Gradle plugin [here](https://github.com/gradle/common-custom-user-data-gradle-plugin#version-compatibility). 144 | 145 | #### For Maven builds 146 | For Maven builds the version used for the Develocity Maven extension can be defined with the `mavenExtensionVersion` input. The compatibility of the specified version with Develocity can be found [here](https://docs.gradle.com/enterprise/maven-extension/#compatibility_with_apache_maven_and_develocity). 147 | For the Common Custom User Data Maven extension which is defined with the `ccudMavenExtensionVersion` input, you can see the compatibility of the specified version with the Develocity Maven extension [here](https://github.com/gradle/common-custom-user-data-maven-extension#version-compatibility). 148 | 149 | ## Authentication 150 | To authenticate against the Develocity server, you should specify a masked environment variable named `DEVELOCITY_ACCESS_KEY`. 151 | See [here](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui) on how to do this in GitLab UI. 152 | To generate a Develocity Access Key, you can check [Develocity Gradle plugin docs](https://docs.gradle.com/enterprise/gradle-plugin/#manual_access_key_configuration) and [Develocity Maven extension docs](https://docs.gradle.com/enterprise/maven-extension/#manual_access_key_configuration). 153 | 154 | ### Short-lived access tokens 155 | Develocity access keys are long-lived, creating risks if they are leaked. To avoid this, users can use short-lived access tokens to authenticate with Develocity. Access tokens can be used wherever an access key would be used. Access tokens are only valid for the Develocity instance that created them. 156 | If a short-lived token fails to be retrieved (for example, if the Develocity server version is lower than `2024.1`), no access key will be set. 157 | In that case, Develocity authenticated operations like build cache read/write and build scan publication will fail without failing the build. 158 | For more information on short-lived tokens, see [Develocity API documentation](https://docs.gradle.com/develocity/api-manual/#short_lived_access_tokens). 159 | 160 | 161 | ## License 162 | This project is available under the [Apache License, Version 2.0](https://github.com/gradle/develocity-gitlab-templates/blob/main/LICENSE). 163 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p build 4 | 5 | # Replace the 'BuildScanCollector' implementation in the reference init-script, 6 | # and indent the init-script for inclusion in the 'develocity-gradle.yml' file. 7 | sed -e '/class BuildScanCollector {}/{ 8 | r src/gradle/init-scripts/build-scan-collector.groovy 9 | d 10 | }' src/gradle/init-scripts/develocity-injection.init.gradle > build/develocity-injection-combined.init.gradle 11 | 12 | # Indent init script for inclusion in the 'develocity-gradle.yml' file. 13 | sed -e 's/^/ /' build/develocity-injection-combined.init.gradle > build/develocity-injection-indented.init.gradle 14 | 15 | # Construct the 'develocity-gradle.yml' file from the template and the init-script contents. 16 | sed -e '/<>/{ 17 | r build/develocity-injection-indented.init.gradle 18 | d 19 | }' src/gradle/develocity-gradle.template.yml > develocity-gradle.yml 20 | -------------------------------------------------------------------------------- /develocity-gradle.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | inputs: 3 | # Develocity server URL 4 | url: 5 | default: 'https://scans.gradle.com' 6 | # Develocity Plugin version 7 | # Allow untrusted server 8 | allowUntrustedServer: 9 | default: 'false' 10 | # Short-lived tokens expiry in hours 11 | shortLivedTokensExpiry: 12 | default: '2' 13 | gradlePluginVersion: 14 | default: '4.0.2' 15 | # Common Custom User Data Gradle Plugin version (see https://github.com/gradle/common-custom-user-data-gradle-plugin/) 16 | ccudPluginVersion: 17 | default: '2.3' 18 | # Develocity Gradle plugin repository URL, defaults in the init script to https://plugins.gradle.org/m2 19 | gradlePluginRepositoryUrl: 20 | default: '' 21 | # Develocity Gradle plugin repository username 22 | gradlePluginRepositoryUsername: 23 | default: '' 24 | # Develocity Gradle plugin repository password, strongly advised to pass a protected and masked variable 25 | gradlePluginRepositoryPassword: 26 | default: '' 27 | # Capture file fingerprints, only set if no Develocity plugin is already present 28 | captureFileFingerprints: 29 | default: 'true' 30 | # Enforce the url over any defined locally to the project 31 | enforceUrl: 32 | default: 'false' 33 | 34 | --- 35 | .build_scan_links_report: 36 | artifacts: 37 | reports: 38 | annotations: $CI_PROJECT_DIR/build-scan-links.json 39 | 40 | .injectDevelocityForGradle: | 41 | function createGradleInit() { 42 | local initScript="${CI_PROJECT_DIR}/init-script.gradle" 43 | 44 | cat > $initScript <<'EOF' 45 | /* 46 | * Initscript for injection of Develocity into Gradle builds. 47 | * Version: 1.2 48 | */ 49 | 50 | import org.gradle.util.GradleVersion 51 | 52 | initscript { 53 | // NOTE: there is no mechanism to share code between the initscript{} block and the main script, so some logic is duplicated 54 | def isTopLevelBuild = !gradle.parent 55 | if (!isTopLevelBuild) { 56 | return 57 | } 58 | 59 | def getInputParam = { Gradle gradle, String name -> 60 | def ENV_VAR_PREFIX = '' 61 | def envVarName = ENV_VAR_PREFIX + name.toUpperCase().replace('.', '_').replace('-', '_') 62 | return gradle.startParameter.systemPropertiesArgs[name] ?: System.getProperty(name) ?: System.getenv(envVarName) 63 | } 64 | 65 | def requestedInitScriptName = getInputParam(gradle, 'develocity.injection.init-script-name') 66 | def initScriptName = buildscript.sourceFile.name 67 | if (requestedInitScriptName != initScriptName) { 68 | return 69 | } 70 | 71 | // Plugin loading is only required for Develocity injection. Abort early if not enabled. 72 | def develocityInjectionEnabled = Boolean.parseBoolean(getInputParam(gradle, "develocity.injection-enabled")) 73 | if (!develocityInjectionEnabled) { 74 | return 75 | } 76 | 77 | def pluginRepositoryUrl = getInputParam(gradle, 'gradle.plugin-repository.url') 78 | def pluginRepositoryUsername = getInputParam(gradle, 'gradle.plugin-repository.username') 79 | def pluginRepositoryPassword = getInputParam(gradle, 'gradle.plugin-repository.password') 80 | def develocityPluginVersion = getInputParam(gradle, 'develocity.plugin.version') 81 | def ccudPluginVersion = getInputParam(gradle, 'develocity.ccud-plugin.version') 82 | 83 | def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0') 84 | def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0') 85 | 86 | if (develocityPluginVersion || ccudPluginVersion && atLeastGradle4) { 87 | pluginRepositoryUrl = pluginRepositoryUrl ?: 'https://plugins.gradle.org/m2' 88 | logger.lifecycle("Develocity plugins resolution: $pluginRepositoryUrl") 89 | 90 | repositories { 91 | maven { 92 | url = pluginRepositoryUrl 93 | if (pluginRepositoryUsername && pluginRepositoryPassword) { 94 | logger.lifecycle("Using credentials for plugin repository") 95 | credentials { 96 | username = pluginRepositoryUsername 97 | password = pluginRepositoryPassword 98 | } 99 | authentication { 100 | basic(BasicAuthentication) 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | dependencies { 108 | if (develocityPluginVersion) { 109 | if (atLeastGradle5) { 110 | if (GradleVersion.version(develocityPluginVersion) >= GradleVersion.version("3.17")) { 111 | classpath "com.gradle:develocity-gradle-plugin:$develocityPluginVersion" 112 | } else { 113 | classpath "com.gradle:gradle-enterprise-gradle-plugin:$develocityPluginVersion" 114 | } 115 | } else { 116 | classpath "com.gradle:build-scan-plugin:1.16" 117 | } 118 | } 119 | 120 | if (ccudPluginVersion && atLeastGradle4) { 121 | classpath "com.gradle:common-custom-user-data-gradle-plugin:$ccudPluginVersion" 122 | } 123 | } 124 | } 125 | 126 | static getInputParam(Gradle gradle, String name) { 127 | def ENV_VAR_PREFIX = '' 128 | def envVarName = ENV_VAR_PREFIX + name.toUpperCase().replace('.', '_').replace('-', '_') 129 | return gradle.startParameter.systemPropertiesArgs[name] ?: System.getProperty(name) ?: System.getenv(envVarName) 130 | } 131 | 132 | def isTopLevelBuild = !gradle.parent 133 | if (!isTopLevelBuild) { 134 | return 135 | } 136 | 137 | def requestedInitScriptName = getInputParam(gradle, 'develocity.injection.init-script-name') 138 | def initScriptName = buildscript.sourceFile.name 139 | if (requestedInitScriptName != initScriptName) { 140 | logger.quiet("Ignoring init script '${initScriptName}' as requested name '${requestedInitScriptName}' does not match") 141 | return 142 | } 143 | 144 | def develocityInjectionEnabled = Boolean.parseBoolean(getInputParam(gradle, "develocity.injection-enabled")) 145 | if (develocityInjectionEnabled) { 146 | enableDevelocityInjection() 147 | } 148 | 149 | // To enable build-scan capture, a `captureBuildScanLink(String)` method must be added to `BuildScanCollector`. 150 | def buildScanCollector = new BuildScanCollector() 151 | def buildScanCaptureEnabled = buildScanCollector.metaClass.respondsTo(buildScanCollector, 'captureBuildScanLink', String) 152 | if (buildScanCaptureEnabled) { 153 | enableBuildScanLinkCapture(buildScanCollector) 154 | } 155 | 156 | void enableDevelocityInjection() { 157 | def BUILD_SCAN_PLUGIN_CLASS = 'com.gradle.scan.plugin.BuildScanPlugin' 158 | 159 | def GRADLE_ENTERPRISE_PLUGIN_ID = 'com.gradle.enterprise' 160 | def GRADLE_ENTERPRISE_PLUGIN_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin' 161 | 162 | def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity' 163 | def DEVELOCITY_PLUGIN_CLASS = 'com.gradle.develocity.agent.gradle.DevelocityPlugin' 164 | 165 | def CI_AUTO_INJECTION_CUSTOM_VALUE_NAME = 'CI auto injection' 166 | def CCUD_PLUGIN_ID = 'com.gradle.common-custom-user-data-gradle-plugin' 167 | def CCUD_PLUGIN_CLASS = 'com.gradle.CommonCustomUserDataGradlePlugin' 168 | 169 | def develocityUrl = getInputParam(gradle, 'develocity.url') 170 | def develocityAllowUntrustedServer = Boolean.parseBoolean(getInputParam(gradle, 'develocity.allow-untrusted-server')) 171 | def develocityEnforceUrl = Boolean.parseBoolean(getInputParam(gradle, 'develocity.enforce-url')) 172 | def buildScanUploadInBackground = Boolean.parseBoolean(getInputParam(gradle, 'develocity.build-scan.upload-in-background')) 173 | def develocityCaptureFileFingerprints = getInputParam(gradle, 'develocity.capture-file-fingerprints') ? Boolean.parseBoolean(getInputParam(gradle, 'develocity.capture-file-fingerprints')) : true 174 | def develocityPluginVersion = getInputParam(gradle, 'develocity.plugin.version') 175 | def ccudPluginVersion = getInputParam(gradle, 'develocity.ccud-plugin.version') 176 | def buildScanTermsOfUseUrl = getInputParam(gradle, 'develocity.terms-of-use.url') 177 | def buildScanTermsOfUseAgree = getInputParam(gradle, 'develocity.terms-of-use.agree') 178 | def ciAutoInjectionCustomValueValue = getInputParam(gradle, 'develocity.auto-injection.custom-value') 179 | 180 | def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0') 181 | def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0') 182 | def shouldApplyDevelocityPlugin = atLeastGradle5 && develocityPluginVersion && isAtLeast(develocityPluginVersion, '3.17') 183 | 184 | def dvOrGe = { def dvValue, def geValue -> 185 | if (shouldApplyDevelocityPlugin) { 186 | return dvValue instanceof Closure ? dvValue() : dvValue 187 | } 188 | return geValue instanceof Closure ? geValue() : geValue 189 | } 190 | 191 | def printEnforcingDevelocityUrl = { 192 | logger.lifecycle("Enforcing Develocity: $develocityUrl, allowUntrustedServer: $develocityAllowUntrustedServer") 193 | } 194 | 195 | def printAcceptingGradleTermsOfUse = { 196 | logger.lifecycle("Accepting Gradle Terms of Use: $buildScanTermsOfUseUrl") 197 | } 198 | 199 | // finish early if DV plugin version is unsupported (v3.6.4 is the minimum version tested and supports back to DV 2021.1) 200 | if (develocityPluginVersion && isNotAtLeast(develocityPluginVersion, '3.6.4')) { 201 | logger.warn("Develocity Gradle plugin must be at least 3.6.4. Configured version is $develocityPluginVersion.") 202 | return 203 | } 204 | 205 | // finish early if configuration parameters passed in via system properties are not valid/supported 206 | if (ccudPluginVersion && isNotAtLeast(ccudPluginVersion, '1.7')) { 207 | logger.warn("Common Custom User Data Gradle plugin must be at least 1.7. Configured version is $ccudPluginVersion.") 208 | return 209 | } 210 | 211 | // Conditionally apply and configure the Develocity plugin 212 | if (GradleVersion.current() < GradleVersion.version('6.0')) { 213 | rootProject { 214 | buildscript.configurations.getByName("classpath").incoming.afterResolve { ResolvableDependencies incoming -> 215 | def resolutionResult = incoming.resolutionResult 216 | 217 | if (develocityPluginVersion) { 218 | def scanPluginComponent = resolutionResult.allComponents.find { 219 | it.moduleVersion.with { group == "com.gradle" && ['build-scan-plugin', 'gradle-enterprise-gradle-plugin', 'develocity-gradle-plugin'].contains(name) } 220 | } 221 | if (!scanPluginComponent) { 222 | def pluginClass = dvOrGe(DEVELOCITY_PLUGIN_CLASS, BUILD_SCAN_PLUGIN_CLASS) 223 | def pluginVersion = atLeastGradle5 ? develocityPluginVersion : "1.16" 224 | applyPluginExternally(pluginManager, pluginClass, pluginVersion) 225 | def rootExtension = dvOrGe( 226 | { develocity }, 227 | { buildScan } 228 | ) 229 | def buildScanExtension = dvOrGe( 230 | { rootExtension.buildScan }, 231 | { rootExtension } 232 | ) 233 | if (develocityUrl) { 234 | logger.lifecycle("Connection to Develocity: $develocityUrl, allowUntrustedServer: $develocityAllowUntrustedServer, captureFileFingerprints: $develocityCaptureFileFingerprints") 235 | rootExtension.server = develocityUrl 236 | rootExtension.allowUntrustedServer = develocityAllowUntrustedServer 237 | } 238 | if (!shouldApplyDevelocityPlugin) { 239 | // Develocity plugin publishes scans by default 240 | buildScanExtension.publishAlways() 241 | } 242 | buildScanExtension.value CI_AUTO_INJECTION_CUSTOM_VALUE_NAME, ciAutoInjectionCustomValueValue 243 | if (isAtLeast(develocityPluginVersion, '2.1') && atLeastGradle5) { 244 | logger.lifecycle("Setting captureFileFingerprints: $develocityCaptureFileFingerprints") 245 | if (isAtLeast(develocityPluginVersion, '3.17')) { 246 | buildScanExtension.capture.fileFingerprints.set(develocityCaptureFileFingerprints) 247 | } else if (isAtLeast(develocityPluginVersion, '3.7')) { 248 | buildScanExtension.capture.taskInputFiles = develocityCaptureFileFingerprints 249 | } else { 250 | buildScanExtension.captureTaskInputFiles = develocityCaptureFileFingerprints 251 | } 252 | } 253 | } 254 | } 255 | 256 | eachDevelocityProjectExtension(project, 257 | { develocity -> 258 | afterEvaluate { 259 | if (develocityUrl && develocityEnforceUrl) { 260 | printEnforcingDevelocityUrl() 261 | develocity.server = develocityUrl 262 | develocity.allowUntrustedServer = develocityAllowUntrustedServer 263 | } 264 | } 265 | 266 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 267 | printAcceptingGradleTermsOfUse() 268 | develocity.buildScan.termsOfUseUrl = buildScanTermsOfUseUrl 269 | develocity.buildScan.termsOfUseAgree = buildScanTermsOfUseAgree 270 | } 271 | 272 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 273 | develocity.buildScan.uploadInBackground = buildScanUploadInBackground 274 | }, 275 | { buildScan -> 276 | afterEvaluate { 277 | if (develocityUrl && develocityEnforceUrl) { 278 | printEnforcingDevelocityUrl() 279 | buildScan.server = develocityUrl 280 | buildScan.allowUntrustedServer = develocityAllowUntrustedServer 281 | } 282 | } 283 | 284 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 285 | printAcceptingGradleTermsOfUse() 286 | if (buildScan.metaClass.respondsTo(buildScan, 'setTermsOfServiceUrl', String)) { 287 | buildScan.termsOfServiceUrl = buildScanTermsOfUseUrl 288 | buildScan.termsOfServiceAgree = buildScanTermsOfUseAgree 289 | } else { 290 | buildScan.licenseAgreementUrl = buildScanTermsOfUseUrl 291 | buildScan.licenseAgree = buildScanTermsOfUseAgree 292 | } 293 | } 294 | 295 | // uploadInBackground available for build-scan-plugin 3.3.4 and later only 296 | if (buildScan.metaClass.respondsTo(buildScan, 'setUploadInBackground', Boolean)) { 297 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 298 | buildScan.uploadInBackground = buildScanUploadInBackground 299 | } 300 | } 301 | ) 302 | 303 | if (ccudPluginVersion && atLeastGradle4) { 304 | def ccudPluginComponent = resolutionResult.allComponents.find { 305 | it.moduleVersion.with { group == "com.gradle" && name == "common-custom-user-data-gradle-plugin" } 306 | } 307 | if (!ccudPluginComponent) { 308 | logger.lifecycle("Applying $CCUD_PLUGIN_CLASS with version $ccudPluginVersion via init script") 309 | pluginManager.apply(initscript.classLoader.loadClass(CCUD_PLUGIN_CLASS)) 310 | } 311 | } 312 | } 313 | } 314 | } else { 315 | gradle.settingsEvaluated { settings -> 316 | if (develocityPluginVersion) { 317 | if (!settings.pluginManager.hasPlugin(GRADLE_ENTERPRISE_PLUGIN_ID) && !settings.pluginManager.hasPlugin(DEVELOCITY_PLUGIN_ID)) { 318 | def pluginClass = dvOrGe(DEVELOCITY_PLUGIN_CLASS, GRADLE_ENTERPRISE_PLUGIN_CLASS) 319 | applyPluginExternally(settings.pluginManager, pluginClass, develocityPluginVersion) 320 | if (develocityUrl) { 321 | logger.lifecycle("Connection to Develocity: $develocityUrl, allowUntrustedServer: $develocityAllowUntrustedServer, captureFileFingerprints: $develocityCaptureFileFingerprints") 322 | eachDevelocitySettingsExtension(settings) { ext -> 323 | // server and allowUntrustedServer must be configured via buildScan extension for gradle-enterprise-plugin 3.1.1 and earlier 324 | if (ext.metaClass.respondsTo(ext, 'getServer')) { 325 | ext.server = develocityUrl 326 | ext.allowUntrustedServer = develocityAllowUntrustedServer 327 | } else { 328 | ext.buildScan.server = develocityUrl 329 | ext.buildScan.allowUntrustedServer = develocityAllowUntrustedServer 330 | } 331 | } 332 | } 333 | 334 | eachDevelocitySettingsExtension(settings) { ext -> 335 | ext.buildScan.value CI_AUTO_INJECTION_CUSTOM_VALUE_NAME, ciAutoInjectionCustomValueValue 336 | } 337 | 338 | eachDevelocitySettingsExtension(settings, 339 | { develocity -> 340 | logger.lifecycle("Setting captureFileFingerprints: $develocityCaptureFileFingerprints") 341 | develocity.buildScan.capture.fileFingerprints = develocityCaptureFileFingerprints 342 | }, 343 | { gradleEnterprise -> 344 | gradleEnterprise.buildScan.publishAlways() 345 | if (isAtLeast(develocityPluginVersion, '2.1')) { 346 | logger.lifecycle("Setting captureFileFingerprints: $develocityCaptureFileFingerprints") 347 | if (isAtLeast(develocityPluginVersion, '3.7')) { 348 | gradleEnterprise.buildScan.capture.taskInputFiles = develocityCaptureFileFingerprints 349 | } else { 350 | gradleEnterprise.buildScan.captureTaskInputFiles = develocityCaptureFileFingerprints 351 | } 352 | } 353 | } 354 | ) 355 | } 356 | } 357 | 358 | eachDevelocitySettingsExtension(settings, 359 | { develocity -> 360 | if (develocityUrl && develocityEnforceUrl) { 361 | printEnforcingDevelocityUrl() 362 | develocity.server = develocityUrl 363 | develocity.allowUntrustedServer = develocityAllowUntrustedServer 364 | } 365 | 366 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 367 | printAcceptingGradleTermsOfUse() 368 | develocity.buildScan.termsOfUseUrl = buildScanTermsOfUseUrl 369 | develocity.buildScan.termsOfUseAgree = buildScanTermsOfUseAgree 370 | } 371 | 372 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 373 | develocity.buildScan.uploadInBackground = buildScanUploadInBackground 374 | }, 375 | { gradleEnterprise -> 376 | if (develocityUrl && develocityEnforceUrl) { 377 | printEnforcingDevelocityUrl() 378 | // server and allowUntrustedServer must be configured via buildScan extension for gradle-enterprise-plugin 3.1.1 and earlier 379 | if (gradleEnterprise.metaClass.respondsTo(gradleEnterprise, 'getServer')) { 380 | gradleEnterprise.server = develocityUrl 381 | gradleEnterprise.allowUntrustedServer = develocityAllowUntrustedServer 382 | } else { 383 | gradleEnterprise.buildScan.server = develocityUrl 384 | gradleEnterprise.buildScan.allowUntrustedServer = develocityAllowUntrustedServer 385 | } 386 | } 387 | 388 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 389 | printAcceptingGradleTermsOfUse() 390 | gradleEnterprise.buildScan.termsOfServiceUrl = buildScanTermsOfUseUrl 391 | gradleEnterprise.buildScan.termsOfServiceAgree = buildScanTermsOfUseAgree 392 | } 393 | 394 | // uploadInBackground available for gradle-enterprise-plugin 3.3.4 and later only 395 | if (gradleEnterprise.buildScan.metaClass.respondsTo(gradleEnterprise.buildScan, 'setUploadInBackground', Boolean)) { 396 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 397 | gradleEnterprise.buildScan.uploadInBackground = buildScanUploadInBackground 398 | } 399 | } 400 | ) 401 | 402 | if (ccudPluginVersion) { 403 | if (!settings.pluginManager.hasPlugin(CCUD_PLUGIN_ID)) { 404 | logger.lifecycle("Applying $CCUD_PLUGIN_CLASS with version $ccudPluginVersion via init script") 405 | settings.pluginManager.apply(initscript.classLoader.loadClass(CCUD_PLUGIN_CLASS)) 406 | } 407 | } 408 | } 409 | } 410 | } 411 | 412 | void applyPluginExternally(def pluginManager, String pluginClassName, String pluginVersion) { 413 | logger.lifecycle("Applying $pluginClassName with version $pluginVersion via init script") 414 | 415 | def externallyApplied = 'develocity.externally-applied' 416 | def externallyAppliedDeprecated = 'gradle.enterprise.externally-applied' 417 | def oldValue = System.getProperty(externallyApplied) 418 | def oldValueDeprecated = System.getProperty(externallyAppliedDeprecated) 419 | System.setProperty(externallyApplied, 'true') 420 | System.setProperty(externallyAppliedDeprecated, 'true') 421 | try { 422 | pluginManager.apply(initscript.classLoader.loadClass(pluginClassName)) 423 | } finally { 424 | if (oldValue == null) { 425 | System.clearProperty(externallyApplied) 426 | } else { 427 | System.setProperty(externallyApplied, oldValue) 428 | } 429 | if (oldValueDeprecated == null) { 430 | System.clearProperty(externallyAppliedDeprecated) 431 | } else { 432 | System.setProperty(externallyAppliedDeprecated, oldValueDeprecated) 433 | } 434 | } 435 | } 436 | 437 | /** 438 | * Apply the `dvAction` to all 'develocity' extensions. 439 | * If no 'develocity' extensions are found, apply the `geAction` to all 'gradleEnterprise' extensions. 440 | * (The develocity plugin creates both extensions, and we want to prefer configuring 'develocity'). 441 | */ 442 | static def eachDevelocitySettingsExtension(def settings, def dvAction, def geAction = dvAction) { 443 | def GRADLE_ENTERPRISE_EXTENSION_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterpriseExtension' 444 | def DEVELOCITY_CONFIGURATION_CLASS = 'com.gradle.develocity.agent.gradle.DevelocityConfiguration' 445 | 446 | def dvExtensions = settings.extensions.extensionsSchema.elements 447 | .findAll { it.publicType.concreteClass.name == DEVELOCITY_CONFIGURATION_CLASS } 448 | .collect { settings[it.name] } 449 | if (!dvExtensions.empty) { 450 | dvExtensions.each(dvAction) 451 | } else { 452 | def geExtensions = settings.extensions.extensionsSchema.elements 453 | .findAll { it.publicType.concreteClass.name == GRADLE_ENTERPRISE_EXTENSION_CLASS } 454 | .collect { settings[it.name] } 455 | geExtensions.each(geAction) 456 | } 457 | } 458 | 459 | /** 460 | * Apply the `dvAction` to the 'develocity' extension. 461 | * If no 'develocity' extension is found, apply the `bsAction` to the 'buildScan' extension. 462 | * (The develocity plugin creates both extensions, and we want to prefer configuring 'develocity'). 463 | */ 464 | static def eachDevelocityProjectExtension(def project, def dvAction, def bsAction = dvAction) { 465 | def BUILD_SCAN_PLUGIN_ID = 'com.gradle.build-scan' 466 | def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity' 467 | 468 | def configureDvOrBsExtension = { 469 | if (project.extensions.findByName("develocity")) { 470 | dvAction(project.develocity) 471 | } else { 472 | bsAction(project.buildScan) 473 | } 474 | } 475 | 476 | project.pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID, configureDvOrBsExtension) 477 | 478 | project.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) { 479 | // Proper extension will be configured by the build-scan callback. 480 | if (project.pluginManager.hasPlugin(BUILD_SCAN_PLUGIN_ID)) return 481 | configureDvOrBsExtension() 482 | } 483 | } 484 | 485 | static boolean isAtLeast(String versionUnderTest, String referenceVersion) { 486 | GradleVersion.version(versionUnderTest) >= GradleVersion.version(referenceVersion) 487 | } 488 | 489 | static boolean isNotAtLeast(String versionUnderTest, String referenceVersion) { 490 | !isAtLeast(versionUnderTest, referenceVersion) 491 | } 492 | 493 | void enableBuildScanLinkCapture(BuildScanCollector collector) { 494 | // Conditionally apply and configure the Develocity plugin 495 | if (GradleVersion.current() < GradleVersion.version('6.0')) { 496 | rootProject { 497 | eachDevelocityProjectExtension(project, 498 | { develocity -> buildScanPublishedAction(develocity.buildScan, collector) }, 499 | { buildScan -> buildScanPublishedAction(buildScan, collector) } 500 | ) 501 | } 502 | } else { 503 | gradle.settingsEvaluated { settings -> 504 | eachDevelocitySettingsExtension(settings) { ext -> 505 | buildScanPublishedAction(ext.buildScan, collector) 506 | } 507 | } 508 | } 509 | } 510 | 511 | // Action will only be called if a `BuildScanCollector.captureBuildScanLink` method is present. 512 | // Add `void captureBuildScanLink(String) {}` to the `BuildScanCollector` class to respond to buildScanPublished events 513 | static buildScanPublishedAction(def buildScanExtension, BuildScanCollector collector) { 514 | if (buildScanExtension.metaClass.respondsTo(buildScanExtension, 'buildScanPublished', Action)) { 515 | buildScanExtension.buildScanPublished { scan -> 516 | collector.captureBuildScanLink(scan.buildScanUri.toString()) 517 | } 518 | } 519 | } 520 | 521 | // Custom implementation of BuildScanCollector for GitLab integration 522 | class BuildScanCollector { 523 | def captureBuildScanLink(String buildScanLink) { 524 | if (System.getenv("BUILD_SCAN_REPORT_PATH")) { 525 | def reportFile = new File(System.getenv("BUILD_SCAN_REPORT_PATH")) 526 | def report 527 | // This might have been created by a previous Gradle invocation in the same GitLab job 528 | // Note that we do not handle parallel Gradle scripts invocation, which should be a very edge case in context of a GitLab job 529 | if (reportFile.exists()) { 530 | report = new groovy.json.JsonSlurper().parseText(reportFile.text) as Report 531 | } else { 532 | report = new Report() 533 | } 534 | report.addLink(buildScanLink) 535 | def generator = new groovy.json.JsonGenerator.Options() 536 | .excludeFieldsByName('contentHash', 'originalClassName') 537 | .build() 538 | reportFile.text = generator.toJson(report) 539 | } 540 | } 541 | } 542 | 543 | class Report { 544 | List build_scans = [] 545 | 546 | void addLink(String url) { 547 | build_scans << new Link(url) 548 | } 549 | } 550 | 551 | class Link { 552 | Map external_link 553 | 554 | Link(String url) { 555 | external_link = [ 'label': url, 'url': url ] 556 | } 557 | } 558 | EOF 559 | 560 | export DEVELOCITY_INIT_SCRIPT_PATH="${initScript}" 561 | export BUILD_SCAN_REPORT_PATH="${CI_PROJECT_DIR}/build-scan-links.json" 562 | } 563 | 564 | function createShortLivedToken() { 565 | local allKeys="${GRADLE_ENTERPRISE_ACCESS_KEY:-${DEVELOCITY_ACCESS_KEY}}" 566 | if [ -z "${allKeys}" ] 567 | then 568 | return 0 569 | fi 570 | 571 | local serverUrl=${1} 572 | local expiry="${2}" 573 | local allowUntrusted="${3}" 574 | 575 | local newAccessKey="" 576 | if [[ "${enforceUrl}" == "true" || $(singleKey "${allKeys}") == "true" ]] 577 | then 578 | local hostname=$(extractHostname "${serverUrl}") 579 | local accessKey=$(extractAccessKey "${allKeys}" "${hostname}") 580 | local tokenUrl="${serverUrl}/api/auth/token" 581 | if [ ! -z "${accessKey}" ] 582 | then 583 | local token=$(getShortLivedToken $tokenUrl $expiry $accessKey $allowUntrusted) 584 | if [ ! -z "${token}" ] 585 | then 586 | newAccessKey="${hostname}=${token}" 587 | fi 588 | else 589 | >&2 echo "Could not create short lived access token, no access key matching given Develocity server hostname ${hostname}" 590 | fi 591 | else 592 | local separator=";" 593 | IFS="${separator}" read -ra pairs <<< "${allKeys}" 594 | for pair in "${pairs[@]}"; do 595 | IFS='=' read -r host key <<< "$pair" 596 | local tokenUrl="https://${host}/api/auth/token" 597 | local token=$(getShortLivedToken $tokenUrl $expiry $key $allowUntrusted) 598 | if [ ! -z "${token}" ] 599 | then 600 | if [ -z "${newAccessKey}" ] 601 | then 602 | newAccessKey="${host}=${token}" 603 | else 604 | newAccessKey="${newAccessKey}${separator}${host}=${token}" 605 | fi 606 | fi 607 | done 608 | fi 609 | 610 | export DEVELOCITY_ACCESS_KEY="${newAccessKey}" 611 | export GRADLE_ENTERPRISE_ACCESS_KEY="${DEVELOCITY_ACCESS_KEY}" 612 | } 613 | 614 | function singleKey() { 615 | local allKeys=$1 616 | local separator=";" 617 | IFS="${separator}" read -ra pairs <<< "${allKeys}" 618 | if [ "${#pairs[@]}" -eq 1 ] 619 | then 620 | echo "true" 621 | else 622 | echo "false" 623 | fi 624 | } 625 | 626 | function extractHostname() { 627 | local url=$1 628 | echo "${url}" | cut -d'/' -f3 | cut -d':' -f1 629 | } 630 | 631 | function extractAccessKey() { 632 | local allKeys=$1 633 | local hostname=$2 634 | key="${allKeys#*$hostname=}" # Remove everything before the host name and '=' 635 | if [ "${key}" == "${allKeys}" ] # if nothing has changed, it's not a match 636 | then 637 | echo "" 638 | else 639 | key="${key%%;*}" # Remove everything after the first ';' 640 | echo "$key" 641 | fi 642 | } 643 | 644 | function getShortLivedToken() { 645 | local tokenUrl=$1 646 | local expiry=$2 647 | local accessKey=$3 648 | local allowUntrusted=$4 649 | local maxRetries=3 650 | local retryInterval=1 651 | local attempt=0 652 | 653 | if [ ! -z "${expiry}" ] 654 | then 655 | tokenUrl="${tokenUrl}?expiresInHours=${expiry}" 656 | fi 657 | 658 | local curlOpts=(-s -w "\n%{http_code}" -X POST) 659 | if [ "${allowUntrusted}" == "true" ]; 660 | then 661 | curlOpts+=(-k) 662 | fi 663 | 664 | while [ ${attempt} -le ${maxRetries} ] 665 | do 666 | local response=$(curl "${curlOpts[@]}" "${tokenUrl}" -H "Authorization: Bearer ${accessKey}") 667 | local status_code=$(tail -n1 <<< "${response}") 668 | local shortLivedToken=$(head -n -1 <<< "${response}") 669 | if [[ "${status_code}" == "200" && ! -z "${shortLivedToken}" ]] 670 | then 671 | echo "${shortLivedToken}" 672 | return 673 | elif [ "${status_code}" == "401" ] 674 | then 675 | >&2 echo "Develocity short lived token request failed ${serverUrl} with status code 401" 676 | return 677 | else 678 | ((attempt++)) 679 | sleep ${retryInterval} 680 | fi 681 | done 682 | } 683 | 684 | function injectDevelocityForGradle() { 685 | export "DEVELOCITY_INJECTION_ENABLED=true" 686 | export "DEVELOCITY_INJECTION_INIT_SCRIPT_NAME=init-script.gradle" 687 | export "DEVELOCITY_AUTO_INJECTION_CUSTOM_VALUE=GitLab" 688 | export "DEVELOCITY_URL=$[[ inputs.url ]]" 689 | export "DEVELOCITY_PLUGIN_VERSION=$[[ inputs.gradlePluginVersion ]]" 690 | export "DEVELOCITY_CCUD_PLUGIN_VERSION=$[[ inputs.ccudPluginVersion ]]" 691 | export "DEVELOCITY_ALLOW_UNTRUSTED_SERVER=$[[ inputs.allowUntrustedServer ]]" 692 | export "DEVELOCITY_ENFORCE_URL=$[[ inputs.enforceUrl ]]" 693 | export "DEVELOCITY_CAPTURE_FILE_FINGERPRINTS=$[[ inputs.captureFileFingerprints ]]" 694 | export "GRADLE_PLUGIN_REPOSITORY_URL=$[[ inputs.gradlePluginRepositoryUrl ]]" 695 | export "GRADLE_PLUGIN_REPOSITORY_USERNAME=$[[ inputs.gradlePluginRepositoryUsername ]]" 696 | export "GRADLE_PLUGIN_REPOSITORY_PASSWORD=$[[ inputs.gradlePluginRepositoryPassword ]]" 697 | } 698 | 699 | createGradleInit 700 | createShortLivedToken "$[[ inputs.url ]]" "$[[ inputs.shortLivedTokensExpiry ]]" "$[[ inputs.allowUntrustedServer ]]" 701 | injectDevelocityForGradle 702 | -------------------------------------------------------------------------------- /develocity-maven.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | inputs: 3 | # Develocity server URL 4 | url: 5 | default: 'https://scans.gradle.com' 6 | # Allow untrusted server 7 | allowUntrustedServer: 8 | default: 'false' 9 | # Short-lived tokens expiry in hours 10 | shortLivedTokensExpiry: 11 | default: '2' 12 | # Develocity Maven extension version 13 | mavenExtensionVersion: 14 | default: '2.0.1' 15 | # Common Custom User Data Maven extension version (see https://github.com/gradle/common-custom-user-data-maven-extension) 16 | ccudMavenExtensionVersion: 17 | default: '2.0.2' 18 | # Maven remote repository to download extension jars from 19 | mavenRepo: 20 | default: 'https://repo1.maven.org/maven2' 21 | # Capture file fingerprints, only set if no Develocity extension is already present 22 | captureFileFingerprints: 23 | default: 'true' 24 | # Will not inject the Develocity Maven extension if an extension with provided coordinates is found. 25 | # Expected format 'groupId:artifactId(:version)' 26 | mavenExtensionCustomCoordinates: 27 | default: '' 28 | # Will not inject the CCUD extension if an extension with provided coordinates is found. 29 | # Expected format 'groupId:artifactId(:version)' 30 | ccudExtensionCustomCoordinates: 31 | default: '' 32 | # Enforce URL 33 | enforceUrl: 34 | default: 'true' 35 | --- 36 | .injectDevelocityForMaven: | 37 | ccudMavenExtensionVersion=$[[ inputs.ccudMavenExtensionVersion ]] 38 | mavenRepo=$[[ inputs.mavenRepo ]] 39 | mavenExtensionVersion=$[[ inputs.mavenExtensionVersion ]] 40 | allowUntrustedServer=$[[ inputs.allowUntrustedServer ]] 41 | shortLivedTokensExpiry=$[[ inputs.shortLivedTokensExpiry ]] 42 | enforceUrl=$[[ inputs.enforceUrl ]] 43 | captureFileFingerprints=$[[ inputs.captureFileFingerprints ]] 44 | customMavenExtensionCoordinates=$[[ inputs.mavenExtensionCustomCoordinates ]] 45 | customCcudCoordinates=$[[ inputs.ccudExtensionCustomCoordinates ]] 46 | url=$[[ inputs.url ]] 47 | 48 | #functions-start 49 | function createTmp() { 50 | export TMP_DV=$(mktemp -d develocity.XXXXXX --tmpdir="${CI_PROJECT_DIR}") 51 | } 52 | 53 | function isAtLeast() { 54 | local v1=$(printf "%03d%03d%03d%03d" $(echo "$1" | cut -f1 -d"-" | tr '.' ' ')) 55 | local v2=$(printf "%03d%03d%03d%03d" $(echo "$2" | cut -f1 -d"-" | tr '.' ' ')) 56 | if [ "$v1" -eq "$v2" ] || [ "$v1" -gt "$v2" ] 57 | then 58 | echo "true" 59 | else 60 | echo "false" 61 | fi 62 | } 63 | 64 | function downloadDvCcudExt() { 65 | local ext_path="${TMP_DV}/common-custom-user-data-maven-extension.jar" 66 | curl -s "${mavenRepo}/com/gradle/common-custom-user-data-maven-extension/${ccudMavenExtensionVersion}/common-custom-user-data-maven-extension-${ccudMavenExtensionVersion}.jar" -o "${ext_path}" 67 | export CCUD_EXT_PATH="${ext_path}" 68 | } 69 | 70 | function downloadDvMavenExt() { 71 | local ext_path 72 | if [ "$(isAtLeast $mavenExtensionVersion 1.21)" = "true" ] 73 | then 74 | ext_path="${TMP_DV}/develocity-maven-extension.jar" 75 | curl -s "${mavenRepo}/com/gradle/develocity-maven-extension/${mavenExtensionVersion}/develocity-maven-extension-${mavenExtensionVersion}.jar" -o "${ext_path}" 76 | else 77 | ext_path="${TMP_DV}/gradle-enterprise-maven-extension.jar" 78 | curl -s "${mavenRepo}/com/gradle/gradle-enterprise-maven-extension/${mavenExtensionVersion}/gradle-enterprise-maven-extension-${mavenExtensionVersion}.jar" -o "${ext_path}" 79 | fi 80 | export DEVELOCITY_EXT_PATH="${ext_path}" 81 | } 82 | 83 | function injectDevelocityForMaven() { 84 | local rootDir=$1 85 | local mavenOpts="-Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=${allowUntrustedServer} -Ddevelocity.allowUntrustedServer=${allowUntrustedServer}" 86 | local mavenExtClasspath='' 87 | local appliedCustomDv="false" 88 | local appliedCustomCcud="false" 89 | local appliedCustomDv=$(detectDvExtension "${rootDir}") 90 | if [ "${appliedCustomDv}" = "false" ] 91 | then 92 | mavenExtClasspath="${DEVELOCITY_EXT_PATH}" 93 | fi 94 | local appliedCustomCcud=$(detectExtension "${rootDir}" "$(ccudCoordinates)") 95 | if [ "${appliedCustomCcud}" = "false" ] 96 | then 97 | if [ ! -z "${mavenExtClasspath}" ] 98 | then 99 | mavenExtClasspath+=":" 100 | fi 101 | mavenExtClasspath="${mavenExtClasspath}${CCUD_EXT_PATH}" 102 | fi 103 | if [ ! -z "${mavenExtClasspath}" ] 104 | then 105 | mavenOpts="-Dmaven.ext.class.path=${mavenExtClasspath} ${mavenOpts}" 106 | fi 107 | if [[ ("${appliedCustomDv}" = "true" || "${appliedCustomCcud}" = "true") && "${enforceUrl}" = "true" ]] 108 | then 109 | mavenOpts="${mavenOpts} -Dgradle.enterprise.url=${url} -Ddevelocity.url=${url}" 110 | elif [[ "${appliedCustomDv}" = "false" && "${appliedCustomCcud}" = "false" ]] 111 | then 112 | mavenOpts="${mavenOpts} -Dgradle.enterprise.url=${url} -Ddevelocity.url=${url}" 113 | fi 114 | if [[ "${appliedCustomDv}" = "false" && ("${captureFileFingerprints}" = "true" || "${captureFileFingerprints}" = "false") ]] 115 | then 116 | mavenOpts="${mavenOpts} -Ddevelocity.scan.captureFileFingerprints=${captureFileFingerprints} -Dgradle.scan.captureGoalInputFiles=${captureFileFingerprints}" 117 | fi 118 | local existingMavenOpts=$(if [ ! -z "$MAVEN_OPTS" ]; then echo "${MAVEN_OPTS} "; else echo ''; fi) 119 | export MAVEN_OPTS=${existingMavenOpts}${mavenOpts} 120 | } 121 | 122 | function detectDvExtension() { 123 | local rootDir=$1 124 | if [ ! -z "${customMavenExtensionCoordinates}" ] 125 | then 126 | echo "$(detectExtension $rootDir $customMavenExtensionCoordinates)" 127 | else 128 | local appliedDefaultExtension="$(detectExtension $rootDir 'com.gradle:gradle-enterprise-maven-extension')" 129 | if [ "${appliedDefaultExtension}" = "false" ] 130 | then 131 | appliedDefaultExtension="$(detectExtension $rootDir 'com.gradle:develocity-maven-extension')" 132 | fi 133 | echo "${appliedDefaultExtension}" 134 | fi 135 | } 136 | 137 | function ccudCoordinates() { 138 | local coordinates='com.gradle:common-custom-user-data-maven-extension' 139 | if [ ! -z "${customCcudCoordinates}" ] 140 | then 141 | coordinates="${customCcudCoordinates}" 142 | fi 143 | echo "${coordinates}" 144 | } 145 | 146 | function detectExtension() { 147 | local rootDir=$1 148 | local extCoordinates=$2 149 | local extFile="${rootDir}/.mvn/extensions.xml" 150 | if [ ! -f "${extFile}" ] 151 | then 152 | echo "false" 153 | return 154 | fi 155 | local currentExtension 156 | while readXml 157 | do 158 | if [ "${elementName}" = "groupId" ] 159 | then 160 | currentExtension="${value}" 161 | fi 162 | if [ "${elementName}" = "artifactId" ] 163 | then 164 | currentExtension="${currentExtension}:${value}" 165 | fi 166 | if [ "${elementName}" = "version" ] 167 | then 168 | currentExtension="${currentExtension}:${value}" 169 | fi 170 | if [[ "${currentExtension}" =~ ^.*:.*:.*$ && "${currentExtension}" == *"${extCoordinates}"* ]] 171 | then 172 | echo "true" 173 | return 174 | fi 175 | done < "${extFile}" 176 | echo "false" 177 | } 178 | 179 | function readXml() { 180 | local IFS=\> 181 | read -d \< elementName value 182 | } 183 | 184 | function createShortLivedToken() { 185 | local allKeys="${GRADLE_ENTERPRISE_ACCESS_KEY:-${DEVELOCITY_ACCESS_KEY}}" 186 | if [ -z "${allKeys}" ] 187 | then 188 | return 0 189 | fi 190 | 191 | local serverUrl=${1} 192 | local expiry="${2}" 193 | local allowUntrusted="${3}" 194 | 195 | local newAccessKey="" 196 | if [[ "${enforceUrl}" == "true" || $(singleKey "${allKeys}") == "true" ]] 197 | then 198 | local hostname=$(extractHostname "${serverUrl}") 199 | local accessKey=$(extractAccessKey "${allKeys}" "${hostname}") 200 | local tokenUrl="${serverUrl}/api/auth/token" 201 | if [ ! -z "${accessKey}" ] 202 | then 203 | local token=$(getShortLivedToken $tokenUrl $expiry $accessKey $allowUntrusted) 204 | if [ ! -z "${token}" ] 205 | then 206 | newAccessKey="${hostname}=${token}" 207 | fi 208 | else 209 | >&2 echo "Could not create short lived access token, no access key matching given Develocity server hostname ${hostname}" 210 | fi 211 | else 212 | local separator=";" 213 | IFS="${separator}" read -ra pairs <<< "${allKeys}" 214 | for pair in "${pairs[@]}"; do 215 | IFS='=' read -r host key <<< "$pair" 216 | local tokenUrl="https://${host}/api/auth/token" 217 | local token=$(getShortLivedToken $tokenUrl $expiry $key $allowUntrusted) 218 | if [ ! -z "${token}" ] 219 | then 220 | if [ -z "${newAccessKey}" ] 221 | then 222 | newAccessKey="${host}=${token}" 223 | else 224 | newAccessKey="${newAccessKey}${separator}${host}=${token}" 225 | fi 226 | fi 227 | done 228 | fi 229 | 230 | export DEVELOCITY_ACCESS_KEY="${newAccessKey}" 231 | export GRADLE_ENTERPRISE_ACCESS_KEY="${DEVELOCITY_ACCESS_KEY}" 232 | } 233 | 234 | function singleKey() { 235 | local allKeys=$1 236 | local separator=";" 237 | IFS="${separator}" read -ra pairs <<< "${allKeys}" 238 | if [ "${#pairs[@]}" -eq 1 ] 239 | then 240 | echo "true" 241 | else 242 | echo "false" 243 | fi 244 | } 245 | 246 | function extractHostname() { 247 | local url=$1 248 | echo "${url}" | cut -d'/' -f3 | cut -d':' -f1 249 | } 250 | 251 | function extractAccessKey() { 252 | local allKeys=$1 253 | local hostname=$2 254 | key="${allKeys#*$hostname=}" # Remove everything before the host name and '=' 255 | if [ "${key}" == "${allKeys}" ] # if nothing has changed, it's not a match 256 | then 257 | echo "" 258 | else 259 | key="${key%%;*}" # Remove everything after the first ';' 260 | echo "$key" 261 | fi 262 | } 263 | 264 | function getShortLivedToken() { 265 | local tokenUrl=$1 266 | local expiry=$2 267 | local accessKey=$3 268 | local allowUntrusted=$4 269 | local maxRetries=3 270 | local retryInterval=1 271 | local attempt=0 272 | 273 | if [ ! -z "${expiry}" ] 274 | then 275 | tokenUrl="${tokenUrl}?expiresInHours=${expiry}" 276 | fi 277 | 278 | local curlOpts=(-s -w "\n%{http_code}" -X POST) 279 | if [ "${allowUntrusted}" == "true" ]; 280 | then 281 | curlOpts+=(-k) 282 | fi 283 | 284 | while [ ${attempt} -le ${maxRetries} ] 285 | do 286 | local response=$(curl "${curlOpts[@]}" "${tokenUrl}" -H "Authorization: Bearer ${accessKey}") 287 | local status_code=$(tail -n1 <<< "${response}") 288 | local shortLivedToken=$(head -n -1 <<< "${response}") 289 | if [[ "${status_code}" == "200" && ! -z "${shortLivedToken}" ]] 290 | then 291 | echo "${shortLivedToken}" 292 | return 293 | elif [ "${status_code}" == "401" ] 294 | then 295 | >&2 echo "Develocity short lived token request failed ${serverUrl} with status code 401" 296 | return 297 | else 298 | ((attempt++)) 299 | sleep ${retryInterval} 300 | fi 301 | done 302 | } 303 | #functions-end 304 | 305 | createTmp 306 | downloadDvCcudExt 307 | downloadDvMavenExt 308 | createShortLivedToken "${url}" "${shortLivedTokensExpiry}" "${allowUntrustedServer}" 309 | injectDevelocityForMaven "${PWD}" 310 | -------------------------------------------------------------------------------- /images/build-scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/d2c86d1a79a27c2c64fba0bb66cefca58cb25b80/images/build-scan.png -------------------------------------------------------------------------------- /images/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradle/develocity-gitlab-templates/d2c86d1a79a27c2c64fba0bb66cefca58cb25b80/images/links.png -------------------------------------------------------------------------------- /src/gradle/develocity-gradle.template.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | inputs: 3 | # Develocity server URL 4 | url: 5 | default: 'https://scans.gradle.com' 6 | # Develocity Plugin version 7 | # Allow untrusted server 8 | allowUntrustedServer: 9 | default: 'false' 10 | # Short-lived tokens expiry in hours 11 | shortLivedTokensExpiry: 12 | default: '2' 13 | gradlePluginVersion: 14 | default: '4.0.2' 15 | # Common Custom User Data Gradle Plugin version (see https://github.com/gradle/common-custom-user-data-gradle-plugin/) 16 | ccudPluginVersion: 17 | default: '2.3' 18 | # Develocity Gradle plugin repository URL, defaults in the init script to https://plugins.gradle.org/m2 19 | gradlePluginRepositoryUrl: 20 | default: '' 21 | # Develocity Gradle plugin repository username 22 | gradlePluginRepositoryUsername: 23 | default: '' 24 | # Develocity Gradle plugin repository password, strongly advised to pass a protected and masked variable 25 | gradlePluginRepositoryPassword: 26 | default: '' 27 | # Capture file fingerprints, only set if no Develocity plugin is already present 28 | captureFileFingerprints: 29 | default: 'true' 30 | # Enforce the url over any defined locally to the project 31 | enforceUrl: 32 | default: 'false' 33 | 34 | --- 35 | .build_scan_links_report: 36 | artifacts: 37 | reports: 38 | annotations: $CI_PROJECT_DIR/build-scan-links.json 39 | 40 | .injectDevelocityForGradle: | 41 | function createGradleInit() { 42 | local initScript="${CI_PROJECT_DIR}/init-script.gradle" 43 | 44 | cat > $initScript <<'EOF' 45 | <> 46 | EOF 47 | 48 | export DEVELOCITY_INIT_SCRIPT_PATH="${initScript}" 49 | export BUILD_SCAN_REPORT_PATH="${CI_PROJECT_DIR}/build-scan-links.json" 50 | } 51 | 52 | function createShortLivedToken() { 53 | local allKeys="${GRADLE_ENTERPRISE_ACCESS_KEY:-${DEVELOCITY_ACCESS_KEY}}" 54 | if [ -z "${allKeys}" ] 55 | then 56 | return 0 57 | fi 58 | 59 | local serverUrl=${1} 60 | local expiry="${2}" 61 | local allowUntrusted="${3}" 62 | 63 | local newAccessKey="" 64 | if [[ "${enforceUrl}" == "true" || $(singleKey "${allKeys}") == "true" ]] 65 | then 66 | local hostname=$(extractHostname "${serverUrl}") 67 | local accessKey=$(extractAccessKey "${allKeys}" "${hostname}") 68 | local tokenUrl="${serverUrl}/api/auth/token" 69 | if [ ! -z "${accessKey}" ] 70 | then 71 | local token=$(getShortLivedToken $tokenUrl $expiry $accessKey $allowUntrusted) 72 | if [ ! -z "${token}" ] 73 | then 74 | newAccessKey="${hostname}=${token}" 75 | fi 76 | else 77 | >&2 echo "Could not create short lived access token, no access key matching given Develocity server hostname ${hostname}" 78 | fi 79 | else 80 | local separator=";" 81 | IFS="${separator}" read -ra pairs <<< "${allKeys}" 82 | for pair in "${pairs[@]}"; do 83 | IFS='=' read -r host key <<< "$pair" 84 | local tokenUrl="https://${host}/api/auth/token" 85 | local token=$(getShortLivedToken $tokenUrl $expiry $key $allowUntrusted) 86 | if [ ! -z "${token}" ] 87 | then 88 | if [ -z "${newAccessKey}" ] 89 | then 90 | newAccessKey="${host}=${token}" 91 | else 92 | newAccessKey="${newAccessKey}${separator}${host}=${token}" 93 | fi 94 | fi 95 | done 96 | fi 97 | 98 | export DEVELOCITY_ACCESS_KEY="${newAccessKey}" 99 | export GRADLE_ENTERPRISE_ACCESS_KEY="${DEVELOCITY_ACCESS_KEY}" 100 | } 101 | 102 | function singleKey() { 103 | local allKeys=$1 104 | local separator=";" 105 | IFS="${separator}" read -ra pairs <<< "${allKeys}" 106 | if [ "${#pairs[@]}" -eq 1 ] 107 | then 108 | echo "true" 109 | else 110 | echo "false" 111 | fi 112 | } 113 | 114 | function extractHostname() { 115 | local url=$1 116 | echo "${url}" | cut -d'/' -f3 | cut -d':' -f1 117 | } 118 | 119 | function extractAccessKey() { 120 | local allKeys=$1 121 | local hostname=$2 122 | key="${allKeys#*$hostname=}" # Remove everything before the host name and '=' 123 | if [ "${key}" == "${allKeys}" ] # if nothing has changed, it's not a match 124 | then 125 | echo "" 126 | else 127 | key="${key%%;*}" # Remove everything after the first ';' 128 | echo "$key" 129 | fi 130 | } 131 | 132 | function getShortLivedToken() { 133 | local tokenUrl=$1 134 | local expiry=$2 135 | local accessKey=$3 136 | local allowUntrusted=$4 137 | local maxRetries=3 138 | local retryInterval=1 139 | local attempt=0 140 | 141 | if [ ! -z "${expiry}" ] 142 | then 143 | tokenUrl="${tokenUrl}?expiresInHours=${expiry}" 144 | fi 145 | 146 | local curlOpts=(-s -w "\n%{http_code}" -X POST) 147 | if [ "${allowUntrusted}" == "true" ]; 148 | then 149 | curlOpts+=(-k) 150 | fi 151 | 152 | while [ ${attempt} -le ${maxRetries} ] 153 | do 154 | local response=$(curl "${curlOpts[@]}" "${tokenUrl}" -H "Authorization: Bearer ${accessKey}") 155 | local status_code=$(tail -n1 <<< "${response}") 156 | local shortLivedToken=$(head -n -1 <<< "${response}") 157 | if [[ "${status_code}" == "200" && ! -z "${shortLivedToken}" ]] 158 | then 159 | echo "${shortLivedToken}" 160 | return 161 | elif [ "${status_code}" == "401" ] 162 | then 163 | >&2 echo "Develocity short lived token request failed ${serverUrl} with status code 401" 164 | return 165 | else 166 | ((attempt++)) 167 | sleep ${retryInterval} 168 | fi 169 | done 170 | } 171 | 172 | function injectDevelocityForGradle() { 173 | export "DEVELOCITY_INJECTION_ENABLED=true" 174 | export "DEVELOCITY_INJECTION_INIT_SCRIPT_NAME=init-script.gradle" 175 | export "DEVELOCITY_AUTO_INJECTION_CUSTOM_VALUE=GitLab" 176 | export "DEVELOCITY_URL=$[[ inputs.url ]]" 177 | export "DEVELOCITY_PLUGIN_VERSION=$[[ inputs.gradlePluginVersion ]]" 178 | export "DEVELOCITY_CCUD_PLUGIN_VERSION=$[[ inputs.ccudPluginVersion ]]" 179 | export "DEVELOCITY_ALLOW_UNTRUSTED_SERVER=$[[ inputs.allowUntrustedServer ]]" 180 | export "DEVELOCITY_ENFORCE_URL=$[[ inputs.enforceUrl ]]" 181 | export "DEVELOCITY_CAPTURE_FILE_FINGERPRINTS=$[[ inputs.captureFileFingerprints ]]" 182 | export "GRADLE_PLUGIN_REPOSITORY_URL=$[[ inputs.gradlePluginRepositoryUrl ]]" 183 | export "GRADLE_PLUGIN_REPOSITORY_USERNAME=$[[ inputs.gradlePluginRepositoryUsername ]]" 184 | export "GRADLE_PLUGIN_REPOSITORY_PASSWORD=$[[ inputs.gradlePluginRepositoryPassword ]]" 185 | } 186 | 187 | createGradleInit 188 | createShortLivedToken "$[[ inputs.url ]]" "$[[ inputs.shortLivedTokensExpiry ]]" "$[[ inputs.allowUntrustedServer ]]" 189 | injectDevelocityForGradle 190 | -------------------------------------------------------------------------------- /src/gradle/init-scripts/build-scan-collector.groovy: -------------------------------------------------------------------------------- 1 | // Custom implementation of BuildScanCollector for GitLab integration 2 | class BuildScanCollector { 3 | def captureBuildScanLink(String buildScanLink) { 4 | if (System.getenv("BUILD_SCAN_REPORT_PATH")) { 5 | def reportFile = new File(System.getenv("BUILD_SCAN_REPORT_PATH")) 6 | def report 7 | // This might have been created by a previous Gradle invocation in the same GitLab job 8 | // Note that we do not handle parallel Gradle scripts invocation, which should be a very edge case in context of a GitLab job 9 | if (reportFile.exists()) { 10 | report = new groovy.json.JsonSlurper().parseText(reportFile.text) as Report 11 | } else { 12 | report = new Report() 13 | } 14 | report.addLink(buildScanLink) 15 | def generator = new groovy.json.JsonGenerator.Options() 16 | .excludeFieldsByName('contentHash', 'originalClassName') 17 | .build() 18 | reportFile.text = generator.toJson(report) 19 | } 20 | } 21 | } 22 | 23 | class Report { 24 | List build_scans = [] 25 | 26 | void addLink(String url) { 27 | build_scans << new Link(url) 28 | } 29 | } 30 | 31 | class Link { 32 | Map external_link 33 | 34 | Link(String url) { 35 | external_link = [ 'label': url, 'url': url ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/gradle/init-scripts/develocity-injection.init.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Initscript for injection of Develocity into Gradle builds. 3 | * Version: 1.2 4 | */ 5 | 6 | import org.gradle.util.GradleVersion 7 | 8 | initscript { 9 | // NOTE: there is no mechanism to share code between the initscript{} block and the main script, so some logic is duplicated 10 | def isTopLevelBuild = !gradle.parent 11 | if (!isTopLevelBuild) { 12 | return 13 | } 14 | 15 | def getInputParam = { Gradle gradle, String name -> 16 | def ENV_VAR_PREFIX = '' 17 | def envVarName = ENV_VAR_PREFIX + name.toUpperCase().replace('.', '_').replace('-', '_') 18 | return gradle.startParameter.systemPropertiesArgs[name] ?: System.getProperty(name) ?: System.getenv(envVarName) 19 | } 20 | 21 | def requestedInitScriptName = getInputParam(gradle, 'develocity.injection.init-script-name') 22 | def initScriptName = buildscript.sourceFile.name 23 | if (requestedInitScriptName != initScriptName) { 24 | return 25 | } 26 | 27 | // Plugin loading is only required for Develocity injection. Abort early if not enabled. 28 | def develocityInjectionEnabled = Boolean.parseBoolean(getInputParam(gradle, "develocity.injection-enabled")) 29 | if (!develocityInjectionEnabled) { 30 | return 31 | } 32 | 33 | def pluginRepositoryUrl = getInputParam(gradle, 'gradle.plugin-repository.url') 34 | def pluginRepositoryUsername = getInputParam(gradle, 'gradle.plugin-repository.username') 35 | def pluginRepositoryPassword = getInputParam(gradle, 'gradle.plugin-repository.password') 36 | def develocityPluginVersion = getInputParam(gradle, 'develocity.plugin.version') 37 | def ccudPluginVersion = getInputParam(gradle, 'develocity.ccud-plugin.version') 38 | 39 | def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0') 40 | def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0') 41 | 42 | if (develocityPluginVersion || ccudPluginVersion && atLeastGradle4) { 43 | pluginRepositoryUrl = pluginRepositoryUrl ?: 'https://plugins.gradle.org/m2' 44 | logger.lifecycle("Develocity plugins resolution: $pluginRepositoryUrl") 45 | 46 | repositories { 47 | maven { 48 | url = pluginRepositoryUrl 49 | if (pluginRepositoryUsername && pluginRepositoryPassword) { 50 | logger.lifecycle("Using credentials for plugin repository") 51 | credentials { 52 | username = pluginRepositoryUsername 53 | password = pluginRepositoryPassword 54 | } 55 | authentication { 56 | basic(BasicAuthentication) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | dependencies { 64 | if (develocityPluginVersion) { 65 | if (atLeastGradle5) { 66 | if (GradleVersion.version(develocityPluginVersion) >= GradleVersion.version("3.17")) { 67 | classpath "com.gradle:develocity-gradle-plugin:$develocityPluginVersion" 68 | } else { 69 | classpath "com.gradle:gradle-enterprise-gradle-plugin:$develocityPluginVersion" 70 | } 71 | } else { 72 | classpath "com.gradle:build-scan-plugin:1.16" 73 | } 74 | } 75 | 76 | if (ccudPluginVersion && atLeastGradle4) { 77 | classpath "com.gradle:common-custom-user-data-gradle-plugin:$ccudPluginVersion" 78 | } 79 | } 80 | } 81 | 82 | static getInputParam(Gradle gradle, String name) { 83 | def ENV_VAR_PREFIX = '' 84 | def envVarName = ENV_VAR_PREFIX + name.toUpperCase().replace('.', '_').replace('-', '_') 85 | return gradle.startParameter.systemPropertiesArgs[name] ?: System.getProperty(name) ?: System.getenv(envVarName) 86 | } 87 | 88 | def isTopLevelBuild = !gradle.parent 89 | if (!isTopLevelBuild) { 90 | return 91 | } 92 | 93 | def requestedInitScriptName = getInputParam(gradle, 'develocity.injection.init-script-name') 94 | def initScriptName = buildscript.sourceFile.name 95 | if (requestedInitScriptName != initScriptName) { 96 | logger.quiet("Ignoring init script '${initScriptName}' as requested name '${requestedInitScriptName}' does not match") 97 | return 98 | } 99 | 100 | def develocityInjectionEnabled = Boolean.parseBoolean(getInputParam(gradle, "develocity.injection-enabled")) 101 | if (develocityInjectionEnabled) { 102 | enableDevelocityInjection() 103 | } 104 | 105 | // To enable build-scan capture, a `captureBuildScanLink(String)` method must be added to `BuildScanCollector`. 106 | def buildScanCollector = new BuildScanCollector() 107 | def buildScanCaptureEnabled = buildScanCollector.metaClass.respondsTo(buildScanCollector, 'captureBuildScanLink', String) 108 | if (buildScanCaptureEnabled) { 109 | enableBuildScanLinkCapture(buildScanCollector) 110 | } 111 | 112 | void enableDevelocityInjection() { 113 | def BUILD_SCAN_PLUGIN_CLASS = 'com.gradle.scan.plugin.BuildScanPlugin' 114 | 115 | def GRADLE_ENTERPRISE_PLUGIN_ID = 'com.gradle.enterprise' 116 | def GRADLE_ENTERPRISE_PLUGIN_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterprisePlugin' 117 | 118 | def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity' 119 | def DEVELOCITY_PLUGIN_CLASS = 'com.gradle.develocity.agent.gradle.DevelocityPlugin' 120 | 121 | def CI_AUTO_INJECTION_CUSTOM_VALUE_NAME = 'CI auto injection' 122 | def CCUD_PLUGIN_ID = 'com.gradle.common-custom-user-data-gradle-plugin' 123 | def CCUD_PLUGIN_CLASS = 'com.gradle.CommonCustomUserDataGradlePlugin' 124 | 125 | def develocityUrl = getInputParam(gradle, 'develocity.url') 126 | def develocityAllowUntrustedServer = Boolean.parseBoolean(getInputParam(gradle, 'develocity.allow-untrusted-server')) 127 | def develocityEnforceUrl = Boolean.parseBoolean(getInputParam(gradle, 'develocity.enforce-url')) 128 | def buildScanUploadInBackground = Boolean.parseBoolean(getInputParam(gradle, 'develocity.build-scan.upload-in-background')) 129 | def develocityCaptureFileFingerprints = getInputParam(gradle, 'develocity.capture-file-fingerprints') ? Boolean.parseBoolean(getInputParam(gradle, 'develocity.capture-file-fingerprints')) : true 130 | def develocityPluginVersion = getInputParam(gradle, 'develocity.plugin.version') 131 | def ccudPluginVersion = getInputParam(gradle, 'develocity.ccud-plugin.version') 132 | def buildScanTermsOfUseUrl = getInputParam(gradle, 'develocity.terms-of-use.url') 133 | def buildScanTermsOfUseAgree = getInputParam(gradle, 'develocity.terms-of-use.agree') 134 | def ciAutoInjectionCustomValueValue = getInputParam(gradle, 'develocity.auto-injection.custom-value') 135 | 136 | def atLeastGradle5 = GradleVersion.current() >= GradleVersion.version('5.0') 137 | def atLeastGradle4 = GradleVersion.current() >= GradleVersion.version('4.0') 138 | def shouldApplyDevelocityPlugin = atLeastGradle5 && develocityPluginVersion && isAtLeast(develocityPluginVersion, '3.17') 139 | 140 | def dvOrGe = { def dvValue, def geValue -> 141 | if (shouldApplyDevelocityPlugin) { 142 | return dvValue instanceof Closure ? dvValue() : dvValue 143 | } 144 | return geValue instanceof Closure ? geValue() : geValue 145 | } 146 | 147 | def printEnforcingDevelocityUrl = { 148 | logger.lifecycle("Enforcing Develocity: $develocityUrl, allowUntrustedServer: $develocityAllowUntrustedServer") 149 | } 150 | 151 | def printAcceptingGradleTermsOfUse = { 152 | logger.lifecycle("Accepting Gradle Terms of Use: $buildScanTermsOfUseUrl") 153 | } 154 | 155 | // finish early if DV plugin version is unsupported (v3.6.4 is the minimum version tested and supports back to DV 2021.1) 156 | if (develocityPluginVersion && isNotAtLeast(develocityPluginVersion, '3.6.4')) { 157 | logger.warn("Develocity Gradle plugin must be at least 3.6.4. Configured version is $develocityPluginVersion.") 158 | return 159 | } 160 | 161 | // finish early if configuration parameters passed in via system properties are not valid/supported 162 | if (ccudPluginVersion && isNotAtLeast(ccudPluginVersion, '1.7')) { 163 | logger.warn("Common Custom User Data Gradle plugin must be at least 1.7. Configured version is $ccudPluginVersion.") 164 | return 165 | } 166 | 167 | // Conditionally apply and configure the Develocity plugin 168 | if (GradleVersion.current() < GradleVersion.version('6.0')) { 169 | rootProject { 170 | buildscript.configurations.getByName("classpath").incoming.afterResolve { ResolvableDependencies incoming -> 171 | def resolutionResult = incoming.resolutionResult 172 | 173 | if (develocityPluginVersion) { 174 | def scanPluginComponent = resolutionResult.allComponents.find { 175 | it.moduleVersion.with { group == "com.gradle" && ['build-scan-plugin', 'gradle-enterprise-gradle-plugin', 'develocity-gradle-plugin'].contains(name) } 176 | } 177 | if (!scanPluginComponent) { 178 | def pluginClass = dvOrGe(DEVELOCITY_PLUGIN_CLASS, BUILD_SCAN_PLUGIN_CLASS) 179 | def pluginVersion = atLeastGradle5 ? develocityPluginVersion : "1.16" 180 | applyPluginExternally(pluginManager, pluginClass, pluginVersion) 181 | def rootExtension = dvOrGe( 182 | { develocity }, 183 | { buildScan } 184 | ) 185 | def buildScanExtension = dvOrGe( 186 | { rootExtension.buildScan }, 187 | { rootExtension } 188 | ) 189 | if (develocityUrl) { 190 | logger.lifecycle("Connection to Develocity: $develocityUrl, allowUntrustedServer: $develocityAllowUntrustedServer, captureFileFingerprints: $develocityCaptureFileFingerprints") 191 | rootExtension.server = develocityUrl 192 | rootExtension.allowUntrustedServer = develocityAllowUntrustedServer 193 | } 194 | if (!shouldApplyDevelocityPlugin) { 195 | // Develocity plugin publishes scans by default 196 | buildScanExtension.publishAlways() 197 | } 198 | buildScanExtension.value CI_AUTO_INJECTION_CUSTOM_VALUE_NAME, ciAutoInjectionCustomValueValue 199 | if (isAtLeast(develocityPluginVersion, '2.1') && atLeastGradle5) { 200 | logger.lifecycle("Setting captureFileFingerprints: $develocityCaptureFileFingerprints") 201 | if (isAtLeast(develocityPluginVersion, '3.17')) { 202 | buildScanExtension.capture.fileFingerprints.set(develocityCaptureFileFingerprints) 203 | } else if (isAtLeast(develocityPluginVersion, '3.7')) { 204 | buildScanExtension.capture.taskInputFiles = develocityCaptureFileFingerprints 205 | } else { 206 | buildScanExtension.captureTaskInputFiles = develocityCaptureFileFingerprints 207 | } 208 | } 209 | } 210 | } 211 | 212 | eachDevelocityProjectExtension(project, 213 | { develocity -> 214 | afterEvaluate { 215 | if (develocityUrl && develocityEnforceUrl) { 216 | printEnforcingDevelocityUrl() 217 | develocity.server = develocityUrl 218 | develocity.allowUntrustedServer = develocityAllowUntrustedServer 219 | } 220 | } 221 | 222 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 223 | printAcceptingGradleTermsOfUse() 224 | develocity.buildScan.termsOfUseUrl = buildScanTermsOfUseUrl 225 | develocity.buildScan.termsOfUseAgree = buildScanTermsOfUseAgree 226 | } 227 | 228 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 229 | develocity.buildScan.uploadInBackground = buildScanUploadInBackground 230 | }, 231 | { buildScan -> 232 | afterEvaluate { 233 | if (develocityUrl && develocityEnforceUrl) { 234 | printEnforcingDevelocityUrl() 235 | buildScan.server = develocityUrl 236 | buildScan.allowUntrustedServer = develocityAllowUntrustedServer 237 | } 238 | } 239 | 240 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 241 | printAcceptingGradleTermsOfUse() 242 | if (buildScan.metaClass.respondsTo(buildScan, 'setTermsOfServiceUrl', String)) { 243 | buildScan.termsOfServiceUrl = buildScanTermsOfUseUrl 244 | buildScan.termsOfServiceAgree = buildScanTermsOfUseAgree 245 | } else { 246 | buildScan.licenseAgreementUrl = buildScanTermsOfUseUrl 247 | buildScan.licenseAgree = buildScanTermsOfUseAgree 248 | } 249 | } 250 | 251 | // uploadInBackground available for build-scan-plugin 3.3.4 and later only 252 | if (buildScan.metaClass.respondsTo(buildScan, 'setUploadInBackground', Boolean)) { 253 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 254 | buildScan.uploadInBackground = buildScanUploadInBackground 255 | } 256 | } 257 | ) 258 | 259 | if (ccudPluginVersion && atLeastGradle4) { 260 | def ccudPluginComponent = resolutionResult.allComponents.find { 261 | it.moduleVersion.with { group == "com.gradle" && name == "common-custom-user-data-gradle-plugin" } 262 | } 263 | if (!ccudPluginComponent) { 264 | logger.lifecycle("Applying $CCUD_PLUGIN_CLASS with version $ccudPluginVersion via init script") 265 | pluginManager.apply(initscript.classLoader.loadClass(CCUD_PLUGIN_CLASS)) 266 | } 267 | } 268 | } 269 | } 270 | } else { 271 | gradle.settingsEvaluated { settings -> 272 | if (develocityPluginVersion) { 273 | if (!settings.pluginManager.hasPlugin(GRADLE_ENTERPRISE_PLUGIN_ID) && !settings.pluginManager.hasPlugin(DEVELOCITY_PLUGIN_ID)) { 274 | def pluginClass = dvOrGe(DEVELOCITY_PLUGIN_CLASS, GRADLE_ENTERPRISE_PLUGIN_CLASS) 275 | applyPluginExternally(settings.pluginManager, pluginClass, develocityPluginVersion) 276 | if (develocityUrl) { 277 | logger.lifecycle("Connection to Develocity: $develocityUrl, allowUntrustedServer: $develocityAllowUntrustedServer, captureFileFingerprints: $develocityCaptureFileFingerprints") 278 | eachDevelocitySettingsExtension(settings) { ext -> 279 | // server and allowUntrustedServer must be configured via buildScan extension for gradle-enterprise-plugin 3.1.1 and earlier 280 | if (ext.metaClass.respondsTo(ext, 'getServer')) { 281 | ext.server = develocityUrl 282 | ext.allowUntrustedServer = develocityAllowUntrustedServer 283 | } else { 284 | ext.buildScan.server = develocityUrl 285 | ext.buildScan.allowUntrustedServer = develocityAllowUntrustedServer 286 | } 287 | } 288 | } 289 | 290 | eachDevelocitySettingsExtension(settings) { ext -> 291 | ext.buildScan.value CI_AUTO_INJECTION_CUSTOM_VALUE_NAME, ciAutoInjectionCustomValueValue 292 | } 293 | 294 | eachDevelocitySettingsExtension(settings, 295 | { develocity -> 296 | logger.lifecycle("Setting captureFileFingerprints: $develocityCaptureFileFingerprints") 297 | develocity.buildScan.capture.fileFingerprints = develocityCaptureFileFingerprints 298 | }, 299 | { gradleEnterprise -> 300 | gradleEnterprise.buildScan.publishAlways() 301 | if (isAtLeast(develocityPluginVersion, '2.1')) { 302 | logger.lifecycle("Setting captureFileFingerprints: $develocityCaptureFileFingerprints") 303 | if (isAtLeast(develocityPluginVersion, '3.7')) { 304 | gradleEnterprise.buildScan.capture.taskInputFiles = develocityCaptureFileFingerprints 305 | } else { 306 | gradleEnterprise.buildScan.captureTaskInputFiles = develocityCaptureFileFingerprints 307 | } 308 | } 309 | } 310 | ) 311 | } 312 | } 313 | 314 | eachDevelocitySettingsExtension(settings, 315 | { develocity -> 316 | if (develocityUrl && develocityEnforceUrl) { 317 | printEnforcingDevelocityUrl() 318 | develocity.server = develocityUrl 319 | develocity.allowUntrustedServer = develocityAllowUntrustedServer 320 | } 321 | 322 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 323 | printAcceptingGradleTermsOfUse() 324 | develocity.buildScan.termsOfUseUrl = buildScanTermsOfUseUrl 325 | develocity.buildScan.termsOfUseAgree = buildScanTermsOfUseAgree 326 | } 327 | 328 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 329 | develocity.buildScan.uploadInBackground = buildScanUploadInBackground 330 | }, 331 | { gradleEnterprise -> 332 | if (develocityUrl && develocityEnforceUrl) { 333 | printEnforcingDevelocityUrl() 334 | // server and allowUntrustedServer must be configured via buildScan extension for gradle-enterprise-plugin 3.1.1 and earlier 335 | if (gradleEnterprise.metaClass.respondsTo(gradleEnterprise, 'getServer')) { 336 | gradleEnterprise.server = develocityUrl 337 | gradleEnterprise.allowUntrustedServer = develocityAllowUntrustedServer 338 | } else { 339 | gradleEnterprise.buildScan.server = develocityUrl 340 | gradleEnterprise.buildScan.allowUntrustedServer = develocityAllowUntrustedServer 341 | } 342 | } 343 | 344 | if (buildScanTermsOfUseUrl && buildScanTermsOfUseAgree) { 345 | printAcceptingGradleTermsOfUse() 346 | gradleEnterprise.buildScan.termsOfServiceUrl = buildScanTermsOfUseUrl 347 | gradleEnterprise.buildScan.termsOfServiceAgree = buildScanTermsOfUseAgree 348 | } 349 | 350 | // uploadInBackground available for gradle-enterprise-plugin 3.3.4 and later only 351 | if (gradleEnterprise.buildScan.metaClass.respondsTo(gradleEnterprise.buildScan, 'setUploadInBackground', Boolean)) { 352 | logger.lifecycle("Setting uploadInBackground: $buildScanUploadInBackground") 353 | gradleEnterprise.buildScan.uploadInBackground = buildScanUploadInBackground 354 | } 355 | } 356 | ) 357 | 358 | if (ccudPluginVersion) { 359 | if (!settings.pluginManager.hasPlugin(CCUD_PLUGIN_ID)) { 360 | logger.lifecycle("Applying $CCUD_PLUGIN_CLASS with version $ccudPluginVersion via init script") 361 | settings.pluginManager.apply(initscript.classLoader.loadClass(CCUD_PLUGIN_CLASS)) 362 | } 363 | } 364 | } 365 | } 366 | } 367 | 368 | void applyPluginExternally(def pluginManager, String pluginClassName, String pluginVersion) { 369 | logger.lifecycle("Applying $pluginClassName with version $pluginVersion via init script") 370 | 371 | def externallyApplied = 'develocity.externally-applied' 372 | def externallyAppliedDeprecated = 'gradle.enterprise.externally-applied' 373 | def oldValue = System.getProperty(externallyApplied) 374 | def oldValueDeprecated = System.getProperty(externallyAppliedDeprecated) 375 | System.setProperty(externallyApplied, 'true') 376 | System.setProperty(externallyAppliedDeprecated, 'true') 377 | try { 378 | pluginManager.apply(initscript.classLoader.loadClass(pluginClassName)) 379 | } finally { 380 | if (oldValue == null) { 381 | System.clearProperty(externallyApplied) 382 | } else { 383 | System.setProperty(externallyApplied, oldValue) 384 | } 385 | if (oldValueDeprecated == null) { 386 | System.clearProperty(externallyAppliedDeprecated) 387 | } else { 388 | System.setProperty(externallyAppliedDeprecated, oldValueDeprecated) 389 | } 390 | } 391 | } 392 | 393 | /** 394 | * Apply the `dvAction` to all 'develocity' extensions. 395 | * If no 'develocity' extensions are found, apply the `geAction` to all 'gradleEnterprise' extensions. 396 | * (The develocity plugin creates both extensions, and we want to prefer configuring 'develocity'). 397 | */ 398 | static def eachDevelocitySettingsExtension(def settings, def dvAction, def geAction = dvAction) { 399 | def GRADLE_ENTERPRISE_EXTENSION_CLASS = 'com.gradle.enterprise.gradleplugin.GradleEnterpriseExtension' 400 | def DEVELOCITY_CONFIGURATION_CLASS = 'com.gradle.develocity.agent.gradle.DevelocityConfiguration' 401 | 402 | def dvExtensions = settings.extensions.extensionsSchema.elements 403 | .findAll { it.publicType.concreteClass.name == DEVELOCITY_CONFIGURATION_CLASS } 404 | .collect { settings[it.name] } 405 | if (!dvExtensions.empty) { 406 | dvExtensions.each(dvAction) 407 | } else { 408 | def geExtensions = settings.extensions.extensionsSchema.elements 409 | .findAll { it.publicType.concreteClass.name == GRADLE_ENTERPRISE_EXTENSION_CLASS } 410 | .collect { settings[it.name] } 411 | geExtensions.each(geAction) 412 | } 413 | } 414 | 415 | /** 416 | * Apply the `dvAction` to the 'develocity' extension. 417 | * If no 'develocity' extension is found, apply the `bsAction` to the 'buildScan' extension. 418 | * (The develocity plugin creates both extensions, and we want to prefer configuring 'develocity'). 419 | */ 420 | static def eachDevelocityProjectExtension(def project, def dvAction, def bsAction = dvAction) { 421 | def BUILD_SCAN_PLUGIN_ID = 'com.gradle.build-scan' 422 | def DEVELOCITY_PLUGIN_ID = 'com.gradle.develocity' 423 | 424 | def configureDvOrBsExtension = { 425 | if (project.extensions.findByName("develocity")) { 426 | dvAction(project.develocity) 427 | } else { 428 | bsAction(project.buildScan) 429 | } 430 | } 431 | 432 | project.pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID, configureDvOrBsExtension) 433 | 434 | project.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) { 435 | // Proper extension will be configured by the build-scan callback. 436 | if (project.pluginManager.hasPlugin(BUILD_SCAN_PLUGIN_ID)) return 437 | configureDvOrBsExtension() 438 | } 439 | } 440 | 441 | static boolean isAtLeast(String versionUnderTest, String referenceVersion) { 442 | GradleVersion.version(versionUnderTest) >= GradleVersion.version(referenceVersion) 443 | } 444 | 445 | static boolean isNotAtLeast(String versionUnderTest, String referenceVersion) { 446 | !isAtLeast(versionUnderTest, referenceVersion) 447 | } 448 | 449 | void enableBuildScanLinkCapture(BuildScanCollector collector) { 450 | // Conditionally apply and configure the Develocity plugin 451 | if (GradleVersion.current() < GradleVersion.version('6.0')) { 452 | rootProject { 453 | eachDevelocityProjectExtension(project, 454 | { develocity -> buildScanPublishedAction(develocity.buildScan, collector) }, 455 | { buildScan -> buildScanPublishedAction(buildScan, collector) } 456 | ) 457 | } 458 | } else { 459 | gradle.settingsEvaluated { settings -> 460 | eachDevelocitySettingsExtension(settings) { ext -> 461 | buildScanPublishedAction(ext.buildScan, collector) 462 | } 463 | } 464 | } 465 | } 466 | 467 | // Action will only be called if a `BuildScanCollector.captureBuildScanLink` method is present. 468 | // Add `void captureBuildScanLink(String) {}` to the `BuildScanCollector` class to respond to buildScanPublished events 469 | static buildScanPublishedAction(def buildScanExtension, BuildScanCollector collector) { 470 | if (buildScanExtension.metaClass.respondsTo(buildScanExtension, 'buildScanPublished', Action)) { 471 | buildScanExtension.buildScanPublished { scan -> 472 | collector.captureBuildScanLink(scan.buildScanUri.toString()) 473 | } 474 | } 475 | } 476 | 477 | class BuildScanCollector {} 478 | -------------------------------------------------------------------------------- /test_maven.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #set -xv 3 | buildDir="$(dirname $0)/build" 4 | DEVELOCITY_EXT_PATH="/path/to/develocity-maven-extension.jar" 5 | CCUD_EXT_PATH="/path/to/common-custom-user-data-maven-extension.jar" 6 | 7 | function test_isAtLeast_Larger() { 8 | local actual=$(isAtLeast 1.1 1.0) 9 | local expected="true" 10 | echo "test_isAtLeast_Larger: actual = ${actual}; expected = ${expected}" 11 | assert "$actual" "$expected" 12 | } 13 | 14 | function test_isAtLeast_Equal() { 15 | local actual=$(isAtLeast 1.1 1.1) 16 | local expected="true" 17 | echo "test_isAtLeast_Equal: actual = ${actual}; expected = ${expected}" 18 | assert "$actual" "$expected" 19 | } 20 | 21 | function test_isAtLeast_Smaller() { 22 | local actual=$(isAtLeast 1.1 1.2) 23 | local expected="false" 24 | echo "test_isAtLeast_Smaller: actual = ${actual}; expected = ${expected}" 25 | assert "$actual" "$expected" 26 | } 27 | 28 | function test_isAtLeast_Minor_And_Patch_Larger() { 29 | local actual=$(isAtLeast 1.2 1.1.1) 30 | local expected="true" 31 | echo "test_isAtLeast_Minor_And_Patch_Larger: actual = ${actual}; expected = ${expected}" 32 | assert "$actual" "$expected" 33 | } 34 | 35 | function test_isAtLeast_Minor_And_Patch_Smaller() { 36 | local actual=$(isAtLeast 1.2 1.2.1) 37 | local expected="false" 38 | echo "test_isAtLeast_Minor_And_Patch_Larger: actual = ${actual}; expected = ${expected}" 39 | assert "$actual" "$expected" 40 | } 41 | 42 | function test_isAtLeast_Ignore_Qualifier() { 43 | local actual=$(isAtLeast 1.2-rc-4 1.2) 44 | local expected="true" 45 | echo "test_isAtLeast_Ignore_Qualifier: actual = ${actual}; expected = ${expected}" 46 | assert "$actual" "$expected" 47 | } 48 | 49 | function test_downloadMavenExtension_GradleEnterprise() { 50 | local mavenRepo="https://repo.grdev.net/artifactory/public" 51 | local mavenExtensionVersion="1.17" 52 | local TMP_DV=$buildDir 53 | downloadDvMavenExt 54 | echo "test_downloadMavenExtension_GradleEnterprise: $DEVELOCITY_EXT_PATH" 55 | assert "$DEVELOCITY_EXT_PATH" "$buildDir/gradle-enterprise-maven-extension.jar" 56 | DEVELOCITY_EXT_PATH="/path/to/develocity-maven-extension.jar" 57 | } 58 | 59 | function test_downloadMavenExtension_Develocity() { 60 | local mavenRepo="https://repo.grdev.net/artifactory/public" 61 | local mavenExtensionVersion="1.21-rc-4" 62 | local TMP_DV=$buildDir 63 | downloadDvMavenExt 64 | echo "test_downloadMavenExtension_Develocity: $DEVELOCITY_EXT_PATH" 65 | assert "$DEVELOCITY_EXT_PATH" "$buildDir/develocity-maven-extension.jar" 66 | DEVELOCITY_EXT_PATH="/path/to/develocity-maven-extension.jar" 67 | } 68 | 69 | function test_detectExtension_detected() { 70 | local projDir=$(setupProject) 71 | cat << EOF >"${projDir}/.mvn/extensions.xml" 72 | 73 | 74 | 75 | com.gradle 76 | develocity-maven-extension 77 | 1.20.1 78 | 79 | 80 | EOF 81 | setupVars 82 | 83 | local result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension") 84 | 85 | echo "test_detectExtension_detected: ${result}" 86 | assert "${result}" "true" 87 | 88 | result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension:1.20.1") 89 | 90 | echo "test_detectExtension_detected with version: ${result}" 91 | assert "${result}" "true" 92 | 93 | result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension:1.18.1") 94 | 95 | echo "test_detectExtension_detected with wrong version: ${result}" 96 | assert "${result}" "false" 97 | } 98 | 99 | function test_detectExtension_multiple_detected() { 100 | local projDir=$(setupProject) 101 | cat << EOF >"${projDir}/.mvn/extensions.xml" 102 | 103 | 104 | 105 | foo 106 | bar 107 | 1.20.1 108 | 109 | 110 | foo 111 | bar2 112 | 1.20.1 113 | 114 | 115 | com.gradle 116 | develocity-maven-extension 117 | 1.20.1 118 | 119 | 120 | EOF 121 | setupVars 122 | 123 | local result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension") 124 | 125 | echo "test_detectExtension_multiple_detected: ${result}" 126 | assert "${result}" "true" 127 | } 128 | 129 | function test_detectExtension_notDetected() { 130 | local projDir=$(setupProject) 131 | cat << EOF >"${projDir}/.mvn/extensions.xml" 132 | 133 | 134 | 135 | org.apache.maven.extensions 136 | maven-enforcer-extension 137 | 3.4.1 138 | 139 | 140 | EOF 141 | setupVars 142 | 143 | local result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension") 144 | 145 | echo "test_detectExtension_notDetected: ${result}" 146 | assert $result "false" 147 | } 148 | 149 | function test_detectExtension_notDetected_junk() { 150 | local projDir=$(setupProject) 151 | cat << EOF >"${projDir}/.mvn/extensions.xml" 152 | 153 | 154 | 155 | EOF 156 | setupVars 157 | 158 | local result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension") 159 | 160 | echo "test_detectExtension_notDetected_junk: ${result}" 161 | assert $result "false" 162 | } 163 | 164 | function test_detectExtension_unexisting() { 165 | local projDir=$(setupProject) 166 | setupVars 167 | 168 | local result=$(detectExtension "${projDir}" "com.gradle:develocity-maven-extension") 169 | 170 | echo "test_detectExtension_notDetected_unexisting: ${result}" 171 | assert $result "false" 172 | } 173 | 174 | function test_detectDvExtension_non_existing() { 175 | local projDir=$(setupProject) 176 | cat << EOF >"${projDir}/.mvn/extensions.xml" 177 | 178 | 179 | 180 | com.foo 181 | bar 182 | 1.0 183 | 184 | 185 | EOF 186 | setupVars 187 | 188 | local result=$(detectDvExtension "${projDir}") 189 | echo "test_detectDvExtension_non_existing: ${result}" 190 | assert "${result}" "false" 191 | } 192 | 193 | function test_detectDvExtension_custom() { 194 | local projDir=$(setupProject) 195 | cat << EOF >"${projDir}/.mvn/extensions.xml" 196 | 197 | 198 | 199 | com.foo 200 | bar 201 | 1.0 202 | 203 | 204 | com.gradle 205 | develocity-maven-extension 206 | 1.21 207 | 208 | 209 | EOF 210 | setupVars 211 | 212 | customMavenExtensionCoordinates="com.foo:bar:1.0" 213 | local result=$(detectDvExtension "${projDir}") 214 | echo "test_detectDvExtension_custom: ${result}" 215 | assert "${result}" "true" 216 | 217 | customMavenExtensionCoordinates="com.foo:bar:2.0" 218 | result=$(detectDvExtension "${projDir}") 219 | echo "test_detectDvExtension_custom with wrong version: ${result}" 220 | assert "${result}" "false" 221 | } 222 | 223 | function test_detectDvExtension_GradleEnterprise() { 224 | local projDir=$(setupProject) 225 | cat << EOF >"${projDir}/.mvn/extensions.xml" 226 | 227 | 228 | 229 | com.gradle 230 | gradle-enterprise-maven-extension 231 | 1.20.1 232 | 233 | 234 | EOF 235 | setupVars 236 | 237 | local result=$(detectDvExtension "${projDir}") 238 | echo "test_detectDvExtension_GradleEnterprise: ${result}" 239 | assert "${result}" "true" 240 | } 241 | 242 | function test_detectDvExtension_Develocity() { 243 | local projDir=$(setupProject) 244 | cat << EOF >"${projDir}/.mvn/extensions.xml" 245 | 246 | 247 | 248 | com.gradle 249 | develocity-maven-extension 250 | 1.21 251 | 252 | 253 | EOF 254 | setupVars 255 | 256 | local result=$(detectDvExtension "${projDir}") 257 | echo "test_detectDvExtension_Develocity: ${result}" 258 | assert "${result}" "true" 259 | } 260 | 261 | function test_inject_develocity_for_maven() { 262 | local projDir=$(setupProject) 263 | setupVars 264 | 265 | injectDevelocityForMaven "${projDir}" 266 | 267 | echo "test_inject_develocity_for_maven: ${MAVEN_OPTS}" 268 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar:/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 269 | } 270 | 271 | function test_inject_develocity_for_maven_existing_maven_opts() { 272 | local projDir=$(setupProject) 273 | setupVars 274 | MAVEN_OPTS="-Dfoo=bar" 275 | 276 | injectDevelocityForMaven "${projDir}" 277 | 278 | echo "test_inject_develocity_for_maven_existing_maven_opts: ${MAVEN_OPTS}" 279 | assert "${MAVEN_OPTS}" "-Dfoo=bar -Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar:/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 280 | } 281 | 282 | function test_inject_develocity_for_maven_existing_extension() { 283 | local projDir=$(setupProject) 284 | cat << EOF >"${projDir}/.mvn/extensions.xml" 285 | 286 | 287 | 288 | org.apache.maven.extensions 289 | maven-enforcer-extension 290 | 3.4.1 291 | 292 | 293 | EOF 294 | setupVars 295 | customMavenExtensionCoordinates="org.apache.maven.extensions:maven-enforcer-extension" 296 | enforceUrl=false 297 | 298 | injectDevelocityForMaven "${projDir}" 299 | 300 | echo "test_inject_develocity_for_maven_existing_extension: ${MAVEN_OPTS}" 301 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false" 302 | } 303 | 304 | function test_inject_develocity_for_maven_existing_extension_enforceUrl() { 305 | local projDir=$(setupProject) 306 | cat << EOF >"${projDir}/.mvn/extensions.xml" 307 | 308 | 309 | 310 | org.apache.maven.extensions 311 | maven-enforcer-extension 312 | 3.4.1 313 | 314 | 315 | EOF 316 | setupVars 317 | customMavenExtensionCoordinates="org.apache.maven.extensions:maven-enforcer-extension" 318 | enforceUrl=true 319 | 320 | injectDevelocityForMaven "${projDir}" 321 | 322 | echo "test_inject_develocity_for_maven_existing_extension_enforceUrl: ${MAVEN_OPTS}" 323 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost" 324 | } 325 | 326 | function test_inject_develocity_for_maven_existing_ccud_extension() { 327 | local projDir=$(setupProject) 328 | cat << EOF >"${projDir}/.mvn/extensions.xml" 329 | 330 | 331 | 332 | org.apache.maven.extensions 333 | maven-enforcer-extension 334 | 3.4.1 335 | 336 | 337 | EOF 338 | setupVars 339 | customCcudCoordinates="org.apache.maven.extensions:maven-enforcer-extension" 340 | enforceUrl=false 341 | 342 | injectDevelocityForMaven "${projDir}" 343 | 344 | echo "test_inject_develocity_for_maven_existing_ccud_extension: ${MAVEN_OPTS}" 345 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 346 | } 347 | 348 | function test_inject_develocity_for_maven_existing_default_ccud_extension() { 349 | local projDir=$(setupProject) 350 | cat << EOF >"${projDir}/.mvn/extensions.xml" 351 | 352 | 353 | 354 | com.gradle 355 | common-custom-user-data-maven-extension 356 | 1.13 357 | 358 | 359 | EOF 360 | setupVars 361 | enforceUrl=false 362 | 363 | injectDevelocityForMaven "${projDir}" 364 | 365 | echo "test_inject_develocity_for_maven_existing_default_ccud_extension: ${MAVEN_OPTS}" 366 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 367 | } 368 | 369 | function test_inject_develocity_for_maven_existing_ccud_extension_enforceUrl() { 370 | local projDir=$(setupProject) 371 | cat << EOF >"${projDir}/.mvn/extensions.xml" 372 | 373 | 374 | 375 | org.apache.maven.extensions 376 | maven-enforcer-extension 377 | 3.4.1 378 | 379 | 380 | EOF 381 | setupVars 382 | customCcudCoordinates="org.apache.maven.extensions:maven-enforcer-extension" 383 | enforceUrl=true 384 | 385 | injectDevelocityForMaven "${projDir}" 386 | 387 | echo "test_inject_develocity_for_maven_existing_ccud_extension_enforceUrl: ${MAVEN_OPTS}" 388 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 389 | } 390 | 391 | function test_inject_develocity_for_maven_existing_dv_and_ccud_extension_enforceUrl() { 392 | local projDir=$(setupProject) 393 | cat << EOF >"${projDir}/.mvn/extensions.xml" 394 | 395 | 396 | 397 | org.apache.maven.extensions 398 | maven-enforcer-extension 399 | 3.4.1 400 | 401 | 402 | org.foo 403 | bar-extension 404 | 1.0 405 | 406 | 407 | EOF 408 | setupVars 409 | customMavenExtensionCoordinates="org.foo:bar-extension" 410 | customCcudCoordinates="org.apache.maven.extensions:maven-enforcer-extension" 411 | enforceUrl=true 412 | 413 | injectDevelocityForMaven "${projDir}" 414 | 415 | echo "test_inject_develocity_for_maven_existing_dv_and_ccud_extension_enforceUrl: ${MAVEN_OPTS}" 416 | assert "${MAVEN_OPTS}" "-Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost" 417 | } 418 | 419 | function test_inject_capture_goal_input_files_true_old() { 420 | local projDir=$(setupProject) 421 | setupVars 422 | captureFileFingerprints=true 423 | mavenExtensionVersion="1.20.1" 424 | 425 | injectDevelocityForMaven "${projDir}" 426 | 427 | echo "test_inject_capture_goal_input_files_true_old: ${MAVEN_OPTS}" 428 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar:/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 429 | } 430 | 431 | function test_inject_capture_goal_input_files_true() { 432 | local projDir=$(setupProject) 433 | setupVars 434 | captureFileFingerprints=true 435 | 436 | injectDevelocityForMaven "${projDir}" 437 | 438 | echo "test_inject_capture_goal_input_files_true: ${MAVEN_OPTS}" 439 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar:/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost -Ddevelocity.scan.captureFileFingerprints=true -Dgradle.scan.captureGoalInputFiles=true" 440 | } 441 | 442 | function test_inject_capture_goal_input_files_false() { 443 | local projDir=$(setupProject) 444 | setupVars 445 | captureFileFingerprints=false 446 | 447 | injectDevelocityForMaven "${projDir}" 448 | 449 | echo "test_inject_capture_goal_input_files_false: ${MAVEN_OPTS}" 450 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/develocity-maven-extension.jar:/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false -Dgradle.enterprise.url=https://localhost -Ddevelocity.url=https://localhost -Ddevelocity.scan.captureFileFingerprints=false -Dgradle.scan.captureGoalInputFiles=false" 451 | } 452 | 453 | function test_inject_capture_goal_input_files_existing_ext() { 454 | local projDir=$(setupProject) 455 | cat << EOF >"${projDir}/.mvn/extensions.xml" 456 | 457 | 458 | 459 | com.gradle 460 | develocity-maven-extension 461 | 1.21 462 | 463 | 464 | EOF 465 | setupVars 466 | enforceUrl=false 467 | captureFileFingerprints=false 468 | 469 | injectDevelocityForMaven "${projDir}" 470 | 471 | echo "test_inject_capture_goal_input_files_existing_ext: ${MAVEN_OPTS}" 472 | assert "${MAVEN_OPTS}" "-Dmaven.ext.class.path=/path/to/common-custom-user-data-maven-extension.jar -Dgradle.scan.uploadInBackground=false -Ddevelocity.uploadInBackground=false -Dgradle.enterprise.allowUntrustedServer=false -Ddevelocity.allowUntrustedServer=false" 473 | } 474 | 475 | function test_extract_hostname() { 476 | echo "test_extract_hostname" 477 | local hostname=$(extractHostname "http://some-dv-server.gradle.com") 478 | assert "${hostname}" "some-dv-server.gradle.com" 479 | 480 | hostname=$(extractHostname "http://some-dv-server.gradle.com/somepath") 481 | assert "${hostname}" "some-dv-server.gradle.com" 482 | 483 | hostname=$(extractHostname "http://192.168.1.10") 484 | assert "${hostname}" "192.168.1.10" 485 | 486 | hostname=$(extractHostname "http://192.168.1.10:5086") 487 | assert "${hostname}" "192.168.1.10" 488 | 489 | # we do not handle this case for now 490 | hostname=$(extractHostname "not_a_url") 491 | assert "${hostname}" "not_a_url" 492 | } 493 | 494 | function test_extract_access_key() { 495 | echo "test_extract_access_key" 496 | local key=$(extractAccessKey "host1=key1;host2=key2;host3=key3" "host2") 497 | assert "${key}" "key2" 498 | 499 | key=$(extractAccessKey "host1=key1;host2=key2;host3=key3" "unknown") 500 | assert "${key}" "" 501 | } 502 | 503 | function test_single_key() { 504 | echo "test_single_key" 505 | local key=$(singleKey "host1=key1") 506 | assert "${key}" "true" 507 | 508 | key=$(singleKey "host1=key1;host2=key2;host3=key3") 509 | assert "${key}" "false" 510 | } 511 | 512 | function setupProject() { 513 | local projDir="$(mktemp -p ${buildDir} -d ge.XXXXXX)" 514 | local extDir="${projDir}/.mvn" 515 | mkdir -p "${extDir}" 516 | 517 | echo "${projDir}" 518 | } 519 | 520 | function setupVars() { 521 | # Set the vars to default 522 | MAVEN_OPTS="" 523 | ccudMavenExtensionVersion="" 524 | mavenRepo="" 525 | mavenExtensionVersion="1.21" 526 | allowUntrustedServer="false" 527 | enforceUrl="true" 528 | captureFileFingerprints="true" 529 | customMavenExtensionCoordinates="" 530 | customCcudCoordinates="" 531 | url="https://localhost" 532 | } 533 | 534 | 535 | function assert() { 536 | local val=$1 537 | local expected=$2 538 | if [ ! "${val}" = "${expected}" ] 539 | then 540 | echo "Not equal to expected" 541 | diff <(echo "${val}" ) <(echo "${expected}") 542 | exit 1 543 | fi 544 | } 545 | 546 | function extractCodeUnderTest() { 547 | local start_pattern="#functions-start" 548 | local end_pattern="#functions-end" 549 | local file="$(dirname $0)/develocity-maven.yml" 550 | 551 | sed -n "/$start_pattern/,/$end_pattern/p" "$file" | sed '/^$/d' > "${buildDir}/under-test.sh" 552 | source "${buildDir}/under-test.sh" 553 | } 554 | 555 | function clean() { 556 | echo "removing ${buildDir}" 557 | rm -Rf "${buildDir}" 558 | mkdir "${buildDir}" 559 | } 560 | 561 | clean 562 | extractCodeUnderTest 563 | test_isAtLeast_Larger 564 | test_isAtLeast_Equal 565 | test_isAtLeast_Smaller 566 | test_isAtLeast_Minor_And_Patch_Larger 567 | test_isAtLeast_Minor_And_Patch_Smaller 568 | test_isAtLeast_Ignore_Qualifier 569 | test_downloadMavenExtension_GradleEnterprise 570 | test_downloadMavenExtension_Develocity 571 | test_detectExtension_detected 572 | test_detectExtension_notDetected 573 | test_detectExtension_notDetected_junk 574 | test_detectExtension_unexisting 575 | test_detectExtension_multiple_detected 576 | test_detectDvExtension_non_existing 577 | test_detectDvExtension_custom 578 | test_detectDvExtension_GradleEnterprise 579 | test_detectDvExtension_Develocity 580 | test_inject_develocity_for_maven 581 | test_inject_develocity_for_maven_existing_maven_opts 582 | test_inject_develocity_for_maven_existing_extension 583 | test_inject_develocity_for_maven_existing_extension_enforceUrl 584 | test_inject_develocity_for_maven_existing_ccud_extension 585 | test_inject_develocity_for_maven_existing_default_ccud_extension 586 | test_inject_develocity_for_maven_existing_ccud_extension_enforceUrl 587 | test_inject_develocity_for_maven_existing_dv_and_ccud_extension_enforceUrl 588 | test_inject_capture_goal_input_files_true_old 589 | test_inject_capture_goal_input_files_true 590 | test_inject_capture_goal_input_files_false 591 | test_inject_capture_goal_input_files_existing_ext 592 | test_extract_hostname 593 | test_extract_access_key 594 | test_single_key 595 | --------------------------------------------------------------------------------