Skip to content

Commit 95c19bb

Browse files
Develocity and Gradle Enterprise remote build cache connectors are supported (#807)
* Support Develocity and Gradle Enterprise remote build cache connectors Adds a new command line argument for Gradle experiment 5, `-y` or `--remote-build-cache-type`, allowing the user to specify a remote build cache type to use. When specified, valid values are: `develocity`, `gradle-enterprise`, or `http`. When the argument is specified, it will always take precedence over what is configured in the build. If the argument is not specified, the remote build cache type of the first build will be fetched from the Develocity API and used for the second build. If the argument is not specified and the user does not have access to the Develocity API, then the existing remote build cache configuration will be used. * Update user-facing text * Update release notes * Extract init script logic to helper functions * Add 'connector' to help, interactive, and error text * Bump com.gradle:build-scan-summary from 1.0.3-2024.1 to 1.0.4-2024.1 Bumps com.gradle:build-scan-summary from 1.0.3-2024.1 to 1.0.4-2024.1. --- updated-dependencies: - dependency-name: com.gradle:build-scan-summary dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent ff8ed43 commit 95c19bb

File tree

8 files changed

+201
-27
lines changed

8 files changed

+201
-27
lines changed

build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ val isDevelopmentRelease = !hasProperty("finalRelease")
6161
val releaseVersion = releaseVersion()
6262
val releaseNotes = releaseNotes()
6363
val distributionVersion = distributionVersion()
64-
val buildScanSummaryVersion = "1.0.3-2024.1"
64+
val buildScanSummaryVersion = "1.0.4-2024.1"
6565

6666
allprojects {
6767
version = releaseVersion.get()

components/scripts/gradle/05-validate-remote-build-caching-ci-local.sh

+44-6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ develocity_server=''
4040
interactive_mode=''
4141

4242
ci_build_scan_url=''
43+
remote_build_cache_type=''
4344
remote_build_cache_url=''
4445
mapping_file=''
4546

@@ -110,9 +111,9 @@ wizard_execute() {
110111
collect_gradle_details
111112

112113
print_bl
113-
explain_remote_build_cache_url
114+
explain_collect_remote_build_cache
114115
print_bl
115-
collect_remote_build_cache_url
116+
collect_remote_build_cache
116117
explain_command_to_repeat_experiment_after_collecting_parameters
117118

118119
print_bl
@@ -136,6 +137,7 @@ wizard_execute() {
136137

137138
map_additional_script_args() {
138139
ci_build_scan_url="${_arg_first_build_ci}"
140+
remote_build_cache_type="${_arg_remote_build_cache_type}"
139141
remote_build_cache_url="${_arg_remote_build_cache_url}"
140142
mapping_file="${_arg_mapping_file}"
141143
}
@@ -151,6 +153,10 @@ validate_required_args() {
151153
if [[ "${enable_develocity}" == "on" && -z "${develocity_server}" ]]; then
152154
_PRINT_HELP=yes die "ERROR: Missing required argument when enabling Develocity on a project not already connected: --develocity-server" "${INVALID_INPUT}"
153155
fi
156+
157+
if [[ -n "${remote_build_cache_type}" && "${remote_build_cache_type}" != 'http' && "${remote_build_cache_type}" != 'gradle-enterprise' && "${remote_build_cache_type}" != 'develocity' ]]; then
158+
_PRINT_HELP=yes die "ERROR: Invalid value for argument --remote-build-cache-type. Values are 'develocity', 'gradle-enterprise', or 'http'." "${INVALID_INPUT}"
159+
fi
154160
}
155161

156162
fetch_build_params_from_build_scan() {
@@ -160,6 +166,9 @@ fetch_build_params_from_build_scan() {
160166
}
161167

162168
read_build_params_from_build_scan_data() {
169+
if [[ "${remote_build_cache_types[0]}" == "disabled" ]]; then
170+
die "ERROR: Remote build cache was disabled for the first build. Enable the remote build cache in the build and restart the experiment."
171+
fi
163172
if [ -z "${git_repo}" ]; then
164173
git_repo="${git_repos[0]}"
165174
project_name="$(basename -s .git "${git_repo}")"
@@ -170,6 +179,9 @@ read_build_params_from_build_scan_data() {
170179
if [ -z "${git_commit_id}" ]; then
171180
git_commit_id="${git_commit_ids[0]}"
172181
fi
182+
if [[ -z "${remote_build_cache_type}" && "${remote_build_cache_types[0]}" != "unknown" ]]; then
183+
remote_build_cache_type="${remote_build_cache_types[0]}"
184+
fi
173185
if [ -z "${remote_build_cache_url}" ]; then
174186
remote_build_cache_url="${remote_build_cache_urls[0]}"
175187
fi
@@ -196,6 +208,9 @@ validate_build_config() {
196208
execute_build() {
197209
local args
198210
args=(--build-cache --init-script "${INIT_SCRIPTS_DIR}/configure-remote-build-caching.gradle")
211+
if [ -n "${remote_build_cache_type}" ]; then
212+
args+=("-Ddevelocity.build-validation.remoteBuildCacheType=${remote_build_cache_type}")
213+
fi
199214
if [ -n "${remote_build_cache_url}" ]; then
200215
args+=("-Ddevelocity.build-validation.remoteBuildCacheUrl=${remote_build_cache_url}")
201216
fi
@@ -360,7 +375,7 @@ EOF
360375
print_interactive_text "${text}"
361376
}
362377

363-
explain_remote_build_cache_url() {
378+
explain_collect_remote_build_cache() {
364379
local text
365380
IFS='' read -r -d '' text <<EOF
366381
The local build will connect to the given remote build cache. The remote build
@@ -369,11 +384,30 @@ EOF
369384
print_interactive_text "${text}"
370385
}
371386

387+
collect_remote_build_cache() {
388+
collect_remote_build_cache_type
389+
collect_remote_build_cache_url
390+
}
391+
392+
collect_remote_build_cache_type() {
393+
local default_remote_cache_type="<project default>"
394+
prompt_for_setting "What is the remote build cache connector type to use? [develocity, gradle-enterprise, http, or <BLANK>]" "${remote_build_cache_type}" "${default_remote_cache_type}" remote_build_cache_type
395+
396+
if [[ -n "${remote_build_cache_type}" && "${remote_build_cache_type}" != 'http' && "${remote_build_cache_type}" != 'gradle-enterprise' && "${remote_build_cache_type}" != 'develocity' ]]; then
397+
print_bl
398+
die "ERROR: Invalid value for remote build cache connector type. Values are 'develocity', 'gradle-enterprise', 'http', or <BLANK> for project default." "${INVALID_INPUT}"
399+
fi
400+
401+
if [[ "${remote_build_cache_type}" == "${default_remote_cache_type}" ]]; then
402+
remote_build_cache_type=''
403+
fi
404+
}
405+
372406
collect_remote_build_cache_url() {
373-
local default_remote_cache="<project default>"
374-
prompt_for_setting "What is the remote build cache url to use?" "${remote_build_cache_url}" "${default_remote_cache}" remote_build_cache_url
407+
local default_remote_cache_url="<project default>"
408+
prompt_for_setting "What is the remote build cache url to use?" "${remote_build_cache_url}" "${default_remote_cache_url}" remote_build_cache_url
375409

376-
if [[ "${remote_build_cache_url}" == "${default_remote_cache}" ]]; then
410+
if [[ "${remote_build_cache_url}" == "${default_remote_cache_url}" ]]; then
377411
remote_build_cache_url=''
378412
fi
379413
}
@@ -465,6 +499,10 @@ generate_command_to_repeat_experiment() {
465499
cmd+=("-m" "${mapping_file}")
466500
fi
467501

502+
if [ -n "${remote_build_cache_type}" ]; then
503+
cmd+=("-y" "${remote_build_cache_type}")
504+
fi
505+
468506
if [ -n "${remote_build_cache_url}" ]; then
469507
cmd+=("-u" "${remote_build_cache_url}")
470508
fi

components/scripts/gradle/gradle-init-scripts/configure-remote-build-caching.gradle

+118-7
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,134 @@ static getInputParam(Gradle gradle, String name) {
44
return gradle.startParameter.systemPropertiesArgs[name] ?: System.getProperty(name) ?: System.getenv(envVarName)
55
}
66

7+
def isTopLevelBuild = !gradle.parent
8+
9+
def expDir = getInputParam(gradle, 'develocity.build-validation.expDir')
10+
def remoteBuildCacheType = getInputParam(gradle, 'develocity.build-validation.remoteBuildCacheType')
711
def remoteBuildCacheUrl = getInputParam(gradle, 'develocity.build-validation.remoteBuildCacheUrl')
812

9-
settingsEvaluated { settings ->
13+
def docsRoot = 'https://docs.gradle.com/develocity/gradle-plugin'
14+
15+
settingsEvaluated { Settings settings ->
1016
settings.buildCache {
1117
local {
1218
enabled = false
1319
}
14-
remote(HttpBuildCache) {
15-
enabled = true
16-
push = false
17-
if (remoteBuildCacheUrl) {
18-
url = withPathTrailingSlash(new URI(remoteBuildCacheUrl))
20+
21+
if (remoteBuildCacheType) {
22+
if (isInvalidRemoteBuildCacheType(remoteBuildCacheType)) {
23+
failInvalidRemoteBuildCacheType(remoteBuildCacheType, expDir)
24+
}
25+
26+
if (isTopLevelBuild && missingRequiredPlugin(settings, remoteBuildCacheType)) {
27+
failMissingRequiredPlugin(remoteBuildCacheType, expDir, docsRoot)
28+
}
29+
30+
def remoteBuildCacheImplementation = getRemoteBuildCacheImplementation(settings, remoteBuildCacheType)
31+
if (remoteBuildCacheImplementation) {
32+
logger.debug("Configuring remote build cache implementation for '${settings.rootProject.name}' as: ${remoteBuildCacheImplementation}")
33+
remote(remoteBuildCacheImplementation)
34+
}
35+
}
36+
37+
logger.debug("Remote build cache implementation for '${settings.rootProject.name}' is: ${remote?.class?.name}")
38+
if (remote) {
39+
remote {
40+
enabled = true
41+
}
42+
43+
def remoteBuildCacheUri = remoteBuildCacheUrl ? withPathTrailingSlash(new URI(remoteBuildCacheUrl)) : null
44+
if (remote instanceof HttpBuildCache) {
45+
if (remoteBuildCacheUrl) {
46+
remote.url = remoteBuildCacheUri
47+
} else if (!remote.url) {
48+
failMissingUrlForHttpBuildCache(expDir, docsRoot)
49+
}
50+
} else if (isBuildCacheImplementationFor(remote, 'com.gradle.develocity') || isBuildCacheImplementationFor(remote, 'com.gradle.enterprise')) {
51+
if (remoteBuildCacheUri) {
52+
remote.server = toServerPart(remoteBuildCacheUri)
53+
remote.path = remoteBuildCacheUri.path
54+
}
1955
}
56+
} else if (isTopLevelBuild) {
57+
failMissingRemoteBuildCacheConfiguration(expDir, docsRoot)
2058
}
2159
}
2260
}
2361

62+
static boolean isInvalidRemoteBuildCacheType(String remoteBuildCacheType) {
63+
return !['develocity', 'gradle-enterprise', 'http'].contains(remoteBuildCacheType)
64+
}
65+
66+
static boolean missingRequiredPlugin(Settings settings, String type) {
67+
return type == "develocity" && !settings.pluginManager.hasPlugin('com.gradle.develocity')
68+
|| type == "gradle-enterprise" && !settings.pluginManager.hasPlugin('com.gradle.enterprise')
69+
}
70+
71+
static Class<? extends BuildCache> getRemoteBuildCacheImplementation(Settings settings, String type) {
72+
if (type == "develocity") {
73+
return settings.develocity.buildCache
74+
} else if (type == "gradle-enterprise") {
75+
return settings.gradleEnterprise.buildCache
76+
} else if (type == "http") {
77+
return HttpBuildCache
78+
}
79+
return null
80+
}
81+
82+
static boolean isBuildCacheImplementationFor(BuildCache buildCache, String implementation) {
83+
return buildCache.class.name.startsWith(implementation)
84+
}
85+
2486
static URI withPathTrailingSlash(URI uri) {
25-
uri.path.endsWith("/") ? uri : new URI(uri.scheme, uri.userInfo, uri.host, uri.port, uri.path + "/", uri.query, uri.fragment)
87+
return uri.path.endsWith("/") ? uri : new URI(uri.scheme, uri.userInfo, uri.host, uri.port, uri.path + "/", uri.query, uri.fragment)
88+
}
89+
90+
static String toServerPart(URI uri) {
91+
return new URI(uri.scheme, uri.userInfo, uri.host, uri.port, null, uri.query, uri.fragment)
92+
}
93+
94+
// The scripts already fail if the value of --remote-build-cache-type isn't valid.
95+
// This is for the sake of completeness since this init script assumes it's valid.
96+
static void failInvalidRemoteBuildCacheType(String remoteBuildCacheType, String expDir) {
97+
def errorFile = new File(expDir, 'errors.txt')
98+
def message = "Invalid value '${remoteBuildCacheType}' for remote build cache connector type. Values are 'develocity', 'gradle-enterprise', or 'http'."
99+
errorFile.text = message
100+
throw new IllegalStateException(message)
101+
}
102+
103+
// Included builds may not have the necessary plugin applied.
104+
// Only fail if the top-level build is missing the required extension.
105+
static void failMissingRequiredPlugin(String remoteBuildCacheType, String expDir, String docsRoot) {
106+
def errorFile = new File(expDir, 'errors.txt')
107+
errorFile.text = "Remote build cache connector type '${remoteBuildCacheType}' requested, but the required plugin is not applied."
108+
if (remoteBuildCacheType == 'develocity') {
109+
throw new IllegalStateException("Remote build cache connector type 'develocity' requested,\n" +
110+
"but the Develocity Gradle plugin is not applied.\n" +
111+
"Either apply it directly (see $docsRoot/current/#applying_the_plugin),\n" +
112+
"use --enable-develocity to enable the plugin,\n" +
113+
"or use --remote-build-cache-type to choose a different remote build cache connector type\n" +
114+
"when running the build validation script.")
115+
} else {
116+
throw new IllegalStateException("Remote build cache connector type 'gradle-enterprise' requested,\n" +
117+
"but the Gradle Enterprise Gradle plugin is not applied (see $docsRoot/legacy/#applying_the_plugin).")
118+
}
119+
}
120+
121+
// Gradle already fails in this case, but handling it here means we can fail the experiment more
122+
// gracefully and provide guidance the user.
123+
static void failMissingUrlForHttpBuildCache(String expDir, String docsRoot) {
124+
def errorFile = new File(expDir, 'errors.txt')
125+
errorFile.text = 'A remote build cache URL has not been configured in the project or on the command line.'
126+
throw new IllegalStateException("A remote build cache URL is not configured.\n"
127+
+ "Either configure it directly (see $docsRoot/current/#using_gradles_built_in_http_connector) in the project,\n"
128+
+ "or use --remote-build-cache-url when running the build validation script.")
129+
}
130+
131+
static void failMissingRemoteBuildCacheConfiguration(String expDir, String docsRoot) {
132+
def errorFile = new File(expDir, 'errors.txt')
133+
errorFile.text = "Remote build cache is not configured for the project."
134+
throw new IllegalStateException("Remote build cache is not configured for the project.\n" +
135+
"Either configure it directly (see $docsRoot/current/#using_the_develocity_connector),\n" +
136+
"or use --remote-build-cache-type when running the build validation script.")
26137
}

components/scripts/lib/build-scan-parse.sh

+16-12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ git_branches=()
1010
git_commit_ids=()
1111
requested_tasks=()
1212
build_outcomes=()
13+
remote_build_cache_types=()
14+
remote_build_cache_class_names=()
1315
remote_build_cache_urls=()
1416
remote_build_cache_shards=()
1517

@@ -77,7 +79,7 @@ parse_build_scan_row() {
7779

7880
local run_num
7981

80-
while IFS=, read -r run_num field_1 field_2 field_3 field_4 field_5 field_6 field_7 field_8 field_9 field_10 field_11 field_12 field_13 field_14 field_15 field_16 field_17 field_18 field_19 field_20; do
82+
while IFS=, read -r run_num field_1 field_2 field_3 field_4 field_5 field_6 field_7 field_8 field_9 field_10 field_11 field_12 field_13 field_14 field_15 field_16 field_17 field_18 field_19 field_20 field_21 field_22; do
8183
debug "Build Scan $field_4 is for build $run_num"
8284

8385
# The project_name should be overridden by Build Scan data if it is
@@ -127,21 +129,23 @@ parse_build_scan_row() {
127129
# The below fields are always set by Build Scan data regardless of their
128130
# previous value and are always safe to override.
129131

130-
remote_build_cache_urls[run_num]="${field_10}"
131-
remote_build_cache_shards[run_num]="${field_11}"
132+
remote_build_cache_types[run_num]="${field_10}"
133+
remote_build_cache_class_names[run_num]="${field_11}"
134+
remote_build_cache_urls[run_num]="${field_12}"
135+
remote_build_cache_shards[run_num]="${field_13}"
132136

133137
# Build caching performance metrics
134-
avoided_up_to_date_num_tasks[run_num]="${field_12}"
135-
avoided_up_to_date_avoidance_savings[run_num]="${field_13}"
136-
avoided_from_cache_num_tasks[run_num]="${field_14}"
137-
avoided_from_cache_avoidance_savings[run_num]="${field_15}"
138-
executed_cacheable_num_tasks[run_num]="${field_16}"
139-
executed_cacheable_duration[run_num]="${field_17}"
140-
executed_not_cacheable_num_tasks[run_num]="${field_18}"
141-
executed_not_cacheable_duration[run_num]="${field_19}"
138+
avoided_up_to_date_num_tasks[run_num]="${field_14}"
139+
avoided_up_to_date_avoidance_savings[run_num]="${field_15}"
140+
avoided_from_cache_num_tasks[run_num]="${field_16}"
141+
avoided_from_cache_avoidance_savings[run_num]="${field_17}"
142+
executed_cacheable_num_tasks[run_num]="${field_18}"
143+
executed_cacheable_duration[run_num]="${field_19}"
144+
executed_not_cacheable_num_tasks[run_num]="${field_20}"
145+
executed_not_cacheable_duration[run_num]="${field_21}"
142146

143147
# Other build metrics
144-
serialization_factors[run_num]="${field_20}"
148+
serialization_factors[run_num]="${field_22}"
145149
done <<< "${build_scan_row}"
146150
}
147151

components/scripts/lib/cli-parsers/gradle/05-cli-parser.m4

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Created by argbash-init v2.10.0
44
# ARG_OPTIONAL_SINGLE([first-build-ci],[1],[])
55
# ARG_OPTIONAL_SINGLE([mapping-file],[m],[])
6+
# ARG_OPTIONAL_SINGLE([remote-build-cache-type],[y],[])
67
# ARG_OPTIONAL_SINGLE([remote-build-cache-url],[u],[])
78
# ARG_OPTIONAL_BOOLEAN([fail-if-not-fully-cacheable],[f],[])
89
# ARG_HELP([This function is overridden later on.])
@@ -30,6 +31,7 @@ function print_help() {
3031
print_option_usage -p
3132
print_option_usage -t
3233
print_option_usage -a
34+
print_option_usage "-y, --remote-build-cache-type" "Specifies the remote build cache connector type to use in the second build run locally. Values are 'develocity', 'gradle-enterprise', or 'http'."
3335
print_option_usage "-u, --remote-build-cache-url" "Specifies the URL for the remote build cache to access in the second build run locally."
3436
print_option_usage -s
3537
print_option_usage -e

components/scripts/lib/summary.sh

+15-1
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,34 @@ detect_warnings_from_build_scans() {
119119
if [ -z "${build_outcomes[i]}" ]; then
120120
warnings+=("Failed to fetch build scan data for the ${ORDINALS[i]} build.")
121121
fi
122+
if [ "${remote_build_cache_types[i]}" == "unknown" ]; then
123+
# "Develocity Build Validation Scripts" is specifically mentioned as to
124+
# not imply Develocity itself does not work with other remote build cache
125+
# implementations.
126+
warnings+=("The ${ORDINALS[i]} build ran using a remote build cache implementation not officially supported by the experiment.")
127+
fi
122128
done
123129

124130
local value_mismatch=false
125131
if [[ "${project_names[0]}" != "${project_names[1]}" ]] ||
126132
[[ "${git_repos[0]}" != "${git_repos[1]}" ]] ||
127133
[[ "${git_branches[0]}" != "${git_branches[1]}" ]] ||
128134
[[ "${git_commit_ids[0]}" != "${git_commit_ids[1]}" ]] ||
129-
[[ "${requested_tasks[0]}" != "${requested_tasks[1]}" ]]; then
135+
[[ "${requested_tasks[0]}" != "${requested_tasks[1]}" ]] ||
136+
[[ "${remote_build_cache_urls[0]}" != "${remote_build_cache_urls[1]}" ]] ||
137+
[[ "${remote_build_cache_class_names[0]}" != "${remote_build_cache_class_names[1]}" ]]; then
130138
value_mismatch=true
131139
fi
132140

133141
if [[ "${value_mismatch}" == "true" ]]; then
134142
warnings+=("Differences were detected between the two builds. This may skew the outcome of the experiment.")
135143
fi
144+
if [ "${remote_build_cache_urls[0]}" != "${remote_build_cache_urls[1]}" ]; then
145+
warnings+=("The two builds ran with different remote build cache URLs configured.")
146+
fi
147+
if [ "${remote_build_cache_class_names[0]}" != "${remote_build_cache_class_names[1]}" ]; then
148+
warnings+=("The two builds ran using different remote build cache implementations.")
149+
fi
136150
if [[ "${unknown_values}" == "true" ]]; then
137151
warnings+=("Some of the build properties could not be determined. This makes it uncertain if the experiment has run correctly.")
138152
fi

components/scripts/maven/04-validate-remote-build-caching-ci-local.sh

+3
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ fetch_build_params_from_build_scan() {
158158
}
159159

160160
read_build_params_from_build_scan_data() {
161+
if [[ "${remote_build_cache_types[0]}" == "disabled" ]]; then
162+
die "ERROR: Remote build cache was disabled for the first build. Enable the remote build cache in the build and restart the experiment."
163+
fi
161164
if [ -z "${git_repo}" ]; then
162165
git_repo="${git_repos[0]}"
163166
project_name="$(basename -s .git "${git_repo}")"

release/changes.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
> [!IMPORTANT]
22
> The distributions of the Develocity Build Validation Scripts prefixed with `gradle-enterprise` are deprecated and will be removed in a future release. Migrate to the distributions prefixed with `develocity` instead.
33
4+
- [NEW] Support Develocity and Gradle Enterprise remote build cache connectors in the Gradle CI/Local experiment
5+
- [NEW] Better handling of remote build cache misconfigurations
46
- [FIX] Scripts do not wait long enough for build scans to become available when `--fail-if-not-fully-cacheable` is used
57
- [FIX] Successful exit code returned when performance characteristics are unknown and `--fail-if-not-fully-cacheable` is used
68
- [FIX] Gradle experiments do not disable background Build Scan publication

0 commit comments

Comments
 (0)