From 19422b12b47f368e9c313507ae18dd68f3555084 Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Thu, 19 Dec 2019 20:51:05 +0300 Subject: [PATCH] Build: Add buildScanUserData script gradle-build-scan-snippets --- build.gradle.kts | 4 + gradle/buildScanUserData.gradle | 239 ++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 gradle/buildScanUserData.gradle diff --git a/build.gradle.kts b/build.gradle.kts index 0e73909078d..648e2302868 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,10 @@ buildscript { } } +if (kotlinBuildProperties.buildScanServer != null) { + apply(from = "gradle/buildScanUserData.gradle") +} + plugins { idea id("jps-compatible") diff --git a/gradle/buildScanUserData.gradle b/gradle/buildScanUserData.gradle new file mode 100644 index 00000000000..5dba4c28fcf --- /dev/null +++ b/gradle/buildScanUserData.gradle @@ -0,0 +1,239 @@ +/** + * 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) +} \ No newline at end of file