#!groovy //=========================================================================================================== // Main loop of the compilation //=========================================================================================================== node ('master'){ def err = null def log_needed = false Settings = null stage_name = '' gitRefOldValue = '' gitRefNewValue = '' builddir = pwd tmp: true srcdir = pwd tmp: false currentBuild.result = "SUCCESS" try { //Wrap build to add timestamp to command line wrap([$class: 'TimestamperBuildWrapper']) { notify_server(0) Settings = prepare_build() clean() checkout() notify_server(0) build() test() benchmark() build_doc() publish() notify_server(45) } } //If an exception is caught we need to change the status and remember to //attach the build log to the email catch (Exception caughtError) { //rethrow error later err = caughtError echo err.toString() //An error has occured, the build log is relevent log_needed = true //Store the result of the build log currentBuild.result = "${stage_name} FAILURE".trim() } finally { //Send email with final results if this is not a full build if( Settings && !Settings.Silent ) { echo 'Notifying users of result' email(currentBuild.result, log_needed, bIsSandbox) } echo 'Build Completed' /* Must re-throw exception to propagate error */ if (err) { throw err } } } //=========================================================================================================== // Helper classes/variables/routines //=========================================================================================================== class BuildSettings implements Serializable { public final CC_Desc Compiler public final Arch_Desc Architecture public final Boolean RunAllTests public final Boolean RunBenchmark public final Boolean BuildDocumentation public final Boolean Publish public final Boolean Silent public final Boolean IsSandbox public final String Branch public final String Commit public final String PrevCommit public final String RepoUrl public final String DescLong public final String DescShort BuildSettings(java.util.Collections$UnmodifiableMap param, java.util.TreeMap scmVars) { echo "${env}" this.Compiler = compiler_from_params( params.Compiler ) this.Architecture = architecture_from_params( params.Architecture ) this.RunAllTests = params.RunAllTests this.RunBenchmark = params.RunBenchmark this.BuildDocumentation = params.BuildDocumentation this.Publish = params.Publish this.Silent = params.Silent this.IsSandbox = scmVars.GIT_BRANCH == "jenkins-sandbox" this.Branch = scmVars.GIT_BRANCH this.Commit = scmVars.GIT_COMMIT this.PrevCommit = scmVars.GIT_PREVIOUS_COMMIT this.RepoUrl = scmVars.GIT_URL def full = params.RunAllTests ? " (Full)" : "" this.DescShort = "${ this.Compiler.cc_name }:${ this.Architecture.name }${full}" this.DescLong """Compiler : ${ this.Compiler.cc_name } (${ this.Compiler.cpp_cc }/${ this.Compiler.cfa_cc }) Architecture : ${ this.Architecture.name } Arc Flags : ${ this.Architecture.flags } Run All Tests : ${ this.RunAllTests.toString() } Run Benchmark : ${ this.RunBenchmark.toString() } Build Documentation : ${ this.BuildDocumentation.toString() } Publish : ${ this.Publish.toString() } Silent : ${ this.Silent.toString() } """ } } def prepare_build() { // prepare the properties properties ([ \ [$class: 'ParametersDefinitionProperty', \ parameterDefinitions: [ \ [$class: 'ChoiceParameterDefinition', \ description: 'Which compiler to use', \ name: 'Compiler', \ choices: 'gcc-6\ngcc-5\ngcc-4.9\nclang', \ defaultValue: 'gcc-6', \ ], \ [$class: 'ChoiceParameterDefinition', \ description: 'The target architecture', \ name: 'Architecture', \ choices: 'x64\nx86', \ defaultValue: 'x64', \ ], \ [$class: 'BooleanParameterDefinition', \ description: 'If false, only the quick test suite is ran', \ name: 'RunAllTests', \ defaultValue: false, \ ], \ [$class: 'BooleanParameterDefinition', \ description: 'If true, jenkins also runs benchmarks', \ name: 'RunBenchmark', \ defaultValue: false, \ ], \ [$class: 'BooleanParameterDefinition', \ description: 'If true, jenkins also builds documentation', \ name: 'BuildDocumentation', \ defaultValue: true, \ ], \ [$class: 'BooleanParameterDefinition', \ description: 'If true, jenkins also publishes results', \ name: 'Publish', \ defaultValue: false, \ ], \ [$class: 'BooleanParameterDefinition', \ description: 'If true, jenkins will not send emails', \ name: 'Silent', \ defaultValue: false, \ ], \ ], ]]) // Collect git information final scmVars = checkout scm final settings = new BuildSettings(params, scmVars) currentBuild.description = settings.DescShort echo settings.DescLong return settings } def build_stage(String name, Closure block ) { stage_name = name stage(name, block) } def notify_server(int wait) { sh """curl --silent --show-error --data "wait=${wait}" -X POST https://cforall.uwaterloo.ca:8082/jenkins/notify > /dev/null || true""" return } def make_doc() { def err = null try { sh 'make clean > /dev/null' sh 'make > /dev/null 2>&1' } catch (Exception caughtError) { err = caughtError //rethrow error later sh 'cat *.log' } finally { if (err) throw err // Must re-throw exception to propagate error } } //Description of a compiler (Must be serializable since pipelines are persistent) class CC_Desc implements Serializable { public String cc_name public String cpp_cc public String cfa_cc CC_Desc(String cc_name, String cpp_cc, String cfa_cc) { this.cc_name = cc_name this.cpp_cc = cpp_cc this.cfa_cc = cfa_cc } } def compiler_from_params(cc) { switch( cc ) { case 'gcc-6': return new CC_Desc('gcc-6', 'g++-6', 'gcc-6') break case 'gcc-5': return new CC_Desc('gcc-5', 'g++-5', 'gcc-5') break case 'gcc-4.9': return new CC_Desc('gcc-4.9', 'g++-4.9', 'gcc-4.9') break case 'clang': return new CC_Desc('clang', 'clang++', 'gcc-6') break default : error "Unhandled compiler : ${cc}" } } //Description of an architecture (Must be serializable since pipelines are persistent) class Arch_Desc implements Serializable { public String name public String flags Arch_Desc(String name, String flags) { this.name = name this.flags = flags } } def architecture_from_params( arch ) { switch( arch ) { case 'x64': return new Arch_Desc('x64', '--host=x86_64') break case 'x86': return new Arch_Desc('x86', '--host=i386') break default : error "Unhandled architecture : ${arch}" } } //=========================================================================================================== // Main compilation routines //=========================================================================================================== def clean() { build_stage('Cleanup') { // clean the build by wipping the build directory dir(builddir) { deleteDir() } //Clean all temporary files to make sure no artifacts of the previous build remain sh 'git clean -fdqx' //Reset the git repo so no local changes persist sh 'git reset --hard' } } //Compilation script is done here but environnement set-up and error handling is done in main loop def checkout() { build_stage('Checkout') { //checkout the source code and clean the repo checkout scm } } def build() { build_stage('Build') { // Build outside of the src tree to ease cleaning dir (builddir) { //Configure the conpilation (Output is not relevant) //Use the current directory as the installation target so nothing escapes the sandbox //Also specify the compiler by hand targets="" if( Settings.RunAllTests ) { targets="--with-target-hosts='host:debug,host:nodebug'" } else { targets="--with-target-hosts='host:debug'" } sh "${srcdir}/configure CXX=${Settings.Compiler.cpp_cc} ${Settings.Architecture.flags} ${targets} --with-backend-compiler=${Settings.Compiler.cfa_cc} --quiet" //Compile the project sh 'make -j 8 --no-print-directory' } } } def test() { build_stage('Test') { dir (builddir) { //Run the tests from the tests directory if ( Settings.RunAllTests ) { sh 'make --no-print-directory -C tests all-tests debug=yes' sh 'make --no-print-directory -C tests all-tests debug=no ' } else { sh 'make --no-print-directory -C tests' } } } } def benchmark() { build_stage('Benchmark') { if( !Settings.RunBenchmark ) return dir (builddir) { //Append bench results sh "make --no-print-directory -C benchmark jenkins githash=${gitRefNewValue} arch=${Settings.Architecture} | tee ${srcdir}/bench.json" } } } def build_doc() { build_stage('Documentation') { if( !Settings.BuildDocumentation ) return dir ('doc/user') { make_doc() } dir ('doc/refrat') { make_doc() } } } def publish() { build_stage('Publish') { if( !Settings.Publish ) return //Then publish the results sh 'curl --silent --show-error -H \'Content-Type: application/json\' --data @bench.json https://cforall.uwaterloo.ca:8082/jenkins/publish > /dev/null || true' } } //=========================================================================================================== //Routine responsible of sending the email notification once the build is completed //=========================================================================================================== def gitBranchUpdate(String gitRefOldValue, String gitRefNewValue) { def update = "" sh "git rev-list ${gitRefOldValue}..${gitRefNewValue} > GIT_LOG"; readFile('GIT_LOG').eachLine { rev -> sh "git cat-file -t ${rev} > GIT_TYPE" def type = readFile('GIT_TYPE') update += " via ${rev} (${type})\n" } def rev = gitRefOldValue sh "git cat-file -t ${rev} > GIT_TYPE" def type = readFile('GIT_TYPE') update += " from ${rev} (${type})\n" return update def output=readFile('result').trim() echo "output=$output"; } //Standard build email notification def email(String status, boolean log, boolean bIsSandbox) { //Since tokenizer doesn't work, figure stuff out from the environnement variables and command line //Configurations for email format def project_name = (env.JOB_NAME =~ /(.+)\/.+/)[0][1].toLowerCase() def gitLog = 'Error retrieving git logs' def gitDiff = 'Error retrieving git diff' def gitUpdate = 'Error retrieving update' try { gitUpdate = gitBranchUpdate(Settings.PrevCommit, Settings.Commit) sh "git rev-list --format=short ${Settings.PrevCommit}...${Settings.Commit} > GIT_LOG" gitLog = readFile('GIT_LOG') sh "git diff --stat ${Settings.Commit} ${Settings.PrevCommit} > GIT_DIFF" gitDiff = readFile('GIT_DIFF') } catch (Exception error) { echo error.toString() echo error.getMessage() } def email_subject = "[${project_name} git][BUILD# ${env.BUILD_NUMBER} - ${status}] - branch ${env.BRANCH_NAME}" def email_body = """This is an automated email from the Jenkins build machine. It was generated because of a git hooks/post-receive script following a ref change was pushed to the repository containing the project "UNNAMED PROJECT". The branch ${env.BRANCH_NAME} has been updated. ${gitUpdate} Check console output at ${env.BUILD_URL} to view the results. - Status -------------------------------------------------------------- BUILD# ${env.BUILD_NUMBER} - ${status} - Log ----------------------------------------------------------------- ${gitLog} ----------------------------------------------------------------------- Summary of changes: ${gitDiff} """ def email_to = "cforall@lists.uwaterloo.ca" if( !bIsSandbox ) { //send email notification emailext body: email_body, subject: email_subject, to: email_to, attachLog: log } else { echo "Would send email to: ${email_to}" echo "With title: ${email_subject}" echo "Content: \n${email_body}" } }