gs2compiler 0.1.0

Compile GS2 source code to bytecode
def notify(status){
	emailext (
		body: '$DEFAULT_CONTENT',
		recipientProviders: [
			[$class: 'CulpritsRecipientProvider'],
			[$class: 'DevelopersRecipientProvider'],
			[$class: 'RequesterRecipientProvider']
		],
		replyTo: '$DEFAULT_REPLYTO',
		subject: '$DEFAULT_SUBJECT',
		to: '$DEFAULT_RECIPIENTS'
	)
}

@NonCPS
def killall_jobs() {
	def jobname = env.JOB_NAME;
	def buildnum = env.BUILD_NUMBER.toInteger();
	def killnums = "";
	def job = Jenkins.instance.getItemByFullName(jobname);
	def split_job_name = env.JOB_NAME.split(/\/{1}/);
	def fixed_job_name = split_job_name[1].replace('%2F',' ');

	for (build in job.builds) {
		if (!build.isBuilding()) { continue; }
		if (buildnum == build.getNumber().toInteger()) { continue; println "equals"; }
		if (buildnum < build.getNumber().toInteger()) { continue; println "newer"; }

		echo("Kill task = ${build}");

		killnums += "#" + build.getNumber().toInteger() + ", ";

		build.doStop();
	}

	if (killnums != "") {
		discordSend description: "in favor of #${buildnum}, ignore following failed builds for ${killnums}", footer: "", link: env.BUILD_URL, result: "ABORTED", title: "[${split_job_name[0]}] Killing task(s) ${fixed_job_name} ${killnums}", webhookURL: env.GS2EMU_WEBHOOK
	}
	echo("Done killing");
}

