/** * This Gradle script captures data about the OS, IDE, CI, and Git and stores it in build scans via custom tags, custom links, and custom values. * * Proceed as following to benefit from this script in your Gradle build: * * - Copy this script to the root folder of your Gradle project, renaming it to 'build-scan-user-data.gradle' * - Apply the Gradle Build Scan plugin * - Point to your Gradle Enterprise server * - Include this script in the root project's build.gradle(.kts) file via `apply from: 'build-scan-user-data.gradle'` * - Further customize this script to your needs */ tagOs() tagIde() tagCiOrLocal() addCiMetadata() addGitMetadata() addTestParallelization() // Add here other scripts, if needed //apply from:"${rootProject.projectDir}/<>" void tagOs() { buildScan.tag System.getProperty('os.name') } void tagIde() { if (project.hasProperty('android.injected.invoked.from.ide')) { buildScan.tag 'Android Studio' } else if (System.getProperty('idea.version')) { buildScan.tag 'IntelliJ IDEA' } else if (System.getProperty('eclipse.buildId')) { buildScan.tag 'Eclipse' } else if (!isCi()) { buildScan.tag 'Cmd Line' } } void tagCiOrLocal() { buildScan.tag(isCi() ? 'CI' : 'LOCAL') } void addCiMetadata() { if (isJenkins()) { if (System.getenv('BUILD_URL')) { buildScan.link 'Jenkins build', System.getenv('BUILD_URL') } if (System.getenv('BUILD_NUMBER')) { buildScan.value 'CI build number', System.getenv('BUILD_NUMBER') } if (System.getenv('NODE_NAME')) { def agentName = System.getenv('NODE_NAME') == 'master' ? 'master-node' : System.getenv('NODE_NAME') buildScan.tag agentName buildScan.value 'CI node name', agentName } if (System.getenv('JOB_NAME')) { def jobNameLabel = 'CI job' def jobName = System.getenv('JOB_NAME') buildScan.value jobNameLabel, jobName addCustomValueSearchLink 'CI job build scans', [(jobNameLabel): jobName] } if (System.getenv('STAGE_NAME')) { def stageNameLabel = 'CI stage' def stageName = System.getenv('STAGE_NAME') buildScan.value stageNameLabel, stageName addCustomValueSearchLink 'CI stage build scans', [(stageNameLabel): stageName] } } if (isTeamCity()) { def teamCityConfigurationFileProp = 'teamcity.configuration.properties.file' if (project.hasProperty(teamCityConfigurationFileProp)) { def properties = new Properties() properties.load(new FileInputStream("${project.property(teamCityConfigurationFileProp)}")) def teamCityServerUrl = properties.getProperty("teamcity.serverUrl") if (teamCityServerUrl && project.hasProperty('build.number') && project.hasProperty('teamcity.buildType.id')) { def teamCityBuildNumber = project.property('build.number') def teamCityBuildTypeId = project.property('teamcity.buildType.id') buildScan.link 'TeamCity build', "${appendIfMissing(teamCityServerUrl, '/')}viewLog.html?buildNumber=${teamCityBuildNumber}&buildTypeId=${teamCityBuildTypeId}" } } if (project.hasProperty('build.number')) { buildScan.value 'CI build number', project.property('build.number') } if (project.hasProperty('agent.name')) { def agentName = project.property('agent.name') buildScan.tag agentName buildScan.value 'CI agent name', agentName } } if (isCircleCI()) { if (System.getenv('CIRCLE_BUILD_URL')) { buildScan.link 'CircleCI build', System.getenv('CIRCLE_BUILD_URL') } if (System.getenv('CIRCLE_BUILD_NUM')) { buildScan.value 'CI build number', System.getenv('CIRCLE_BUILD_NUM') } if (System.getenv('CIRCLE_JOB')) { def jobLabel = 'CI job' def job = System.getenv('CIRCLE_JOB') buildScan.value jobLabel, job addCustomValueSearchLink 'CI job build scans', [(jobLabel): job] } if (System.getenv('CIRCLE_WORKFLOW_ID')) { def workflowIdLabel = 'CI workflow' def workflowId = System.getenv('CIRCLE_WORKFLOW_ID') buildScan.value workflowIdLabel, workflowId addCustomValueSearchLink 'CI workflow build scans', [(workflowIdLabel): workflowId] } } if (isBamboo()) { if (System.getenv('bamboo_resultsUrl')) { buildScan.link 'Bamboo build', System.getenv('bamboo_resultsUrl') } if (System.getenv('bamboo_buildNumber')) { buildScan.value 'CI build number', System.getenv('bamboo_buildNumber') } if (System.getenv('bamboo_planName')) { def planNameLabel = 'CI plan' def planName = System.getenv('bamboo_planName') buildScan.value planNameLabel, planName addCustomValueSearchLink 'CI plan build scans', [(planNameLabel): planName] } if (System.getenv('bamboo_buildPlanName')) { def jobNameLabel = 'CI job' def jobName = System.getenv('bamboo_buildPlanName') buildScan.value jobNameLabel, jobName addCustomValueSearchLink 'CI job build scans', [(jobNameLabel): jobName] } if (System.getenv('bamboo_agentId')) { def agentId = System.getenv('bamboo_agentId') buildScan.tag agentId buildScan.value 'CI agent ID', agentId } } } void addGitMetadata() { buildScan.background { if (!isGitInstalled()) { return } def gitCommitId = execAndGetStdout('git', 'rev-parse', '--short=8', '--verify', 'HEAD') def gitBranchName = execAndGetStdout('git', 'rev-parse', '--abbrev-ref', 'HEAD') def gitStatus = execAndGetStdout('git', 'status', '--porcelain') if (gitCommitId) { def commitIdLabel = 'Git commit id' value commitIdLabel, gitCommitId addCustomValueSearchLink 'Git commit id build scans', [(commitIdLabel): gitCommitId] def originUrl = execAndGetStdout('git', 'config', '--get', 'remote.origin.url') if (originUrl.contains('github.com')) { // only for GitHub def repoPath = (originUrl =~ /(.*)github\.com[\/|:](.*)/)[0][2] if (repoPath.endsWith('.git')) { repoPath = repoPath.substring(0, repoPath.length() - 4) } link 'Github Source', "https://github.com/$repoPath/tree/" + gitCommitId } } if (gitBranchName) { tag gitBranchName value 'Git branch', gitBranchName } if (gitStatus) { tag 'Dirty' value 'Git status', gitStatus } } } void addTestParallelization() { allprojects { p -> p.tasks.withType(Test).configureEach { t -> doFirst { buildScan.value "${t.identityPath}#maxParallelForks", t.maxParallelForks.toString() } } } } static boolean isCi() { isJenkins() || isTeamCity() || isCircleCI() || isBamboo() } static boolean isJenkins() { System.getenv('JENKINS_URL') } static boolean isTeamCity() { System.getenv('TEAMCITY_VERSION') } static boolean isCircleCI() { System.getenv('CIRCLECI') } static boolean isBamboo() { System.getenv('bamboo_resultsUrl') } String execAndGetStdout(String... args) { def stdout = new ByteArrayOutputStream() exec { commandLine(args) standardOutput = stdout } trimAtEnd(stdout.toString()) } void addCustomValueSearchLink(String title, Map search) { if (buildScan.server) { buildScan.link title, customValueSearchUrl(search) } } String customValueSearchUrl(Map search) { def query = search.collect { name, value -> "search.names=${encodeURL(name)}&search.values=${encodeURL(value)}" }.join('&') "${appendIfMissing(buildScan.server, '/')}scans?$query" } static String encodeURL(String url) { URLEncoder.encode(url, 'UTF-8') } static boolean isGitInstalled() { try { "git --version".execute().waitFor() == 0 } catch (IOException ignored) { false } } static String appendIfMissing(String str, String suffix) { str.endsWith(suffix) ? str : str + suffix } static String trimAtEnd(String str) { ('x' + str).trim().substring(1) }