123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930 |
- #!/usr/bin/env groovy
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- import groovy.json.JsonOutput
- PIPELINE_CONFIG_FILE = 'scripts/build/Jenkins/lumberyard.json'
- INCREMENTAL_BUILD_SCRIPT_PATH = 'scripts/build/bootstrap/incremental_build_util.py'
- PIPELINE_RETRY_ATTEMPTS = 3
- EMPTY_JSON = readJSON text: '{}'
- PROJECT_REPOSITORY_NAME = 'loft-arch-vis-sample'
- PROJECT_ORGANIZATION_NAME = 'aws-lumberyard'
- PROJECT_PATH = "Project"
- ENGINE_REPOSITORY_NAME = 'o3de'
- ENGINE_ORGANIZATION_NAME = 'o3de'
- ENGINE_BRANCH_DEFAULT = "${env.BRANCH_DEFAULT}" ?: "${env.BRANCH_NAME}"
- // Branches with build snapshots
- BUILD_SNAPSHOTS = ['development', 'stabilization/2205']
- // Build snapshots with empty snapshot (for use with 'SNAPSHOT' pipeline parameter)
- BUILD_SNAPSHOTS_WITH_EMPTY = BUILD_SNAPSHOTS + ''
- // The default build snapshot to be selected in the 'SNAPSHOT' pipeline parameter
- DEFAULT_BUILD_SNAPSHOT = BUILD_SNAPSHOTS_WITH_EMPTY.get(0)
- // Branches with build snapshots as comma separated value string
- env.BUILD_SNAPSHOTS = BUILD_SNAPSHOTS.join(",")
- def pipelineProperties = []
- def pipelineParameters = [
- // Build/clean Parameters
- // The CLEAN_OUTPUT_DIRECTORY is used by ci_build scripts. Creating the parameter here passes it as an environment variable to jobs and is consumed that way
- booleanParam(defaultValue: false, description: 'Deletes the contents of the output directory before building. This will cause a \"clean\" build. NOTE: does not imply CLEAN_ASSETS', name: 'CLEAN_OUTPUT_DIRECTORY'),
- booleanParam(defaultValue: false, description: 'Deletes the contents of the output directories of the AssetProcessor before building.', name: 'CLEAN_ASSETS'),
- booleanParam(defaultValue: false, description: 'Deletes the contents of the workspace and forces a complete pull.', name: 'CLEAN_WORKSPACE'),
- booleanParam(defaultValue: false, description: 'Recreates the volume used for the workspace. The volume will be created out of a snapshot taken from main.', name: 'RECREATE_VOLUME'),
- stringParam(defaultValue: "${ENGINE_BRANCH_DEFAULT}", description: 'Sets a different branch from o3de engine repo to use or use commit id. Default is branchname', trim: true, name: 'ENGINE_BRANCH')
- ]
- def palSh(cmd, lbl = '', winSlashReplacement = true, winCharReplacement = true) {
- if (env.IS_UNIX) {
- sh label: lbl,
- script: cmd
- } else {
- if (winSlashReplacement) {
- cmd = cmd.replace('/','\\')
- }
- if (winCharReplacement) {
- cmd = cmd.replace('%', '%%')
- }
- bat label: lbl,
- script: cmd
- }
- }
- def palMkdir(path) {
- if (env.IS_UNIX) {
- sh label: "Making directories ${path}",
- script: "mkdir -p ${path}"
- } else {
- def win_path = path.replace('/','\\')
- bat label: "Making directories ${win_path}",
- script: "mkdir ${win_path}."
- }
- }
- def palRm(path) {
- if (env.IS_UNIX) {
- sh label: "Removing ${path}",
- script: "rm ${path}"
- } else {
- def win_path = path.replace('/','\\')
- bat label: "Removing ${win_path}",
- script: "del /Q ${win_path}"
- }
- }
- def palRmDir(path) {
- if (env.IS_UNIX) {
- sh label: "Removing ${path}",
- script: "if [ -d ${path} ]; then rm -rf ${path}; fi"
- } else {
- def win_path = path.replace('/','\\')
- bat label: "Removing ${win_path}",
- script: "IF exist ${win_path} rd /s /q ${win_path}"
- }
- }
- def IsPullRequest(branchName) {
- // temporarily using the name to detect if we are in a PR
- // In the future we will check with github
- return branchName.startsWith('PR-')
- }
- def IsJobEnabled(branchName, buildTypeMap, pipelineName, platformName) {
- if (IsPullRequest(branchName)) {
- return buildTypeMap.value.TAGS && buildTypeMap.value.TAGS.contains(pipelineName)
- }
- def job_list_override = params.JOB_LIST_OVERRIDE ? params.JOB_LIST_OVERRIDE.tokenize(',') : ''
- if (!job_list_override.isEmpty()) {
- return params[platformName] && job_list_override.contains(buildTypeMap.key);
- } else {
- return params[platformName] && buildTypeMap.value.TAGS && buildTypeMap.value.TAGS.contains(pipelineName)
- }
- }
- def IsAPLogUpload(branchName, jobName) {
- return !IsPullRequest(branchName) && jobName.toLowerCase().contains('asset') && env.AP_LOGS_S3_BUCKET
- }
- def GetRunningPipelineName(JENKINS_JOB_NAME) {
- // If the job name has an underscore
- def job_parts = JENKINS_JOB_NAME.tokenize('/')[0].tokenize('_')
- if (job_parts.size() > 1) {
- return [job_parts.take(job_parts.size() - 1).join('_'), job_parts[job_parts.size()-1]]
- }
- return [job_parts[0], 'default']
- }
- @NonCPS
- def RegexMatcher(str, regex) {
- def matcher = (str =~ regex)
- return matcher ? matcher.group(1) : null
- }
- def LoadPipelineConfig(String pipelineName, String branchName) {
- echo 'Loading pipeline config'
- def pipelineConfig = {}
- pipelineConfig = readJSON file: PIPELINE_CONFIG_FILE
- palRm(PIPELINE_CONFIG_FILE)
- pipelineConfig.platforms = EMPTY_JSON
- // Load the pipeline configs per platform
- pipelineConfig.PIPELINE_CONFIGS.each { pipeline_config ->
- def platform_regex = pipeline_config.replace('.','\\.').replace('*', '(.*)')
- if (!env.IS_UNIX) {
- platform_regex = platform_regex.replace('/','\\\\')
- }
- echo "Searching platform pipeline configs in ${pipeline_config} using ${platform_regex}"
- for (pipeline_config_path in findFiles(glob: pipeline_config)) {
- echo "\tFound platform pipeline config ${pipeline_config_path}"
- def platform = RegexMatcher(pipeline_config_path, platform_regex)
- if (platform) {
- pipelineConfig.platforms[platform] = EMPTY_JSON
- pipelineConfig.platforms[platform].PIPELINE_ENV = readJSON file: pipeline_config_path.toString()
- }
- palRm(pipeline_config_path.toString())
- }
- }
- // Load the build configs
- pipelineConfig.BUILD_CONFIGS.each { build_config ->
- def platform_regex = build_config.replace('.','\\.').replace('*', '(.*)')
- if (!env.IS_UNIX) {
- platform_regex = platform_regex.replace('/','\\\\')
- }
- echo "Searching configs in ${build_config} using ${platform_regex}"
- for (build_config_path in findFiles(glob: build_config)) {
- echo "\tFound config ${build_config_path}"
- def platform = RegexMatcher(build_config_path, platform_regex)
- if (platform) {
- pipelineConfig.platforms[platform].build_types = readJSON file: build_config_path.toString()
- }
- }
- }
- return pipelineConfig
- }
- def GetBuildEnvVars(Map platformEnv, Map buildTypeEnv, String pipelineName) {
- def envVarMap = [:]
- platformPipelineEnv = platformEnv['ENV'] ?: [:]
- platformPipelineEnv.each { var ->
- envVarMap[var.key] = var.value
- }
- platformEnvOverride = platformEnv['PIPELINE_ENV_OVERRIDE'] ?: [:]
- platformPipelineEnvOverride = platformEnvOverride[pipelineName] ?: [:]
- platformPipelineEnvOverride.each { var ->
- envVarMap[var.key] = var.value
- }
- buildTypeEnv.each { var ->
- // This may override the above one if there is an entry defined by the job
- envVarMap[var.key] = var.value
- }
- // Environment that only applies to to Jenkins tweaks.
- // For 3rdParty downloads, we store them in the EBS volume so we can reuse them across node
- // instances. This allow us to scale up and down without having to re-download 3rdParty
- envVarMap['LY_PACKAGE_DOWNLOAD_CACHE_LOCATION'] = "${envVarMap['WORKSPACE']}/3rdParty/downloaded_packages"
- envVarMap['LY_PACKAGE_UNPACK_LOCATION'] = "${envVarMap['WORKSPACE']}/3rdParty/packages"
- return envVarMap
- }
- def GetEnvStringList(Map envVarMap) {
- def strList = []
- envVarMap.each { var ->
- strList.add("${var.key}=${var.value}")
- }
- return strList
- }
- def GetEngineRemoteConfig(remoteConfigs) {
- def engineRemoteConfigs = [name: "${ENGINE_REPOSITORY_NAME}",
- url: remoteConfigs.url[0]
- .replace("${PROJECT_REPOSITORY_NAME}", "${ENGINE_REPOSITORY_NAME}")
- .replace("/${PROJECT_ORGANIZATION_NAME}/", "/${ENGINE_ORGANIZATION_NAME}/"),
- credentialsId: remoteConfigs.credentialsId[0]
- ]
- return engineRemoteConfigs
- }
- def CheckoutBootstrapScripts(String branchName) {
- checkout([$class: 'GitSCM',
- branches: [[name: "*/${branchName}"]],
- doGenerateSubmoduleConfigurations: false,
- extensions: [
- [$class: 'PruneStaleBranch'],
- [$class: 'AuthorInChangelog'],
- [$class: 'SparseCheckoutPaths', sparseCheckoutPaths: [
- [ $class: 'SparseCheckoutPath', path: 'scripts/build/Jenkins/' ],
- [ $class: 'SparseCheckoutPath', path: 'scripts/build/bootstrap/' ],
- [ $class: 'SparseCheckoutPath', path: 'scripts/build/Platform' ]
- ]],
- // Shallow checkouts break changelog computation. Do not enable.
- [$class: 'CloneOption', noTags: false, reference: '', shallow: false]
- ],
- submoduleCfg: [],
- userRemoteConfigs: [GetEngineRemoteConfig(scm.userRemoteConfigs)]
- ])
- }
- def CheckoutRepo(boolean disableSubmodules = false) {
- def projectsAndUrl = [
- "${ENGINE_REPOSITORY_NAME}": GetEngineRemoteConfig(scm.userRemoteConfigs),
- "${PROJECT_REPOSITORY_NAME}": scm.userRemoteConfigs[0]
- ]
- projectsAndUrl.each { projectAndUrl ->
- if (!fileExists(projectAndUrl.key)) {
- palMkdir(projectAndUrl.key)
- }
- dir(projectAndUrl.key) {
- palSh('git lfs uninstall', 'Git LFS Uninstall') // Prevent git from pulling lfs objects during checkout
- if (fileExists('.git')) {
- // If the repository after checkout is locked, likely we took a snapshot while git was running,
- // to leave the repo in a usable state, garbage collect.
- def indexLockFile = '.git/index.lock'
- if (fileExists(indexLockFile)) {
- palSh('git gc', 'Git GarbageCollect')
- }
- if (fileExists(indexLockFile)) { // if it is still there, remove it
- palRm(indexLockFile)
- }
- }
- }
- }
- def random = new Random()
- def retryAttempt = 0
- retry(5) {
- if (retryAttempt > 0) {
- sleep random.nextInt(60 * retryAttempt) // Stagger checkouts to prevent HTTP 429 (Too Many Requests) response from Github
- }
- retryAttempt = retryAttempt + 1
- projectsAndUrl.each { projectAndUrl ->
- dir(projectAndUrl.key) {
- def branchName = scm.branches
- if (projectAndUrl.key == "${ENGINE_REPOSITORY_NAME}") {
- branchName = [[name: params.ENGINE_BRANCH]]
- }
- checkout scm: [
- $class: 'GitSCM',
- branches: branchName,
- extensions: [
- [$class: 'PruneStaleBranch'],
- [$class: 'AuthorInChangelog'],
- [$class: 'SubmoduleOption', disableSubmodules: disableSubmodules, recursiveSubmodules: true],
- [$class: 'CheckoutOption', timeout: 60]
- ],
- userRemoteConfigs: [projectAndUrl.value]
- ]
- }
- }
- }
- // CHANGE_ID is used by some scripts to identify uniquely the current change (usually metric jobs)
- dir(PROJECT_REPOSITORY_NAME) {
- palSh('git rev-parse HEAD > commitid', 'Getting commit id')
- env.CHANGE_ID = readFile file: 'commitid'
- env.CHANGE_ID = env.CHANGE_ID.trim()
- palRm('commitid')
- // CHANGE_DATE is used by the installer to provide some ability to sort tagged builds in addition to BRANCH_NAME and CHANGE_ID
- commitDateFmt = '%%cI'
- if (env.IS_UNIX) commitDateFmt = '%cI'
- palSh("git show -s --format=${commitDateFmt} ${env.CHANGE_ID} > commitdate", 'Getting commit date')
- env.CHANGE_DATE = readFile file: 'commitdate'
- env.CHANGE_DATE = env.CHANGE_DATE.trim()
- palRm('commitdate')
- }
- }
- def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) {
- unstash name: 'incremental_build_script'
- def pythonCmd = ''
- if (env.IS_UNIX) pythonCmd = 'sudo -E python3 -u '
- else pythonCmd = 'python3 -u '
- if(recreateVolume) {
- palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume', winSlashReplacement=false)
- }
- palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false)
- if (env.IS_UNIX) {
- sh label: 'Setting volume\'s ownership',
- script: """
- if sudo test ! -d "${workspace}"; then
- sudo mkdir -p ${workspace}
- cd ${workspace}/..
- sudo chown -R lybuilder:root .
- fi
- """
- }
- }
- def PreBuildCommonSteps(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean mount = true, boolean disableSubmodules = false) {
- echo 'Starting pre-build common steps...'
- if (mount) {
- if(env.RECREATE_VOLUME?.toBoolean()){
- echo 'Starting to recreating drive...'
- HandleDriveMount(snapshot, repositoryName, projectName, pipeline, branchName, platform, buildType, workspace, true)
- } else {
- echo 'Starting to mounting drive...'
- HandleDriveMount(snapshot, repositoryName, projectName, pipeline, branchName, platform, buildType, workspace, false)
- }
- }
- // Cleanup previous repo location, we are currently at the root of the workspace, if we have a .git folder
- // we need to cleanup. Once all branches take this relocation, we can remove this
- if (env.CLEAN_WORKSPACE?.toBoolean() || fileExists("${workspace}/.git")) {
- if (fileExists(workspace)) {
- palRmDir(workspace)
- }
- }
- dir(workspace) {
- // Add folder where we will store the 3rdParty downloads and packages
- if (!fileExists('3rdParty')) {
- palMkdir('3rdParty')
- }
- CheckoutRepo(disableSubmodules)
- }
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- // Get python
- if (env.IS_UNIX) {
- sh label: 'Getting python',
- script: 'python/get_python.sh'
- } else {
- bat label: 'Getting python',
- script: 'python/get_python.bat'
- }
- // Always run the clean step, the scripts detect what variables were set, but it also cleans if
- // the NODE_LABEL has changed
- def command = "${pipelineConfig.PYTHON_DIR}/python"
- if (env.IS_UNIX) command += '.sh'
- else command += '.cmd'
- command += " -u ${pipelineConfig.BUILD_ENTRY_POINT} --platform ${platform} --type clean"
- palSh(command, "Running ${platform} clean")
-
- // Run lfs in a separate step. Jenkins is unable to load the credentials for the custom LFS endpoint
- withCredentials([usernamePassword(credentialsId: "${env.GITHUB_USER}", passwordVariable: 'accesstoken', usernameVariable: 'username')]) {
- palSh("git config -f .lfsconfig lfs.url https://${username}:${accesstoken}@${env.LFS_URL}", 'Set credentials', false)
- }
- if(fileExists('.lfsconfig')) {
- palSh("git lfs install", "LFS config exists. Installing LFS hooks to local repo")
- palSh("git lfs pull", "Pulling new LFS objects")
- }
- }
- }
- def Build(Map pipelineConfig, String platform, String type, String workspace) {
- // If EXECUTE_FROM_PROJECT is defined, we execute the script from the project instead of from the engine
- // In both cases, the scripts are in the engine, is just what the current dir is and how we get to the scripts
- def currentDir = "${workspace}/${ENGINE_REPOSITORY_NAME}"
- def pathToEngine = ""
- timeout(time: env.TIMEOUT, unit: 'MINUTES', activity: true) {
- def command = "${pipelineConfig.PYTHON_DIR}/python"
- def ext = ''
- if (env.IS_UNIX) {
- command += '.sh'
- ext = '.sh'
- }
- else command += '.cmd'
- // Setup environment for project execution, otherwise, register the project
- if (env.EXECUTE_FROM_PROJECT?.toBoolean()) {
- currentDir = "${workspace}/${PROJECT_REPOSITORY_NAME}"
- pathToEngine = "../${ENGINE_REPOSITORY_NAME}/"
- } else {
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- palSh("scripts/o3de${ext} register --project-path ${workspace}/${PROJECT_REPOSITORY_NAME}/${PROJECT_PATH}", "Registering project ${PROJECT_REPOSITORY_NAME}")
- }
- }
- command += " -u ${pipelineConfig.BUILD_ENTRY_POINT} --platform ${platform} --type ${type}"
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- palSh(command, "Running ${platform} ${type}")
- }
- }
- }
- def TestMetrics(Map pipelineConfig, String workspace, String branchName, String repoName, String buildJobName, String outputDirectory, String configuration) {
- catchError(buildResult: null, stageResult: null) {
- def cmakeBuildDir = [workspace, ENGINE_REPOSITORY_NAME, outputDirectory].join('/')
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- checkout scm: [
- $class: 'GitSCM',
- branches: [[name: '*/main']],
- extensions: [
- [$class: 'AuthorInChangelog'],
- [$class: 'RelativeTargetDirectory', relativeTargetDir: 'mars']
- ],
- userRemoteConfigs: [[url: "${env.MARS_REPO}", name: 'mars', credentialsId: "${env.GITHUB_USER}"]]
- ]
- withCredentials([usernamePassword(credentialsId: "${env.SERVICE_USER}", passwordVariable: 'apitoken', usernameVariable: 'username')]) {
- def command = "${pipelineConfig.PYTHON_DIR}/python.cmd -u mars/scripts/python/ctest_test_metric_scraper.py " +
- '-e jenkins.creds.user %username% -e jenkins.creds.pass %apitoken% ' +
- "-e jenkins.base_url ${env.JENKINS_URL} " +
- "${cmakeBuildDir} ${branchName} %BUILD_NUMBER% AR ${configuration} ${repoName} --url ${env.BUILD_URL.replace('%','%%')}"
- bat label: "Publishing ${buildJobName} Test Metrics",
- script: command
- }
- }
- }
- }
- def BenchmarkMetrics(Map pipelineConfig, String workspace, String branchName, String outputDirectory) {
- catchError(buildResult: null, stageResult: null) {
- def cmakeBuildDir = [workspace, ENGINE_REPOSITORY_NAME, outputDirectory].join('/')
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- checkout scm: [
- $class: 'GitSCM',
- branches: [[name: '*/main']],
- extensions: [
- [$class: 'AuthorInChangelog'],
- [$class: 'RelativeTargetDirectory', relativeTargetDir: 'mars']
- ],
- userRemoteConfigs: [[url: "${env.MARS_REPO}", name: 'mars', credentialsId: "${env.GITHUB_USER}"]]
- ]
- def command = "${pipelineConfig.PYTHON_DIR}/python.cmd -u mars/scripts/python/benchmark_scraper.py ${cmakeBuildDir} ${branchName}"
- palSh(command, "Publishing Benchmark Metrics")
- }
- }
- }
- def ExportTestResults(Map options, String platform, String type, String workspace, Map params) {
- catchError(message: "Error exporting tests results (this won't fail the build)", buildResult: 'SUCCESS', stageResult: 'FAILURE') {
- def o3deroot = "${workspace}/${ENGINE_REPOSITORY_NAME}"
- dir("${o3deroot}/${params.OUTPUT_DIRECTORY}") {
- junit testResults: "Testing/**/*.xml", skipPublishingChecks: true
- palRmDir("Testing")
- // Recreate test runner xml directories that need to be pre generated
- palMkdir("Testing/Pytest")
- palMkdir("Testing/Gtest")
- }
- }
- }
- def ExportTestScreenshots(Map options, String branchName, String platformName, String jobName, String workspace, Map params) {
- catchError(message: "Error exporting test screenshots (this won't fail the build)", buildResult: 'SUCCESS', stageResult: 'FAILURE') {
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- def screenshotsFolder = "${workspace}/${PROJECT_REPOSITORY_NAME}/user/Scripts/Screenshots"
- def s3Uploader = "scripts/build/tools/upload_to_s3.py"
- def command = "${options.PYTHON_DIR}/python.cmd -u ${s3Uploader} --base_dir ${screenshotsFolder} " +
- '--file_regex \\"(.*\$)\\" ' +
- "--bucket ${env.TEST_SCREENSHOT_BUCKET} " +
- "--search_subdirectories True --key_prefix ${PROJECT_REPOSITORY_NAME}/${branchName}/${env.BUILD_NUMBER}/${jobName} " +
- '--extra_args {\\"ACL\\":\\"bucket-owner-full-control\\"}'
- palSh(command, "Uploading test screenshots for ${jobName}")
- }
- }
- }
- def UploadAPLogs(Map options, String branchName, String platformName, String jobName, String workspace, Map params) {
- catchError(message: "Error exporting logs (this won't fail the build)", buildResult: 'SUCCESS', stageResult: 'FAILURE') {
- dir("${workspace}/${ENGINE_REPOSITORY_NAME}") {
- def apLogsPath = "${workspace}/${PROJECT_REPOSITORY_NAME}/user/log"
- def s3UploadScriptPath = "scripts/build/tools/upload_to_s3.py"
- if(env.IS_UNIX) {
- pythonPath = "${options.PYTHON_DIR}/python.sh"
- }
- else {
- pythonPath = "${options.PYTHON_DIR}/python.cmd"
- }
- def command = "${pythonPath} -u ${s3UploadScriptPath} --base_dir ${apLogsPath} " +
- "--file_regex \".*\" --bucket ${env.AP_LOGS_S3_BUCKET} " +
- "--search_subdirectories True --key_prefix ${PROJECT_REPOSITORY_NAME}/${branchName}/${env.BUILD_NUMBER}/${platformName}/${jobName} " +
- '--extra_args {\\"ACL\\":\\"bucket-owner-full-control\\"}'
- palSh(command, "Uploading AP logs for job ${jobName} for branch ${branchName}", false)
- }
- }
- }
- def PostBuildCommonSteps(String workspace, boolean mount = true) {
- echo 'Starting post-build common steps...'
- if (mount) {
- def pythonCmd = ''
- if (env.IS_UNIX) pythonCmd = 'sudo -E python3 -u '
- else pythonCmd = 'python3 -u '
- try {
- timeout(5) {
- palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action unmount", 'Unmounting volume')
- }
- } catch (Exception e) {
- echo "Unmount script error ${e}"
- }
- }
- }
- def CreateSetupStage(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipelineName, String branchName, String platformName, String jobName, Map environmentVars, boolean onlyMountEBSVolume = false) {
- return {
- stage('Setup') {
- if(onlyMountEBSVolume) {
- HandleDriveMount(snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], false)
- } else {
- PreBuildCommonSteps(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
- }
- }
- }
- }
- def CreateBuildStage(Map pipelineConfig, String platformName, String jobName, Map environmentVars) {
- return {
- stage("${jobName}") {
- Build(pipelineConfig, platformName, jobName, environmentVars['WORKSPACE'])
- }
- }
- }
- def CreateTestMetricsStage(Map pipelineConfig, String branchName, Map environmentVars, String buildJobName, String outputDirectory, String configuration) {
- return {
- stage("${buildJobName}_metrics") {
- TestMetrics(pipelineConfig, environmentVars['WORKSPACE'], branchName, env.DEFAULT_REPOSITORY_NAME, buildJobName, outputDirectory, configuration)
- BenchmarkMetrics(pipelineConfig, environmentVars['WORKSPACE'], branchName, outputDirectory)
- }
- }
- }
- def CreateExportTestResultsStage(Map pipelineConfig, String platformName, String jobName, Map environmentVars, Map params) {
- return {
- stage("${jobName}_results") {
- ExportTestResults(pipelineConfig, platformName, jobName, environmentVars['WORKSPACE'], params)
- }
- }
- }
- def CreateExportTestScreenshotsStage(Map pipelineConfig, String branchName, String platformName, String jobName, Map environmentVars, Map params) {
- return {
- stage("${jobName}_screenshots") {
- ExportTestScreenshots(pipelineConfig, branchName, platformName, jobName, environmentVars['WORKSPACE'], params)
- }
- }
- }
- def CreateUploadAPLogsStage(Map pipelineConfig, String branchName, String platformName, String jobName, String workspace, Map params) {
- return {
- stage("${jobName}_upload_ap_logs") {
- UploadAPLogs(pipelineConfig, branchName, platformName, jobName, workspace, params)
- }
- }
- }
- def CreateTeardownStage(Map environmentVars) {
- return {
- stage('Teardown') {
- PostBuildCommonSteps(environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
- }
- }
- }
- def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName, boolean onlyMountEBSVolume = false) {
- def nodeLabel = envVars['NODE_LABEL']
- return {
- def currentResult = ''
- def currentException = ''
- retry(PIPELINE_RETRY_ATTEMPTS) {
- node("${nodeLabel}") {
- if(isUnix()) { // Has to happen inside a node
- envVars['IS_UNIX'] = 1
- }
- withEnv(GetEnvStringList(envVars)) {
- def build_job_name = build_job.key
- try {
- CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call()
- if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages
- pipelineEnvVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
- build_job.value.steps.each { build_step ->
- build_job_name = build_step
- // This addition of maps makes it that the right operand will override entries if they overlap with the left operand
- envVars = pipelineEnvVars + GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
- try {
- CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call()
- }
- catch (Exception e) {
- if (envVars['NONBLOCKING_STEP']?.toBoolean()) {
- unstable(message: "Build step ${build_step} failed but it's a non-blocking step in build job ${build_job.key}")
- } else {
- throw e
- }
- }
- }
- } else {
- CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call()
- }
- }
- catch(Exception e) {
- if (e instanceof org.jenkinsci.plugins.workflow.steps.FlowInterruptedException) {
- def causes = e.getCauses().toString()
- if (causes.contains('RemovedNodeCause')) {
- error "Node disconnected during build: ${e}" // Error raised to retry stage on a new node
- }
- }
- if (IsAPLogUpload(branchName, build_job_name)) {
- CreateUploadAPLogsStage(pipelineConfig, branchName, platform.key, build_job_name, envVars['WORKSPACE'], platform.value.build_types[build_job_name].PARAMETERS).call()
- }
- // All other errors will be raised outside the retry block
- currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE'
- currentException = e.toString()
- }
- finally {
- def params = platform.value.build_types[build_job_name].PARAMETERS
- if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') {
- def output_directory = params.OUTPUT_DIRECTORY
- def configuration = params.CONFIGURATION
- CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call()
- }
- if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') {
- CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call()
- }
- if (params && params.containsKey('TEST_SCREENSHOTS') && params.TEST_SCREENSHOTS == 'True' && currentResult == 'FAILURE') {
- CreateExportTestScreenshotsStage(pipelineConfig, branchName, platform.key, build_job_name, envVars, params).call()
- }
- CreateTeardownStage(envVars).call()
- }
- }
- }
- }
- // https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java
- // {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED}
- if (currentResult == 'FAILURE') {
- currentBuild.result = 'FAILURE'
- error "FAILURE: ${currentException}"
- } else if (currentResult == 'UNSTABLE') {
- currentBuild.result = 'UNSTABLE'
- unstable(message: "UNSTABLE: ${currentException}")
- }
- }
- }
- // Used in CreateBuildJobs() to preprocess the build_job steps to programically create
- // Node sections with a set of steps that can run on that node.
- class PipeStepJobData {
- String m_nodeLabel = ""
- def m_steps = []
- PipeStepJobData(String label) {
- this.m_nodeLabel = label
- }
- def addStep(def step) {
- this.m_steps.add(step)
- }
- }
- def CreateBuildJobs(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName) {
- // if this is a pipeline, split jobs based on the NODE_LABEL
- if(build_job.value.steps) {
- def defaultLabel = envVars['NODE_LABEL']
- def lastNodeLable = ""
- def jobList = []
- def currentIdx = -1;
- // iterate the steps to build the order of node label + steps sets.
- // Order matters, as it is executed from first to last.
- // example layout.
- // node A
- // step 1
- // step 2
- // node B
- // step 3
- // node C
- // step 4
- build_job.value.steps.each { build_step ->
- //if node label defined
- if(platform.value.build_types[build_step] && platform.value.build_types[build_step].PIPELINE_ENV &&
- platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL']) {
- //if the last node label doen't match the new one, append it.
- if(platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL'] != lastNodeLable) {
- lastNodeLable = platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL']
- jobList.add(new PipeStepJobData(lastNodeLable))
- currentIdx++
- }
- }
- //no label define, so it needs to run on the default node label
- else if(lastNodeLable != defaultLabel) { //if the last node is not the default, append default
- lastNodeLable = defaultLabel
- jobList.add(new PipeStepJobData(lastNodeLable))
- currentIdx++
- }
- //add the build_step to the current node
- jobList[currentIdx].addStep(build_step)
- }
- return {
- jobList.eachWithIndex{ element, idx ->
- //update the node label + steps to the discovered data
- envVars['NODE_LABEL'] = element.m_nodeLabel
- build_job.value.steps = element.m_steps
- //no any additional nodes just mount the drive, do not handle clean parameters as that will be done by the first node.
- boolean onlyMountEBSVolume = idx != 0;
- //add this node
- CreateSingleNode(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName, onlyMountEBSVolume).call()
- }
- }
- } else {
- return CreateSingleNode(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName)
- }
- }
- def projectName = ''
- def pipelineName = ''
- def branchName = ''
- def pipelineConfig = {}
- // Start Pipeline
- try {
- stage('Setup Pipeline') {
- node('controller') {
- def envVarList = []
- if (isUnix()) {
- envVarList.add('IS_UNIX=1')
- }
- withEnv(envVarList) {
- timestamps {
- repositoryUrl = scm.getUserRemoteConfigs()[0].getUrl()
- // repositoryName is the full repository name
- repositoryName = (repositoryUrl =~ /https:\/\/github.com\/(.*)\.git/)[0][1]
- env.REPOSITORY_NAME = repositoryName
- (projectName, pipelineName) = GetRunningPipelineName(env.JOB_NAME) // env.JOB_NAME is the name of the job given by Jenkins
- env.PIPELINE_NAME = pipelineName
- if(env.BRANCH_NAME) {
- branchName = env.BRANCH_NAME
- } else {
- branchName = scm.branches[0].name // for non-multibranch pipelines
- env.BRANCH_NAME = branchName // so scripts that read this environment have it (e.g. incremental_build_util.py)
- }
- if(env.CHANGE_TARGET) {
- // PR builds
- if(BUILD_SNAPSHOTS.contains(env.CHANGE_TARGET)) {
- snapshot = env.CHANGE_TARGET
- echo "Snapshot for destination branch \"${env.CHANGE_TARGET}\" found."
- } else {
- snapshot = DEFAULT_BUILD_SNAPSHOT
- echo "Snapshot for destination branch \"${env.CHANGE_TARGET}\" does not exist, defaulting to snapshot \"${snapshot}\""
- }
- } else {
- // Non-PR builds
- pipelineParameters.add(choice(defaultValue: DEFAULT_BUILD_SNAPSHOT, name: 'SNAPSHOT', choices: BUILD_SNAPSHOTS_WITH_EMPTY, description: 'Selects the build snapshot to use. A more diverted snapshot will cause longer build times, but will not cause build failures.'))
- snapshot = env.SNAPSHOT
- echo "Snapshot \"${snapshot}\" selected."
- }
- pipelineProperties.add(disableConcurrentBuilds())
- def engineBranch = params.ENGINE_BRANCH ?: "${ENGINE_BRANCH_DEFAULT}" // This allows the first run to work with parameters having null value, but use the engine branch parameter afterwards
- echo "Running repository: \"${repositoryName}\", pipeline: \"${pipelineName}\", branch: \"${branchName}\" on engine branch \"${engineBranch}\"..."
- CheckoutBootstrapScripts(engineBranch)
- // Load configs
- pipelineConfig = LoadPipelineConfig(pipelineName, branchName)
- // Add each platform as a parameter that the user can disable if needed
- if (!IsPullRequest(branchName)) {
- pipelineParameters.add(stringParam(defaultValue: '', description: 'Filters and overrides the list of jobs to run for each of the below platforms (comma-separated). Can\'t be used during a pull request.', name: 'JOB_LIST_OVERRIDE'))
- pipelineConfig.platforms.each { platform ->
- pipelineParameters.add(booleanParam(defaultValue: true, description: '', name: platform.key))
- }
- }
- // Add additional Jenkins parameters
- pipelineConfig.platforms.each { platform ->
- platformEnv = platform.value.PIPELINE_ENV
- pipelineJenkinsParameters = platformEnv['PIPELINE_JENKINS_PARAMETERS'] ?: [:]
- jenkinsParametersToAdd = pipelineJenkinsParameters[pipelineName] ?: [:]
- jenkinsParametersToAdd.each{ jenkinsParameter ->
- defaultValue = jenkinsParameter['default_value']
- // Use last run's value as default value so we can save values in different Jenkins environment
- if (jenkinsParameter['use_last_run_value']?.toBoolean()) {
- defaultValue = params."${jenkinsParameter['parameter_name']}" ?: jenkinsParameter['default_value']
- }
- switch (jenkinsParameter['parameter_type']) {
- case 'string':
- pipelineParameters.add(stringParam(defaultValue: defaultValue,
- description: jenkinsParameter['description'],
- name: jenkinsParameter['parameter_name']
- ))
- break
- case 'boolean':
- pipelineParameters.add(booleanParam(defaultValue: defaultValue,
- description: jenkinsParameter['description'],
- name: jenkinsParameter['parameter_name']
- ))
- break
- case 'password':
- pipelineParameters.add(password(defaultValue: defaultValue,
- description: jenkinsParameter['description'],
- name: jenkinsParameter['parameter_name']
- ))
- break
- }
- }
- }
- pipelineProperties.add(parameters(pipelineParameters))
- properties(pipelineProperties)
- // Stash the INCREMENTAL_BUILD_SCRIPT_PATH since all nodes will use it
- stash name: 'incremental_build_script',
- includes: INCREMENTAL_BUILD_SCRIPT_PATH
- }
- }
- }
- }
- if (env.BUILD_NUMBER == '1' && !IsPullRequest(branchName)) {
- // Exit pipeline early on the intial build. This allows Jenkins to load the pipeline for the branch and enables users
- // to select build parameters on their first actual build. See https://issues.jenkins.io/browse/JENKINS-41929
- currentBuild.result = 'SUCCESS'
- return
- }
- def someBuildHappened = false
- // Build and Post-Build Testing Stage
- def buildConfigs = [:]
- // Platform Builds run on EC2
- pipelineConfig.platforms.each { platform ->
- platform.value.build_types.each { build_job ->
- if (IsJobEnabled(branchName, build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline
- def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
- envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this
- envVars['CMAKE_LY_PROJECTS'] = "../${PROJECT_REPOSITORY_NAME}/${PROJECT_PATH}"
- def nodeLabel = envVars['NODE_LABEL']
- someBuildHappened = true
- buildConfigs["${platform.key} [${build_job.key}]"] = CreateBuildJobs(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName)
- }
- }
- }
- timestamps {
- stage('Build') {
- parallel buildConfigs // Run parallel builds
- }
- echo 'All builds successful'
- }
- if (!someBuildHappened) {
- currentBuild.result = 'NOT_BUILT'
- }
- }
- catch(Exception e) {
- error "Exception: ${e}"
- }
- finally {
- try {
- node('controller') {
- if("${currentBuild.currentResult}" == "SUCCESS") {
- buildFailure = ""
- emailBody = "${BUILD_URL}\nSuccess!"
- } else {
- buildFailure = tm('${BUILD_FAILURE_ANALYZER}')
- emailBody = "${BUILD_URL}\n${buildFailure}!"
- }
- if(env.POST_AR_BUILD_SNS_TOPIC) {
- message_json = [
- "build_url": env.BUILD_URL,
- "build_number": env.BUILD_NUMBER,
- "repository_name": env.REPOSITORY_NAME,
- "branch_name": env.BRANCH_NAME,
- "build_result": "${currentBuild.currentResult}",
- "build_failure": buildFailure,
- "recreate_volume": env.RECREATE_VOLUME,
- "clean_output_directory": env.CLEAN_OUTPUT_DIRECTORY,
- "clean_assets": env.CLEAN_ASSETS
- ]
- snsPublish(
- topicArn: env.POST_AR_BUILD_SNS_TOPIC,
- subject:'Build Result',
- message:JsonOutput.toJson(message_json)
- )
- }
- emailext (
- body: "${emailBody}",
- subject: "${currentBuild.currentResult}: ${JOB_NAME} - Build # ${BUILD_NUMBER}",
- recipientProviders: [
- [$class: 'RequesterRecipientProvider']
- ]
- )
- }
- } catch(Exception e) {
- }
- }
|