Last active
October 7, 2023 02:29
-
-
Save jrodbx/2ecfe78ec4df859795de1ec8fec410d7 to your computer and use it in GitHub Desktop.
Example Gradle Enterprise config
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.json.JsonSlurper | |
import static java.nio.charset.Charset.defaultCharset | |
import static java.util.concurrent.TimeUnit.SECONDS | |
// Captures build and environment data and stores it in build scans via custom tags, links, | |
// and values, for one-off and trend analyses. | |
gradleEnterprise { | |
buildScan { | |
server = 'YOUR SERVER URL' | |
// Capturing task input files increases the amount of data transmitted to the build scan server | |
// at the end of the build. If the network connection to the build scan server is poor, | |
// it may increase the time required to transmit. | |
captureTaskInputFiles = !isCi() | |
uploadInBackground = !isCi() | |
publishAlwaysIf shouldCreateBuildScans() | |
} | |
} | |
def buildScanApi = project.extensions.findByName('buildScan') | |
if (!buildScanApi) { | |
return | |
} | |
private boolean shouldCreateBuildScans() { | |
if (isCi()) { | |
// Generate build scans only for master + release branch builds. | |
def gitBranch = System.getenv("GIT_BRANCH") | |
return gitBranch == "master" || gitBranch.startsWith("release/") | |
} else { | |
// Local builds default to always generating build scans. | |
return true | |
} | |
} | |
tagBuildEnvironment() | |
tagIde() | |
tagCorePluginVersions() | |
tagCiOrLocal() | |
addOsXMetadata() | |
addKochikuMetadata() | |
addGitMetadata() | |
addGradleBuildMetadata() | |
private void tagBuildEnvironment() { | |
buildScan { | |
tag providers.systemProperty('os.name').forUseAtConfigurationTime().get() | |
value 'OS: Device Total Processors', Runtime.getRuntime().availableProcessors().toString() | |
def (maxMemory, totalMemory, freeMemory) = getMemoryParameters() | |
value 'OS: Device Max Memory [Begin]', maxMemory | |
value 'OS: Device Total Memory [Begin]', totalMemory | |
value 'OS: Device Free Memory [Begin]', freeMemory | |
buildFinished { | |
(maxMemory, totalMemory, freeMemory) = getMemoryParameters() | |
value 'OS: Device Max Memory [End]', maxMemory | |
value 'OS: Device Total Memory [End]', totalMemory | |
value 'OS: Device Free Memory [End]', freeMemory | |
} | |
} | |
} | |
void tagIde() { | |
buildScan { | |
if (project.hasProperty('android.injected.invoked.from.ide')) { | |
tag 'Android Studio' | |
value 'Android Studio Version', project.findProperty('android.injected.studio.version') | |
value '# Modules', rootProject.allprojects.findAll { it.subprojects.isEmpty() }.size().toString() | |
} else if (System.getProperty('idea.version')) { | |
tag 'IntelliJ IDEA' | |
} else if (!isCi()) { | |
tag 'CLI' | |
} | |
if (gradle.ext.isIdeSync) { | |
tag 'IDE sync' | |
} | |
} | |
} | |
void tagCorePluginVersions() { | |
buildScan { | |
value 'Version: AGP', ext.versions.androidGradle | |
value 'Version: JDK', JavaVersion.current().toString() | |
value 'Version: Kotlin', ext.versions.kotlin | |
} | |
} | |
void tagCiOrLocal() { | |
buildScan { | |
tag isCi() ? 'CI' : 'LOCAL' | |
} | |
} | |
private void addOsXMetadata() { | |
buildScan.background { | |
// We'll bail entirely if this isn't a Mac, we don't have equivalent Linux commands yet. | |
if (!execAndGetStdout('uname', '-s').contains('Darwin')) return | |
value 'OS: Device CPU', execAndGetStdout('sysctl', '-n', 'machdep.cpu.brand_string') | |
value 'OS: Build Version', execAndGetStdout('sw_vers', '-buildVersion') | |
value 'OS: Product Version', execAndGetStdout('sw_vers', '-productVersion') | |
value 'OS: Product Name', execAndGetStdout('sw_vers', '-productName') | |
def powerSource = execAndGetStdout('pmset', '-g', 'batt') | |
if (powerSource.contains("Battery")) { | |
value 'OS: Power Source', 'Battery' | |
value 'OS: Battery Percentage', powerSource.split(";")[0].split().last() | |
} else if (powerSource.contains('AC Power')) { | |
value 'OS: Power Source', 'AC Power' | |
} | |
def json = new JsonSlurper() | |
def systemDisplays = json.parseText(execAndGetStdout('system_profiler', 'SPDisplaysDataType', '-json')) | |
def ndrvs = systemDisplays.SPDisplaysDataType[1].spdisplays_ndrvs | |
value 'OS: Displays', ndrvs.collect { it._name }.toString() | |
def localIp = execAndGetStdout('curl', '-s', 'https://ipinfo.io/ip') | |
def localGeo | |
if (localIp) { | |
localGeo = json.parseText(execAndGetStdout('curl', '-s', "https://json.geoiplookup.io/$localIp")) | |
} | |
if (localIp && localGeo) { | |
value 'OS: Location', "${localGeo.city}, ${localGeo.region}, ${localGeo.country_code}" | |
value 'OS: Coordinates', "${localGeo.latitude}, ${localGeo.longitude}" | |
} else { | |
value 'OS: Location', 'Unknown' | |
} | |
} | |
} | |
void addKochikuMetadata() { | |
if (!isKochiku()) return | |
def buildTestRunner = System.getenv('TEST_RUNNER') | |
def buildId = System.getenv('BUILD_ID') | |
def buildPartId = System.getenv('BUILD_PART_ID') | |
def buildAttemptId = System.getenv('BUILD_ATTEMPT_ID') | |
def buildTestRunnerLabel = 'Kochiku: Build Test Runner' | |
def buildIdLabel = 'Kochiku: Build Id' | |
def buildPartIdLabel = 'Kochiku: Build Part Id' | |
def buildAttemptIdLabel = 'Kochiku: Build Attempt Id' | |
buildScan { | |
value buildTestRunnerLabel, buildTestRunner | |
value buildIdLabel, buildId | |
value buildPartIdLabel, buildPartId | |
value buildAttemptIdLabel, buildAttemptId | |
link 'Kochiku Build', "https://kochiku.sqprod.co/squareup/cash-android/builds/$buildId" | |
link 'Kochiku Part', "https://kochiku.sqprod.co/squareup/cash-android/builds/$buildId/parts/$buildPartId" | |
addSearchLink buildScan, "$buildTestRunner scans", [(buildTestRunnerLabel): buildTestRunner] | |
addSearchLink buildScan, "Build #$buildId scans", [(buildIdLabel): buildId] | |
} | |
} | |
void addGitMetadata() { | |
buildScan.background { api -> | |
if (!isGitInstalled()) return | |
if (!isGitRepo()) return | |
def gitCommitSha | |
def gitBranchName | |
def gitStatus | |
if (isCi()) { | |
gitCommitSha = System.getenv('GIT_COMMIT').substring(0, 8) | |
gitBranchName = System.getenv('GIT_BRANCH') | |
gitStatus = null | |
} else { | |
gitCommitSha = execAndGetStdout('git', 'rev-parse', '--short=8', '--verify', 'HEAD') | |
// https://stackoverflow.com/a/45028375 | |
gitBranchName = execAndGetStdout('git', 'name-rev', '--name-only', 'HEAD') | |
gitStatus = execAndGetStdout('git', 'status', '--porcelain') | |
} | |
if (gitCommitSha) { | |
def gitCommitShaLabel = 'Git: SHA' | |
api.value gitCommitShaLabel, gitCommitSha | |
addSearchLink api, "SHA $gitCommitSha scans", [(gitCommitShaLabel): gitCommitSha] | |
api.link 'Github commit', "https://github.com/squareup/cash-android/commit/$gitCommitSha" | |
} | |
if (gitBranchName) { | |
def branchNameLabel = 'Git: Branch' | |
api.value branchNameLabel, gitBranchName | |
addSearchLink api, "$gitBranchName scans", [(branchNameLabel): gitBranchName] | |
} | |
if (gitStatus) { | |
api.tag 'Dirty' | |
api.value 'Git: Status', gitStatus | |
} | |
} | |
} | |
void addGradleBuildMetadata() { | |
def filteredProperties = project.properties | |
.findAll { | |
def key = it.key | |
key.startsWith('android.') || | |
key.startsWith('org.gradle.') || | |
key.startsWith('com.squareup.') || | |
key.startsWith('app.cash.') || | |
key.startsWith('kotlin.') || | |
key.startsWith('kapt.') | |
} | |
filteredProperties.sort()*.key | |
.forEach { key -> | |
println("$key = ${filteredProperties[key]}") | |
buildScan.value("Property: $key", "${filteredProperties[key]}") | |
} | |
} | |
private static boolean isCi() { | |
isKochiku() || isJenkins() | |
} | |
private static boolean isKochiku() { | |
return System.getenv('KOCHIKU_ENV') != null | |
} | |
private static boolean isJenkins() { | |
return System.getenv('JENKINS_URL') != null | |
} | |
private static List<String> getMemoryParameters() { | |
def runtime = Runtime.getRuntime() | |
def bytesPerMb = 1024 * 1024 | |
def maxMemory = runtime.maxMemory() | |
return Arrays.asList( | |
maxMemory == Long.MAX_VALUE ? "No limit" : (maxMemory / bytesPerMb).intValue() + " MiB", | |
(runtime.totalMemory() / bytesPerMb).intValue() + " MiB", | |
(runtime.freeMemory() / bytesPerMb).intValue() + " MiB" | |
) | |
} | |
private static String execAndGetStdout(String... args) { | |
Process process = args.toList().execute() | |
try { | |
def standardText = process.getInputStream().getText(defaultCharset().name()) | |
def ignore = process.getErrorStream().getText(defaultCharset().name()) | |
def finished = process.waitFor(10, SECONDS) | |
finished && process.exitValue() == 0 ? trimAtEnd(standardText) : null | |
} finally { | |
process.destroyForcibly() | |
} | |
} | |
private static int execAndGetReturnValue(String... args) { | |
Process process = args.toList().execute() | |
try { | |
def finished = process.waitFor(10, SECONDS) | |
finished ? process.exitValue() : -1 | |
} finally { | |
process.destroyForcibly() | |
} | |
} | |
private static void addSearchLink(def buildScan, String title, Map<String, String> search) { | |
if (buildScan.server) { | |
buildScan.link title, customValueSearchUrl(buildScan, search) | |
} | |
} | |
private static String customValueSearchUrl(def buildScan, Map<String, String> search) { | |
def query = search.collect { name, value -> | |
"search.names=${encodeURL(name)}&search.values=${encodeURL(value)}" | |
}.join('&') | |
return "${appendIfMissing(buildScan.server, '/')}scans?$query" | |
} | |
private static String encodeURL(String url) { | |
return URLEncoder.encode(url, defaultCharset().name()) | |
} | |
private static boolean isGitInstalled() { | |
return execAndGetReturnValue('git', '--version') == 0 | |
} | |
private static boolean isGitRepo() { | |
return execAndGetReturnValue('git', 'rev-parse', '--is-inside-work-tree') == 0 | |
} | |
private static String appendIfMissing(String str, String suffix) { | |
return str.endsWith(suffix) ? str : str + suffix | |
} | |
private static String trimAtEnd(String str) { | |
return ('x' + str).trim().substring(1) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment