Skip to content

Instantly share code, notes, and snippets.

@jrodbx
Last active October 7, 2023 02:29
Show Gist options
  • Save jrodbx/2ecfe78ec4df859795de1ec8fec410d7 to your computer and use it in GitHub Desktop.
Save jrodbx/2ecfe78ec4df859795de1ec8fec410d7 to your computer and use it in GitHub Desktop.
Example Gradle Enterprise config
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