#!groovy //=========================================================================================================== // Main loop of the compilation //=========================================================================================================== node ('master'){ // Globals BuildDir = pwd tmp: true SrcDir = pwd tmp: false Settings = null StageName = '' // Local variables def err = null def log_needed = 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 = "${StageName} 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 } } } //=========================================================================================================== // Main compilation routines //=========================================================================================================== def clean() { build_stage('Cleanup') { // clean the build by wipping the build directory dir(BuildDir) { deleteDir() } } } //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 changeLogSets = currentBuild.changeSets for (int i = 0; i < changeLogSets.size(); i++) { def entries = changeLogSets[i].items for (int j = 0; j < entries.length; j++) { def entry = entries[j] echo "${entry.commitId} by ${entry.author} on ${new Date(entry.timestamp)}: ${entry.msg}" def files = new ArrayList(entry.affectedFiles) for (int k = 0; k < files.size(); k++) { def file = files[k] echo " ${file.editType.name} ${file.path}" } } } echo """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. Check console output at ${env.BUILD_URL} to view the results. - Status -------------------------------------------------------------- BUILD# ${env.BUILD_NUMBER} - ${status} - Log ----------------------------------------------------------------- ----------------------------------------------------------------------- Summary of changes: """ } } 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 { final scmVars = checkout(scm) gitUpdate = gitBranchUpdate(scmVars.GIT_PREVIOUS_COMMIT, scmVars.GIT_COMMIT) sh "git rev-list --format=short ${scmVars.GIT_PREVIOUS_COMMIT}...${scmVars.GIT_COMMIT} > ${BuildDir}/GIT_LOG" gitLog = readFile("${BuildDir}/GIT_LOG") sh "git diff --stat ${scmVars.GIT_COMMIT} ${scmVars.GIT_PREVIOUS_COMMIT} > ${BuildDir}/GIT_DIFF" gitDiff = readFile("${BuildDir}/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( Settings && !Settings.IsSandbox ) { //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}" } } //=========================================================================================================== // Helper classes/variables/routines //=========================================================================================================== //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 } } //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 } } 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 DescLong public final String DescShort BuildSettings(java.util.Collections$UnmodifiableMap param, String branch) { switch( param.Compiler ) { case 'gcc-6': this.Compiler = new CC_Desc('gcc-6', 'g++-6', 'gcc-6') break case 'gcc-5': this.Compiler = new CC_Desc('gcc-5', 'g++-5', 'gcc-5') break case 'gcc-4.9': this.Compiler = new CC_Desc('gcc-4.9', 'g++-4.9', 'gcc-4.9') break case 'clang': this.Compiler = new CC_Desc('clang', 'clang++', 'gcc-6') break default : error "Unhandled compiler : ${cc}" } switch( param.Architecture ) { case 'x64': this.Architecture = new Arch_Desc('x64', '--host=x86_64') break case 'x86': this.Architecture = new Arch_Desc('x86', '--host=i386') break default : error "Unhandled architecture : ${arch}" } this.RunAllTests = param.RunAllTests this.RunBenchmark = param.RunBenchmark this.BuildDocumentation = param.BuildDocumentation this.Publish = param.Publish this.Silent = param.Silent this.IsSandbox = (branch == "jenkins-sandbox") def full = param.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, \ ], \ ], ]]) final settings = new BuildSettings(params, env.BRANCH_NAME) currentBuild.description = settings.DescShort echo settings.DescLong return settings } def build_stage(String name, Closure block ) { StageName = name echo " -------- ${StageName} -------- " 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 } }