/*
 * 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-2022 RichardG.
 */

/* ['main builds', 'branch builds'] */
def repository = ['https://github.com/PCBox/PCBox.git', scm.userRemoteConfigs[0].url]
def commitBrowser = ['https://github.com/PCBox/PCBox/commit/%s', null]
def branch = ['pcbox', scm.branches[0].name]
def buildType = ['beta', 'alpha']
def tarballFlags = ['', '-t']
def buildBranch = env.JOB_BASE_NAME.contains('-') ? 1 : 0

def osArchs = [
	'Windows': ['64'],
]

def osFlags = [
	'Windows': '-D QT=ON',
]

def archNames = [
	'32': 'x86 (32-bit)',
	'x86': 'x86 (32-bit)',
	'64': 'x64 (64-bit)',
	'x86_64': 'x64 (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'],
	'arm64': ['NDR'],
	'x86_64+arm64': ['ODR', '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'
]

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

def gitClone(repository, branch) {
	/* Clean workspace. */
	cleanWs()

	/* Perform git clone if stashed data isn't available yet, or if
	   this is not debian.citadel where stash is faster than clone. */
	if (env.GIT_STASHED != 'true' || env.NODE_NAME != 'debian.citadel') {
		/* Catch network issues in clone. */
		try {
			/* Perform clone/checkout, making sure to set poll and changelog only
			   once to avoid interference from new commits pushed inbetween clones. */
			def scmVars = checkout(poll: env.GIT_STASHED != 'true',
					       changelog: env.GIT_STASHED != '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 if required, marking it as stashed. */
			if (env.GIT_STASHED != 'true') {
				stash(name: 'git', useDefaultExcludes: false)
				env.GIT_STASHED = 'true'
			}

			/* No need to use stashed data. */
			return;
		} catch (e) {
			/* If clone fails, use stashed data if available, or re-throw exception otherwise. */
			if (env.GIT_STASHED != 'true')
				throw e;
		}
	}
	
	/* 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

	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 {
					/* 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"
					}

					/* Build here to avoid creating a redundant parent stage on the stage view. */
					osArchs.each { os, thisOsArchs ->
						def combinations = [:]
						thisOsArchs.each { arch ->
							def archSlug = arch.replace('+x86_64h', '') /* all instances of arch except the one passed to -b */
							def thisArchDynarecs = dynarecArchs[archSlug.toLowerCase()]
							if (!thisArchDynarecs)
								thisArchDynarecs = ['NoDR']
							thisArchDynarecs.each { dynarec ->
								presets.each { preset ->
									def combination = "$os $archSlug $dynarec $preset"
									combinations[combination] = {
										catchError(buildResult: 'FAILURE', stageResult: 'SUCCESS') {
											retry(10) {
												node('built-in') {
													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-$archSlug$buildSuffix"
															def ret = -1
															def archName = archNames[archSlug]
															dir(dynarecNames[dynarec]) {
																dir("$os - $archName") {
																	ret = runBuild("-b \"$packageName\" \"$arch\" ${presetFlags[preset]} ${dynarecFlags[dynarec]} ${osFlags[os]} $buildFlags")
																	if (presets.size() == 1)
																		writeFile file: '.forcedir', text: ''
																}
																if ((osArchs.size() == 1) && (thisOsArchs.size() == 1))
																	writeFile file: '.forcedir', text: ''
															}

															if (ret == 0) {
																/* Archive resulting artifacts. */
																archiveArtifacts artifacts: "**/$packageName*, **/.forcedir", defaultExcludes: false
															} else {
																/* Fail this stage. */
																failStage()
															}
														}

														/* Clean up. */
														removeDir("${env.WORKSPACE_TMP}/output")
													}
												}
											}
										}
									}
								}
							}
						}
						parallel combinations
					}
				}
			}
		}
	}
}