def buildStep(dockerImage, generator, os, osdir, defines) {
	def split_job_name = env.JOB_NAME.split(/\/{1}/);
	def fixed_job_name = split_job_name[1].replace('%2F','-');
	fixed_job_name = fixed_job_name.replace('/','-');
    def fixed_os = os.replace(' ','-');

	try{
		stage("Building on \"${dockerImage}\" with \"${generator}\" for \"${os}\"...") {
			properties([pipelineTriggers([githubPush()])]);
			def commondir = env.WORKSPACE + '/../' + fixed_job_name + '/';

			docker.image("${dockerImage}").inside("") {

				checkout(scm);

				if (env.CHANGE_ID) {
					echo("Trying to build pull request");
				}

				if (!env.CHANGE_ID) {

				}

				sh("mkdir -p build/");
				sh("mkdir -p lib/");
				sh("rm -rfv build/*");
				sh("rm -rfv lib/*");

				discordSend(description: "", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Starting ${os} build target...", webhookURL: env.GS2EMU_WEBHOOK);

				dir("build") {
					sh("cmake -G\"${generator}\" -DCMAKE_BUILD_TYPE=Release ${defines} -DVER_EXTRA=\"-${fixed_os}-${fixed_job_name}\" .. || true"); // Temporary fix for Windows MingW builds
					sh("cmake --build . --config Release --target all -- -j `nproc`");
				}

				archiveArtifacts(artifacts: 'lib/*.dylib,lib/*.so,bin/*.dll', allowEmptyArchive: true);
				stash(name: "dynamic-${osdir}", includes: 'lib/*.dylib,lib/*.so,bin/*.dll', allowEmpty: true);

				discordSend(description: "", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build ${fixed_job_name} #${env.BUILD_NUMBER} Target: ${os} successful!", webhookURL: env.GS2EMU_WEBHOOK);

				sh("rm -rfv build/*");

				dir("build") {
					sh("cmake -G\"${generator}\" -DCMAKE_BUILD_TYPE=Release -DSTATIC=ON ${defines} -DVER_EXTRA=\"-${fixed_os}-${fixed_job_name}\" .. || true");
					sh("cmake --build . --config Release --target all -- -j `nproc`");
				}

				// Move static libraries to lib/
				sh("mv build/*.a lib/ || true");
				sh("mv build/*.lib lib/ || true");

				// Archive static libraries
				stash(name: "static-${osdir}", includes: 'lib/*.a,lib/*.lib', allowEmpty: true);

				archiveArtifacts(artifacts: 'lib/*.a,lib/*.lib', allowEmptyArchive: true);
				discordSend(description: "", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build ${fixed_job_name} #${env.BUILD_NUMBER} Target: [static] ${os} successful!", webhookURL: env.GS2EMU_WEBHOOK);
			}
		}
	} catch(err) {
		discordSend(description: "", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK);
		currentBuild.result = 'FAILURE';
		notify('Build failed');
		throw err;
	}
}

// publish rust package using PREAGONAL_RUST_TOKEN. we should use a container with cargo installed
def publishRust() {
	def split_job_name = env.JOB_NAME.split(/\/{1}/);
	def fixed_job_name = split_job_name[1].replace('%2F',' ');

	try {
		def customImage = docker.image("rust:latest");
		customImage.pull();

		stage("Publishing Rust Package") {
			customImage.inside("-u 0") {
				// TODO: Fix this
				sh("apt-get update && apt-get install -y cmake && apt-get install -y bison && apt-get install -y flex");
				withCredentials([string(credentialsId: 'PREAGONAL_RUST_TOKEN', variable: 'CARGO_REGISTRY_TOKEN')]) {
					sh("echo ${env.CARGO_REGISTRY_TOKEN} | cargo login");

					// TODO: Fix this
					sh("cargo publish --no-verify");
				}
			}
		}
	} catch(err) {
		currentBuild.result = 'FAILURE';
		discordSend description: "", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Rust Publish Failed: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK;

		notify("Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER}");
		throw err;
	}
}

def buildStepDocker() {
	def split_job_name = env.JOB_NAME.split(/\/{1}/);
	def fixed_job_name = split_job_name[1].replace('%2F',' ');

	def customImage = docker.image("mcr.microsoft.com/dotnet/sdk:8.0");
	customImage.pull();

	try {
        def buildenv = "";
        def tag = '';
        def VER = '';
        def EXTRA_VER = '';


        if(env.TAG_NAME) {
            sh(returnStdout: true, script: "echo '```' > RELEASE_DESCRIPTION.txt");
            env.RELEASE_DESCRIPTION = sh(returnStdout: true, script: "git tag -l --format='%(contents)' ${env.TAG_NAME} >> RELEASE_DESCRIPTION.txt");
            sh(returnStdout: true, script: "echo '```' >> RELEASE_DESCRIPTION.txt");
        }

        if (env.BRANCH_NAME.equals('main')) {
            tag = "latest";
        } else {
            tag = "${env.BRANCH_NAME.replace('/','-')}";
        }

        if (env.TAG_NAME) {
            EXTRA_VER = "";
            VER = "/p:Version=${env.TAG_NAME}";
        } else if (env.BRANCH_NAME.equals('dev')) {
            EXTRA_VER = "-beta";
        } else {
            EXTRA_VER = "--build-arg VER_EXTRA=-${tag}";
        }

        docker.withRegistry("https://index.docker.io/v1/", "dockergraal") {
            def release_name = env.JOB_NAME.replace('%2F','/');
            def release_type = ("${release_name}").replace('/','-').replace('GS2Compiler-','').replace('main','').replace('dev','');

            stage("Building NuGet Package") {

                customImage.inside("-u 0") {
                    dir("bindings/dotnet/") {
                        sh("chmod 777 -R .");
                        sh("dotnet pack GS2Compiler.csproj -c Release ${VER}");
                        sh("chmod 777 -R .");
                    }
                }
            }

            def archive_date = sh (
                script: 'date +"-%Y%m%d-%H%M"',
                returnStdout: true
            ).trim();

            if (env.TAG_NAME) {
                archive_date = '';
            }

            if (env.TAG_NAME) {
                stage("Pushing NuGet") {
                    customImage.inside("-u 0") {
                        dir("bindings/dotnet/") {
                            withCredentials([string(credentialsId: 'PREAGONAL_GITHUB_TOKEN', variable: 'GITHUB_TOKEN')]) {
                                sh("dotnet nuget push -s https://nuget.pkg.github.com/Preagonal/index.json -k ${env.GITHUB_TOKEN} bin/Release/*.nupkg;chmod 777 -R .");
                                discordSend description: "NuGet Successful", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Artifact Successful: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK;
                            }
                            withCredentials([string(credentialsId: 'PREAGONAL_NUGET_TOKEN', variable: 'NUGET_TOKEN')]) {
                                sh("dotnet nuget push -s https://api.nuget.org/v3/index.json -k ${env.NUGET_TOKEN} bin/Release/*.nupkg;chmod 777 -R .");
                                discordSend description: "NuGet Successful", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Artifact Successful: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK;
                            }
                        }
                    }
                }
            }
        }
	} catch(err) {
		currentBuild.result = 'FAILURE'
		customImage.inside("-u 0") {
			sh("chmod 777 -R .");
		}
		discordSend description: "", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK

		notify("Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER}")
		throw err
	}
}

node('master') {
killall_jobs();
	def split_job_name = env.JOB_NAME.split(/\/{1}/);
	def fixed_job_name = split_job_name[1].replace('%2F',' ');
	checkout(scm);

	env.COMMIT_MSG = sh(
		script: 'git log -1 --pretty=%B ${GIT_COMMIT}',
		returnStdout: true
	).trim();

	env.GIT_COMMIT = sh(
		script: 'git log -1 --pretty=%H ${GIT_COMMIT}',
		returnStdout: true
	).trim();

	sh('git fetch --tags');

	env.LATEST_TAG = sh(
		script: 'git tag -l | tail -1',
		returnStdout: true
	).trim();

	echo("Latest tag: ${env.LATEST_TAG}");

	def version = env.LATEST_TAG.split(/\./);

	echo("Version: ${version}");

	def verMajor = version[0] as Integer;
	def verMinor = version[1] as Integer;
	def verPatch = version[2] as Integer;
	def versionChanged = false;

	echo("Version - Major: ${verMajor}, Minor: ${verMinor}, Patch: ${verPatch}");

	if (env.BRANCH_NAME.equals('main')) {
		verMinor++;
		verPatch = 0;
		versionChanged = true;
	} else if (env.BRANCH_NAME.equals('dev')) {
		verPatch++;
		versionChanged = true;
	}

    if (versionChanged) {
        withCredentials([string(credentialsId: 'PREAGONAL_GITHUB_TOKEN', variable: 'GITHUB_TOKEN')]) {
            def tagName = "${verMajor}.${verMinor}.${verPatch}";

            def iso8601Date = sh(
                script: 'date -Iseconds',
                returnStdout: true
            ).trim();

            env.JSON_RESPONSE = sh(
                script: "curl -L -X POST -H \"Accept: application/vnd.github+json\" -H \"Authorization: Bearer ${env.GITHUB_TOKEN}\" -H \"X-GitHub-Api-Version: 2022-11-28\" https://api.github.com/repos/xtjoeytx/gs2-parser/git/tags -d '{\"tag\":\"${tagName}\",\"message\":\"${env.COMMIT_MSG}\",\"object\":\"${env.GIT_COMMIT}\",\"type\":\"tree\",\"tagger\":{\"name\":\"preagonal-pipeline[bot]\",\"email\":\"119898225+preagonal-pipeline[bot]@users.noreply.github.com\",\"date\":\"${iso8601Date}\"}}'",
                returnStdout: true
            );
            def response = readJSON(text: env.JSON_RESPONSE);

            sh(
                script: "curl -L -X POST -H \"Accept: application/vnd.github+json\" -H \"Authorization: Bearer ${env.GITHUB_TOKEN}\" -H \"X-GitHub-Api-Version: 2022-11-28\" https://api.github.com/repos/xtjoeytx/gs2-parser/git/refs -d '{\"ref\": \"refs/tags/${tagName}\", \"sha\": \"${response.sha}\"}'",
                returnStdout: true
            );
        }
    }

	discordSend description: "${env.COMMIT_MSG}", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build Started: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK

	if (env.TAG_NAME) {
		sh(returnStdout: true, script: "echo '```' > RELEASE_DESCRIPTION.txt");
		env.RELEASE_DESCRIPTION = sh(returnStdout: true, script: "git tag -l --format='%(contents)' ${env.TAG_NAME} >> RELEASE_DESCRIPTION.txt");
		sh(returnStdout: true, script: "echo '```' >> RELEASE_DESCRIPTION.txt");
	}

	def branches = [:];
	def project = readJSON file: "JenkinsEnv.json";

	project.builds.each { v ->
		branches["Build ${v.DockerRoot}/${v.DockerImage}:${v.DockerTag}"] = {
			node {
				buildStep(v.DockerImage, v.Generator, v.OS, v.OSDir, v.Defines);
			}
		}
	}

	parallel(branches);

	// def customImage = docker.image("mcr.microsoft.com/dotnet/sdk:8.0");
	// customImage.pull();

	project.builds.each { v ->
        sh("mkdir -p bindings/dotnet/cross-compile/${v.OSDir}/");
		sh("mkdir -p precompiled/${v.OSDir}/");

        dir("bindings/dotnet/cross-compile/${v.OSDir}/") {
            unstash(name: "dynamic-${v.OSDir}");
            try {
                sh("mv -fv bin/* .");
                sh("rm -rf bin");
            } catch(err) { }
            try {
                sh("mv -fv lib/* .");
                sh("rm -rf lib")
            } catch(err) { }
        }

		dir("precompiled/${v.OSDir}/") {
			unstash(name: "static-${v.OSDir}");
			try {
				sh("mv -fv lib/* .");
				sh("rm -rf lib");
			} catch(err) { }
			try {
				sh("mv -fv bin/* .");
				sh("rm -rf bin");
			} catch(err) { }
		}
    }

    dir("bindings/dotnet/") {
        sh("ls -l cross-compile/*");
    }

	dir("precompiled/") {
		sh("ls -l *");
	}

	publishRust();
    buildStepDocker();
	
	if (env.TAG_NAME) {
		//def DESC = sh(returnStdout: true, script: 'cat RELEASE_DESCRIPTION.txt');
		//discordSend description: "${DESC}", customUsername: "OpenGraal", customAvatarUrl: "https://pbs.twimg.com/profile_images/1895028712/13460_106738052711614_100001262603030_51047_4149060_n_400x400.jpg", footer: "OpenGraal Team", link: "https://github.com/xtjoeytx/gs2-parser/pkgs/nuget/GS2Compiler", result: "SUCCESS", title: "GS2Compiler v${env.TAG_NAME} NuGet Package", webhookURL: env.GS2EMU_RELEASE_WEBHOOK;
	}

    sh("rm -rf ./*");
}