Index: Jenkinsfile
===================================================================
--- Jenkinsfile	(revision 30e32b2b00dfeda222cacf523a031087c59f8fe0)
+++ Jenkinsfile	(revision 30e32b2b00dfeda222cacf523a031087c59f8fe0)
@@ -0,0 +1,418 @@
+#!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()
+
+			node(Settings.Architecture.node) {
+				BuildDir  = pwd tmp: true
+				SrcDir    = pwd tmp: false
+
+				clean()
+
+				checkout()
+
+				notify_server(0)
+
+				build()
+
+				test()
+
+				benchmark()
+
+				build_doc()
+
+				publish()
+			}
+
+			// Update the build directories when exiting the node
+			BuildDir  = pwd tmp: true
+			SrcDir    = pwd tmp: false
+
+			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 ) {
+			email(log_needed, Settings.IsSandbox)
+		}
+
+		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
+		final scmVars = checkout scm
+		Settings.GitNewRef = scmVars.GIT_COMMIT
+		Settings.GitOldRef = scmVars.GIT_PREVIOUS_COMMIT
+
+		echo GitLogMessage()
+	}
+}
+
+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.CXX} CC=${Settings.Compiler.CC} ${Settings.Architecture.flags} ${targets} --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 timeouts="--timeout=600" all-tests debug=yes'
+				sh 'make --no-print-directory -C tests timeouts="--timeout=600" 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 "${SrcDir}/benchmark/jenkins.sh ${Settings.GitNewRef} ${Settings.Architecture} ${BuildDir}/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 @${BuildDir}/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 GitLogMessage() {
+	if (!Settings || !Settings.GitOldRef || !Settings.GitNewRef) return "\nERROR retrieveing git information!\n"
+
+	sh "${SrcDir}/tools/PrettyGitLogs.sh ${SrcDir} ${BuildDir} ${Settings.GitOldRef} ${Settings.GitNewRef}"
+
+	def gitUpdate = readFile("${BuildDir}/GIT_UPDATE")
+	def gitLog    = readFile("${BuildDir}/GIT_LOG")
+	def gitDiff   = readFile("${BuildDir}/GIT_DIFF")
+
+	return """
+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} - ${currentBuild.result}
+
+- Log -----------------------------------------------------------------
+${gitLog}
+-----------------------------------------------------------------------
+Summary of changes:
+${gitDiff}
+"""
+}
+
+//Standard build email notification
+def email(boolean log, boolean bIsSandbox) {
+	//Since tokenizer doesn't work, figure stuff out from the environnement variables and command line
+	//Configurations for email format
+	echo 'Notifying users of result'
+
+	def project_name = (env.JOB_NAME =~ /(.+)\/.+/)[0][1].toLowerCase()
+	def email_subject = "[${project_name} git][BUILD# ${env.BUILD_NUMBER} - ${currentBuild.result}] - 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 which was pushed to the Cforall repository.
+""" + GitLogMessage()
+
+	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 name
+	public String CXX
+	public String CC
+
+	CC_Desc(String name, String CXX, String CC) {
+		this.name = name
+		this.CXX = CXX
+		this.CC = CC
+	}
+}
+
+//Description of an architecture (Must be serializable since pipelines are persistent)
+class Arch_Desc implements Serializable {
+	public String name
+	public String flags
+	public String node
+
+	Arch_Desc(String name, String flags, String node) {
+		this.name  = name
+		this.flags = flags
+		this.node  = node
+	}
+}
+
+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
+
+	public String GitNewRef
+	public String GitOldRef
+
+	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', 'x64')
+			break
+			case 'x86':
+				this.Architecture = new Arch_Desc('x86', '--host=i386', 'x86')
+			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.name }:${ this.Architecture.name }${full}"
+
+		this.DescLong = """Compiler 	         : ${ this.Compiler.name } (${ this.Compiler.CXX }/${ this.Compiler.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() }
+"""
+
+		this.GitNewRef = ''
+		this.GitOldRef = ''
+	}
+}
+
+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,  								\
+				],												\
+			],
+		]])
+
+	// It's unfortunate but it looks like we need to checkout the entire repo just to get the pretty git printer
+	checkout scm
+
+	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 build/*.log'
+	}
+	finally {
+		if (err) throw err // Must re-throw exception to propagate error
+	}
+}
