/*
 * 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, <richardg867@gmail.com>
 *
 *		Copyright 2021 RichardG.
 */

def repository = 'https://github.com/86Box/86Box.git'
def branch = 'master'

def osArchs = [
	'Windows': ['32', '64'],
	'Linux': ['x86', 'x86_64', 'arm32', 'arm64']
]

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',
	'Debug': '--preset=debug',
	'Dev': '-t --preset=experimental -D VNC=OFF'
]

def anyFailure = false

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 "git checkout ${env.GIT_COMMIT} || exit 0"
			else
				bat "git checkout ${env.GIT_COMMIT} || exit /b 0"
		}
		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())
		sh "rm -rf \"$dir\" || exit 0"
	else
		bat "if exist \"$dir\" rd /s /q \"$dir\" & exit /b 0"
}

def runBuild(args) {
	if (isUnix())
		sh "chmod u+x \"$WORKSPACE/.ci/build.sh\" && exec \"$WORKSPACE/.ci/build.sh\" $args"
	else
		bat "C:\\msys64\\msys2_shell.cmd -msys2 -defterm -here -no-start -c 'exec \"\$(cygpath -u \\'%WORKSPACE%\\')/.ci/build.sh\" $args'"
}

pipeline {
	agent none

	environment {
		DISCORD_WEBHOOK_URL = credentials('discord-webhook-url')
	}

	options {
		disableConcurrentBuilds()
		quietPeriod(0)
	}

	parameters {
		string(name: 'BUILD_TYPE',
		       defaultValue: 'beta', /* !!! CHANGE HERE !!! for build type */
		       description: "Build type to pass on to CMake. Don't change this, you should instead change the default value on .ci/Jenkinsfile")
	}

	stages {
		stage('Source Tarball') {
			agent none

			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. */
					node {
						/* Run git clone. */
						gitClone(repository, branch)

						/* Clean workspace, in case this is running in a non-build node. */
						cleanWs()
					}

					/* Create source tarball. */
					node('Linux') {
						try {
							/* Run git clone. */
							gitClone(repository, branch)

							/* Switch to temp directory. */
							dir("${env.WORKSPACE_TMP}/output") {
								/* Run source tarball creation process. */
								def packageName = "${env.JOB_BASE_NAME}-Source-b${env.BUILD_NUMBER}"
								runBuild("-s \"$packageName\"")

								/* Archive resulting artifacts. */
								archiveArtifacts artifacts: "$packageName*"
							}
						} catch (e) {
							/* Mark that a failure occurred. */
							anyFailure = true

							/* Force this stage to fail. */
							catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
								throw e;
							}
						}
					}

					/* Build here to avoid creating a redundant parent stage on the stage view. */
					osArchs.each { os, thisOsArchs ->
						thisOsArchs.each { arch ->
							def thisArchDynarecs = dynarecArchs[arch]
							if (!thisArchDynarecs)
								thisArchDynarecs = ['NoDR']
							thisArchDynarecs.each { dynarec ->
								presets.each { preset ->
									node(os) {
										stage("$os $arch $dynarec $preset") {
											try {
												/* Run git clone. */
												gitClone(repository, branch)

												/* Switch to output directory. */
												dir("${env.WORKSPACE_TMP}/output") {
													/* Run build process. */
													def packageName = "${env.JOB_BASE_NAME}${dynarecSlugs[dynarec]}${presetSlugs[preset]}-$os-$arch-b${env.BUILD_NUMBER}"
													dir("${dynarecNames[dynarec]}/$os - ${archNames[arch]}") {
														runBuild("-b \"$packageName\" \"$arch\" ${presetFlags[preset]} ${dynarecFlags[dynarec]} -D \"BUILD_TYPE=$BUILD_TYPE\" -D \"EMU_BUILD=build ${env.BUILD_NUMBER}\" -D \"EMU_BUILD_NUM=${env.BUILD_NUMBER}\"")
													}

													/* Archive resulting artifacts. */
													archiveArtifacts artifacts: "**/**/$packageName*"
												}
											} catch (e) {
												/* Mark that a failure occurred. */
												anyFailure = true

												/* Force this stage to fail. */
												catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
													throw e;
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}

	post {
		always {
			script {
				/* Mark build as failed if any step has failed. */
				if (anyFailure) {
					println "[!] Failing build because a build stage failed"
					currentBuild.result = 'FAILURE'
				}

				/* Send out build notifications. */
				if (!env.JOB_BASE_NAME.contains('TestBuildPleaseIgnore')) {
					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

						/* Notify IRC, which needs a node for whatever reason. */
						node {
							ircNotify()
						}
					} catch (e) {
						/* Force this stage to fail. */
						catchError(buildResult: currentBuild.result, stageResult: 'FAILURE') {
							throw e;
						}
					}
				}
			}
		}
	}
}
