/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Jenkins build pipeline definition. * * * * Authors: RichardG, * * Copyright 2021-2022 RichardG. */ /* ['main builds', 'branch builds'] */ def repository = ['https://github.com/86Box/86Box.git', 'https://github.com/richardg867/86Box.git'] def commitBrowser = ['https://github.com/86Box/86Box/commit/%s', null] def branch = ['master', 'cleanup30'] def buildType = ['beta', 'alpha'] def buildBranch = env.JOB_BASE_NAME.contains('-') ? 1 : 0 def osArchs = [ 'Windows': ['32', '64'], 'Linux': ['x86', 'x86_64', 'arm32', 'arm64'] ] def osFlags = [ 'Windows': '-D QT=ON', 'Linux': '-D QT=ON' ] def archNames = [ '32': 'x86 (32-bit)', 'x86': 'x86 (32-bit)', '64': 'x64 (64-bit)', 'x86_64': 'x64 (64-bit)', 'arm32': 'ARM (32-bit)', 'arm64': 'ARM (64-bit)' ] def dynarecNames = [ 'ODR': 'Old Recompiler (recommended)', 'NDR': 'New Recompiler (beta)', 'NoDR': 'No Dynamic Recompiler' ] def dynarecArchs = [ '32': ['ODR', 'NDR'], 'x86': ['ODR', 'NDR'], '64': ['ODR', 'NDR'], 'x86_64': ['ODR', 'NDR'], 'arm32': ['NDR'], 'ARM32': ['NDR'], 'arm64': ['NDR'], 'ARM64': ['NDR'] ] def dynarecFlags = [ 'ODR': '-D NEW_DYNAREC=OFF', 'NDR': '-D NEW_DYNAREC=ON', 'NoDR': '-D DYNAREC=OFF' ] def dynarecSlugs = [ 'ODR': '', 'NDR': '-NDR', 'NoDR': '' ] def presets = [ 'Regular', 'Debug' ] def presetSlugs = [ 'Regular': '', 'Debug': '-Debug', 'Dev': '-Dev' ] def presetFlags = [ 'Regular': '-t --preset=regular -D CMAKE_BUILD_TYPE=Release', 'Debug': '--preset=debug -D CMAKE_BUILD_TYPE=Debug', 'Dev': '--preset=experimental -D CMAKE_BUILD_TYPE=Debug -D VNC=OFF' ] def gitClone(repository, branch) { /* Clean workspace. */ cleanWs() /* Use stashes to avoid performing multiple clones. */ if (env.GIT_STASHED != 'true') { /* Perform clone/checkout. */ def scmVars = checkout poll: true, changelog: true, scm: [$class: 'GitSCM', branches: [[name: branch]], userRemoteConfigs: [[url: repository]]] if (env.GIT_COMMIT == null) { /* Save the current HEAD commit. */ env.GIT_COMMIT = scmVars.GIT_COMMIT } else if (env.GIT_COMMIT != scmVars.GIT_COMMIT) { /* Checkout the commit read from the polling log. */ if (isUnix()) sh returnStatus: true, script: "git checkout ${env.GIT_COMMIT}" else bat returnStatus: true, script: "git checkout ${env.GIT_COMMIT}" } println "[-] Using git commit [${env.GIT_COMMIT}]" /* Stash data and mark it as stashed. */ stash name: 'git', useDefaultExcludes: false env.GIT_STASHED = 'true' } else { /* Unstash data. */ unstash name: 'git' } } def removeDir(dir) { if (isUnix()) return sh(returnStatus: true, script: "rm -rf \"$dir\"") else return bat(returnStatus: true, script: "rd /s /q \"$dir\"") } def runBuild(args) { if (isUnix()) return sh(returnStatus: true, script: "chmod u+x \"$WORKSPACE/.ci/build.sh\" && exec \"$WORKSPACE/.ci/build.sh\" $args") else return bat(returnStatus: true, script: "C:\\msys64\\msys2_shell.cmd -msys2 -defterm -here -no-start -c 'exec \"\$(cygpath -u \\'%WORKSPACE%\\')/.ci/build.sh\" $args'") } def failStage() { /* Force this stage to fail. */ catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { def x = 1 / 0 } } pipeline { agent none environment { DISCORD_WEBHOOK_URL = credentials('discord-webhook-url') } options { quietPeriod(0) } parameters { string(name: 'BUILD_TYPE', defaultValue: buildType[buildBranch], description: "Build type to pass on to CMake (on main builds) or feature branch identifier (on branch builds).") } stages { stage('Source Tarball') { agent none failFast false steps { script { /* Extract the polled commit from the polling log, so that git checkout can be used to avoid JENKINS-20518 race conditions caused by two pushes too close together. */ node('master') { /* must run on master node to read polling log */ /* Ignore exceptions as this is not really critical. */ try { /* Switch to this build's directory. */ dir("${env.JENKINS_HOME}/jobs/${env.JOB_NAME}/builds/${env.BUILD_NUMBER}") { /* Parse polling log. */ def pollingLog = readFile file: 'polling.log' def match = pollingLog =~ /Latest remote head revision on [^ ]+ is: ([a-zA-Z0-9]+)/ if (match && match[0]) { env.GIT_COMMIT = match[0][1] println "[-] Read git commit [${env.GIT_COMMIT}] from polling log" } } } catch (e) {} } /* Adding to the above, run a git clone as soon as possible on any node to further avoid race conditions caused by busy node executor delays. */ retry(10) { node('citadel && !Windows') { /* Run git clone. */ gitClone(repository[buildBranch], branch[buildBranch]) /* Clean workspace, in case this is running in a non-build node. */ cleanWs() } } /* Determine build metadata. */ def buildFlags = "-D \"BUILD_TYPE=$BUILD_TYPE\" -D \"EMU_BUILD=build ${env.BUILD_NUMBER}\" -D \"EMU_BUILD_NUM=${env.BUILD_NUMBER}\"" def buildSuffix = "-b${env.BUILD_NUMBER}" if (buildBranch > 0) { def date = new Date().format("yyyyMMdd") buildFlags = "-D \"BUILD_TYPE=${buildType[buildBranch]}\" -D \"EMU_BUILD=${env.JOB_BASE_NAME.split('-')[1]} build $date.$BUILD_TYPE\"" buildSuffix = "-$date-$BUILD_TYPE" } /* Create source tarball. */ try { retry(10) { node('Linux') { /* Run git clone. */ gitClone(repository[buildBranch], branch[buildBranch]) /* Switch to temp directory. */ dir("${env.WORKSPACE_TMP}/output") { /* Run source tarball creation process. */ def packageName = "${env.JOB_BASE_NAME}-Source$buildSuffix" if (runBuild("-s \"$packageName\"") == 0) { /* Archive resulting artifacts. */ archiveArtifacts artifacts: "$packageName*" } else { /* Fail this stage. */ failStage() } } /* Clean up. */ removeDir("${env.WORKSPACE_TMP}/output") } } } catch (e) { /* Fail this stage. */ failStage() } /* Build here to avoid creating a redundant parent stage on the stage view. */ osArchs.each { os, thisOsArchs -> def combinations = [:] thisOsArchs.each { arch -> def thisArchDynarecs = dynarecArchs[arch] if (!thisArchDynarecs) thisArchDynarecs = ['NoDR'] thisArchDynarecs.each { dynarec -> presets.each { preset -> def combination = "$os $arch $dynarec $preset" combinations[combination] = { catchError(buildResult: 'FAILURE', stageResult: 'SUCCESS') { retry(10) { node(os) { stage(combination) { /* Run git clone. */ gitClone(repository[buildBranch], branch[buildBranch]) /* Switch to output directory. */ dir("${env.WORKSPACE_TMP}/output") { /* Run build process. */ def packageName = "${env.JOB_BASE_NAME}${dynarecSlugs[dynarec]}${presetSlugs[preset]}-$os-$arch$buildSuffix" def ret = -1 dir("${dynarecNames[dynarec]}/$os - ${archNames[arch]}") { ret = runBuild("-b \"$packageName\" \"$arch\" ${presetFlags[preset]} ${dynarecFlags[dynarec]} ${osFlags[os]} $buildFlags") } if (ret == 0) { /* Archive resulting artifacts. */ archiveArtifacts artifacts: "**/**/$packageName*" } else { /* Fail this stage. */ failStage() } } /* Clean up. */ removeDir("${env.WORKSPACE_TMP}/output") } } } } } } } } parallel combinations } } } } } post { always { script { /* Send out build notifications. */ if (commitBrowser[buildBranch]) { try { /* Notify Discord. */ def result = currentBuild.currentResult.toLowerCase() discordSend webhookURL: DISCORD_WEBHOOK_URL, title: "${env.JOB_BASE_NAME} #${env.BUILD_NUMBER}", link: env.BUILD_URL, result: currentBuild.currentResult, description: "**Status:** ${result}\n\u2060", /* word joiner character forces a blank line */ enableArtifactsList: false, showChangeset: true, scmWebUrl: commitBrowser[buildBranch] /* Notify IRC, which needs a node for whatever reason. */ node('citadel') { ircNotify() } } catch (e) { /* Force this stage to fail. */ catchError(buildResult: currentBuild.result, stageResult: 'FAILURE') { throw e; } } } } } } }