From a8c041067c7a120ada81b218cff580bc0c3c0e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horv=C3=A1th=20Istv=C3=A1n?= <dzctir@inf.elte.hu> Date: Wed, 2 Mar 2022 13:08:54 +0100 Subject: [PATCH] Add CI to the project (see #18) --- .gitlab-ci.yml | 254 ++++++++++++++++++++ Assets/Scripts.meta | 8 + Assets/Scripts/Editor.meta | 8 + Assets/Scripts/Editor/BuildCommand.cs | 267 +++++++++++++++++++++ Assets/Scripts/Editor/BuildCommand.cs.meta | 11 + ci/before_script.sh | 32 +++ ci/build.sh | 36 +++ ci/docker_build.sh | 14 ++ ci/docker_test.sh | 13 + ci/get_activation_file.sh | 49 ++++ ci/nunit-transforms/LICENSE.txt | 19 ++ ci/nunit-transforms/nunit3-junit.xslt | 69 ++++++ ci/test.sh | 56 +++++ 13 files changed, 836 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 Assets/Scripts.meta create mode 100644 Assets/Scripts/Editor.meta create mode 100644 Assets/Scripts/Editor/BuildCommand.cs create mode 100644 Assets/Scripts/Editor/BuildCommand.cs.meta create mode 100644 ci/before_script.sh create mode 100644 ci/build.sh create mode 100644 ci/docker_build.sh create mode 100644 ci/docker_test.sh create mode 100644 ci/get_activation_file.sh create mode 100644 ci/nunit-transforms/LICENSE.txt create mode 100644 ci/nunit-transforms/nunit3-junit.xslt create mode 100644 ci/test.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..6197a34 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,254 @@ +stages: + - prepare + - build_and_test + - deploy + +# If you are looking for a place where to add 'UNITY_LICENSE_FILE' and other secrets, please visit your project's gitlab page: +# settings > CI/CD > Variables instead +variables: + BUILD_NAME: Tree-o + UNITY_ACTIVATION_FILE: ./unity3d.alf + IMAGE: unityci/editor # https://hub.docker.com/r/unityci/editor + IMAGE_VERSION: "0.15" # https://github.com/game-ci/docker/releases + UNITY_DIR: $CI_PROJECT_DIR # this needs to be an absolute path. Defaults to the root of your tree. + # You can expose this in Unity via Application.version + VERSION_NUMBER_VAR: $CI_COMMIT_REF_SLUG-$CI_PIPELINE_ID-$CI_JOB_ID + VERSION_BUILD_VAR: $CI_PIPELINE_IID + +image: $IMAGE:$UNITY_VERSION-base-$IMAGE_VERSION + +get-unity-version: + image: alpine + stage: prepare + variables: + GIT_DEPTH: 1 + script: + - echo UNITY_VERSION=$(cat $UNITY_DIR/ProjectSettings/ProjectVersion.txt | grep "m_EditorVersion:.*" | awk '{ print $2}') | tee prepare.env + artifacts: + reports: + dotenv: prepare.env + +.unity_before_script: &unity_before_script + before_script: + - chmod +x ./ci/before_script.sh && ./ci/before_script.sh + needs: + - job: get-unity-version + artifacts: true + +.cache: &cache + cache: + key: "$CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG-$TEST_PLATFORM" + paths: + - $UNITY_DIR/Library/ + +.license: &license + rules: + - if: '$UNITY_LICENSE != null' + when: always + +.unity_defaults: &unity_defaults + <<: + - *unity_before_script + - *cache + - *license + +# run this job when you need to request a license +# you may need to follow activation steps from documentation +get-activation-file: + <<: *unity_before_script + rules: + - if: '$UNITY_LICENSE == null' + when: manual + stage: prepare + script: + - chmod +x ./ci/get_activation_file.sh && ./ci/get_activation_file.sh + artifacts: + paths: + - $UNITY_ACTIVATION_FILE + expire_in: 10 min # Expiring this as artifacts may contain sensitive data and should not be kept public + +.test: &test + stage: build_and_test + <<: *unity_defaults + script: + - chmod +x ./ci/test.sh && ./ci/test.sh + artifacts: + when: always + expire_in: 2 weeks + # https://gitlab.com/gableroux/unity3d-gitlab-ci-example/-/issues/83 + # you may need to remove or replace these to fit your need if you are using your own runners + tags: + - gitlab-org + coverage: /<Linecoverage>(.*?)</Linecoverage>/ + +# Tests without junit reporting results in GitLab +# test-playmode: +# <<: *test +# variables: +# TEST_PLATFORM: playmode +# TESTING_TYPE: NUNIT + +# test-editmode: +# <<: *test +# variables: +# TEST_PLATFORM: editmode +# TESTING_TYPE: NUNIT + +# uncomment the following blocks if you'd like to have junit reporting unity test results in gitlab +# We currently have the following issue which prevents it from working right now, but you can give +# a hand if you're interested in this feature: +# https://gitlab.com/gableroux/unity3d-gitlab-ci-example/-/issues/151 + +#.test-with-junit-reports: &test-with-junit-reports +# stage: build_and_test +# <<: *unity_defaults +# script: +# # This could be made faster by adding these packages to base image or running in a separate job (and step) +# # We could use an image with these two depencencies only and only do the saxonb-xslt command on +# # previous job's artifacts +# - apt-get update && apt-get install -y default-jre libsaxonb-java +# - chmod +x ./ci/test.sh && ./ci/test.sh +# - saxonb-xslt -s $UNITY_DIR/$TEST_PLATFORM-results.xml -xsl $CI_PROJECT_DIR/ci/nunit-transforms/nunit3-junit.xslt >$UNITY_DIR/$TEST_PLATFORM-junit-results.xml +# artifacts: +# when: always +# paths: +# # This is exported to allow viewing the Coverage Report in detail if needed +# - $UNITY_DIR/$TEST_PLATFORM-coverage/ +# reports: +# junit: +# - $UNITY_DIR/$TEST_PLATFORM-junit-results.xml +# - "$UNITY_DIR/$TEST_PLATFORM-coverage/coverage.xml" +# expire_in: 2 weeks +# # https://gitlab.com/gableroux/unity3d-gitlab-ci-example/-/issues/83 +# # you may need to remove or replace these to fit your need if you are using your own runners +# tags: +# - gitlab-org +# coverage: /<Linecoverage>(.*?)</Linecoverage>/ +# +#test-playmode-with-junit-reports: +# <<: *test-with-junit-reports +# variables: +# TEST_PLATFORM: playmode +# TESTING_TYPE: JUNIT +# +#test-editmode-with-junit-reports: +# <<: *test-with-junit-reports +# variables: +# TEST_PLATFORM: editmode +# TESTING_TYPE: JUNIT + +.build: &build + stage: build_and_test + <<: *unity_defaults + script: + - chmod +x ./ci/build.sh && ./ci/build.sh + artifacts: + paths: + - $UNITY_DIR/Builds/ + # https://gitlab.com/gableroux/unity3d-gitlab-ci-example/-/issues/83 + # you may need to remove or replace these to fit your need if you are using your own runners + tags: + - gitlab-org + +build-StandaloneLinux64: + <<: *build + variables: + BUILD_TARGET: StandaloneLinux64 + +build-StandaloneLinux64-il2cpp: + <<: *build + image: $IMAGE:$UNITY_VERSION-linux-il2cpp-$IMAGE_VERSION + variables: + BUILD_TARGET: StandaloneLinux64 + SCRIPTING_BACKEND: IL2CPP + +#build-StandaloneOSX: +# <<: *build +# image: $IMAGE:$UNITY_VERSION-mac-mono-$IMAGE_VERSION +# variables: +# BUILD_TARGET: StandaloneOSX + +#Note: build target names changed in recent versions, use this for versions < 2017.2: +# build-StandaloneOSXUniversal: +# <<: *build +# variables: +# BUILD_TARGET: StandaloneOSXUniversal + +build-StandaloneWindows64: + <<: *build + image: $IMAGE:$UNITY_VERSION-windows-mono-$IMAGE_VERSION + variables: + BUILD_TARGET: StandaloneWindows64 + +# For webgl support, you need to set Compression Format to Disabled for v0.9. See https://github.com/game-ci/docker/issues/75 +build-WebGL: + <<: *build + image: $IMAGE:$UNITY_VERSION-webgl-$IMAGE_VERSION + # Temporary workaround for https://github.com/game-ci/docker/releases/tag/v0.9 and webgl support in current project to prevent errors with missing ffmpeg + before_script: + - chmod +x ./ci/before_script.sh && ./ci/before_script.sh + - apt-get update && apt-get install ffmpeg -y + variables: + BUILD_TARGET: WebGL + +#build-android: +# <<: *build +# image: $IMAGE:$UNITY_VERSION-android-$IMAGE_VERSION +# variables: +# BUILD_TARGET: Android +# BUILD_APP_BUNDLE: "false" + +#build-android-il2cpp: +# <<: *build +# image: $IMAGE:$UNITY_VERSION-android-$IMAGE_VERSION +# variables: +# BUILD_TARGET: Android +# BUILD_APP_BUNDLE: "false" +# SCRIPTING_BACKEND: IL2CPP + +#deploy-android: +# stage: deploy +# image: ruby +# script: +# - cd $UNITY_DIR/Builds/Android +# - echo $GPC_TOKEN > gpc_token.json +# - gem install bundler +# - bundle install +# - fastlane supply --aab "${BUILD_NAME}.aab" --track internal --package_name com.youcompany.yourgame --json_key ./gpc_token.json +# needs: ["build-android"] + +#build-ios-xcode: +# <<: *build +# image: $IMAGE:$UNITY_VERSION-ios-$IMAGE_VERSION +# variables: +# BUILD_TARGET: iOS + +#build-and-deploy-ios: +# stage: deploy +# script: +# - cd $UNITY_DIR/Builds/iOS/$BUILD_NAME +# - pod install +# - fastlane ios beta +# tags: +# - ios +# - mac +# needs: ["build-ios-xcode"] + +pages: + image: alpine:latest + stage: deploy + script: + - mv "$UNITY_DIR/Builds/WebGL/${BUILD_NAME}" public + artifacts: + paths: + - public + only: + - main + +workflow: + rules: + - if: $CI_MERGE_REQUEST_ID + when: never + - if: $CI_COMMIT_TAG + when: never + - when: always diff --git a/Assets/Scripts.meta b/Assets/Scripts.meta new file mode 100644 index 0000000..a789076 --- /dev/null +++ b/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 441a5be2d4157cd419d9457727681552 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor.meta b/Assets/Scripts/Editor.meta new file mode 100644 index 0000000..4b672b9 --- /dev/null +++ b/Assets/Scripts/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e73cc4c83019b134db945ba52f915a02 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/BuildCommand.cs b/Assets/Scripts/Editor/BuildCommand.cs new file mode 100644 index 0000000..d5cc771 --- /dev/null +++ b/Assets/Scripts/Editor/BuildCommand.cs @@ -0,0 +1,267 @@ +using UnityEditor; +using System.Linq; +using System; +using System.IO; + +static class BuildCommand { + private const string KEYSTORE_PASS = "KEYSTORE_PASS"; + private const string KEY_ALIAS_PASS = "KEY_ALIAS_PASS"; + private const string KEY_ALIAS_NAME = "KEY_ALIAS_NAME"; + private const string KEYSTORE = "keystore.keystore"; + private const string BUILD_OPTIONS_ENV_VAR = "BuildOptions"; + private const string ANDROID_BUNDLE_VERSION_CODE = "VERSION_BUILD_VAR"; + private const string ANDROID_APP_BUNDLE = "BUILD_APP_BUNDLE"; + private const string SCRIPTING_BACKEND_ENV_VAR = "SCRIPTING_BACKEND"; + private const string VERSION_NUMBER_VAR = "VERSION_NUMBER_VAR"; + private const string VERSION_iOS = "VERSION_BUILD_VAR"; + + static string GetArgument(string name) { + string[] args = Environment.GetCommandLineArgs(); + for (int i = 0; i < args.Length; i++) { + if (args[i].Contains(name)) { + return args[i + 1]; + } + } + + return null; + } + + static string[] GetEnabledScenes() { + return ( + from scene in EditorBuildSettings.scenes + where scene.enabled + where !string.IsNullOrEmpty(scene.path) + select scene.path + ).ToArray(); + } + + static BuildTarget GetBuildTarget() { + string buildTargetName = GetArgument("customBuildTarget"); + Console.WriteLine(":: Received customBuildTarget " + buildTargetName); + + if (buildTargetName.ToLower() == "android") { +#if !UNITY_5_6_OR_NEWER + // https://issuetracker.unity3d.com/issues/buildoptions-dot-acceptexternalmodificationstoplayer-causes-unityexception-unknown-project-type-0 + // Fixed in Unity 5.6.0 + // side effect to fix android build system: + EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Internal; +#endif + } + + if (buildTargetName.TryConvertToEnum(out BuildTarget target)) return target; + + Console.WriteLine( + $":: {nameof(buildTargetName)} \"{buildTargetName}\" not defined on enum {nameof(BuildTarget)}, using {nameof(BuildTarget.NoTarget)} enum to build"); + + return BuildTarget.NoTarget; + } + + static string GetBuildPath() { + string buildPath = GetArgument("customBuildPath"); + Console.WriteLine(":: Received customBuildPath " + buildPath); + if (buildPath == "") { + throw new Exception("customBuildPath argument is missing"); + } + + return buildPath; + } + + static string GetBuildName() { + string buildName = GetArgument("customBuildName"); + Console.WriteLine(":: Received customBuildName " + buildName); + if (buildName == "") { + throw new Exception("customBuildName argument is missing"); + } + + return buildName; + } + + static string GetFixedBuildPath(BuildTarget buildTarget, string buildPath, string buildName) { + if (buildTarget.ToString().ToLower().Contains("windows")) { + buildName += ".exe"; + } else if (buildTarget == BuildTarget.Android) { +#if UNITY_2018_3_OR_NEWER + buildName += EditorUserBuildSettings.buildAppBundle ? ".aab" : ".apk"; +#else + buildName += ".apk"; +#endif + } + + return buildPath + buildName; + } + + static BuildOptions GetBuildOptions() { + if (TryGetEnv(BUILD_OPTIONS_ENV_VAR, out string envVar)) { + string[] allOptionVars = envVar.Split(','); + BuildOptions allOptions = BuildOptions.None; + BuildOptions option; + string optionVar; + int length = allOptionVars.Length; + + Console.WriteLine($":: Detecting {BUILD_OPTIONS_ENV_VAR} env var with {length} elements ({envVar})"); + + for (int i = 0; i < length; i++) { + optionVar = allOptionVars[i]; + + if (optionVar.TryConvertToEnum(out option)) { + allOptions |= option; + } else { + Console.WriteLine($":: Cannot convert {optionVar} to {nameof(BuildOptions)} enum, skipping it."); + } + } + + return allOptions; + } + + return BuildOptions.None; + } + + // https://stackoverflow.com/questions/1082532/how-to-tryparse-for-enum-value + static bool TryConvertToEnum<TEnum>(this string strEnumValue, out TEnum value) { + if (!Enum.IsDefined(typeof(TEnum), strEnumValue)) { + value = default; + return false; + } + + value = (TEnum) Enum.Parse(typeof(TEnum), strEnumValue); + return true; + } + + static bool TryGetEnv(string key, out string value) { + value = Environment.GetEnvironmentVariable(key); + return !string.IsNullOrEmpty(value); + } + + static void SetScriptingBackendFromEnv(BuildTarget platform) { + var targetGroup = BuildPipeline.GetBuildTargetGroup(platform); + if (TryGetEnv(SCRIPTING_BACKEND_ENV_VAR, out string scriptingBackend)) { + if (scriptingBackend.TryConvertToEnum(out ScriptingImplementation backend)) { + Console.WriteLine($":: Setting ScriptingBackend to {backend}"); + PlayerSettings.SetScriptingBackend(targetGroup, backend); + } else { + string possibleValues = string.Join(", ", + Enum.GetValues(typeof(ScriptingImplementation)).Cast<ScriptingImplementation>()); + throw new Exception( + $"Could not find '{scriptingBackend}' in ScriptingImplementation enum. Possible values are: {possibleValues}"); + } + } else { + var defaultBackend = PlayerSettings.GetDefaultScriptingBackend(targetGroup); + Console.WriteLine( + $":: Using project's configured ScriptingBackend (should be {defaultBackend} for targetGroup {targetGroup}"); + } + } + + static void PerformBuild() { + var buildTarget = GetBuildTarget(); + + Console.WriteLine(":: Performing build"); + if (TryGetEnv(VERSION_NUMBER_VAR, out var bundleVersionNumber)) { + if (buildTarget == BuildTarget.iOS) { + // bundleVersionNumber = GetIosVersion(); // NOTE: we don't build for iOS + } + + Console.WriteLine( + $":: Setting bundleVersionNumber to '{bundleVersionNumber}' (Length: {bundleVersionNumber.Length})"); + PlayerSettings.bundleVersion = bundleVersionNumber; + } + + if (buildTarget == BuildTarget.Android) { + // HandleAndroidAppBundle(); // NOTE: we don't build for Android + // HandleAndroidBundleVersionCode(); + // HandleAndroidKeystore(); + } + + var buildPath = GetBuildPath(); + var buildName = GetBuildName(); + var buildOptions = GetBuildOptions(); + var fixedBuildPath = GetFixedBuildPath(buildTarget, buildPath, buildName); + + SetScriptingBackendFromEnv(buildTarget); + + var buildReport = BuildPipeline.BuildPlayer(GetEnabledScenes(), fixedBuildPath, buildTarget, buildOptions); + + if (buildReport.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded) + throw new Exception($"Build ended with {buildReport.summary.result} status"); + + Console.WriteLine(":: Done with build"); + } +// +// private static void HandleAndroidAppBundle() { +// if (TryGetEnv(ANDROID_APP_BUNDLE, out string value)) { +// #if UNITY_2018_3_OR_NEWER +// if (bool.TryParse(value, out bool buildAppBundle)) { +// EditorUserBuildSettings.buildAppBundle = buildAppBundle; +// Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected, set buildAppBundle to {value}."); +// } else { +// Console.WriteLine( +// $":: {ANDROID_APP_BUNDLE} env var detected but the value \"{value}\" is not a boolean."); +// } +// #else +// Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected but does not work with lower Unity version than 2018.3"); +// #endif +// } +// } +// +// private static void HandleAndroidBundleVersionCode() { +// if (TryGetEnv(ANDROID_BUNDLE_VERSION_CODE, out string value)) { +// if (int.TryParse(value, out int version)) { +// PlayerSettings.Android.bundleVersionCode = version; +// Console.WriteLine( +// $":: {ANDROID_BUNDLE_VERSION_CODE} env var detected, set the bundle version code to {value}."); +// } else +// Console.WriteLine( +// $":: {ANDROID_BUNDLE_VERSION_CODE} env var detected but the version value \"{value}\" is not an integer."); +// } +// } +// +// private static string GetIosVersion() { +// if (TryGetEnv(VERSION_iOS, out string value)) { +// if (int.TryParse(value, out int version)) { +// Console.WriteLine($":: {VERSION_iOS} env var detected, set the version to {value}."); +// return version.ToString(); +// } else +// Console.WriteLine( +// $":: {VERSION_iOS} env var detected but the version value \"{value}\" is not an integer."); +// } +// +// throw new ArgumentNullException(nameof(value), $":: Error finding {VERSION_iOS} env var"); +// } +// +// private static void HandleAndroidKeystore() { +// #if UNITY_2019_1_OR_NEWER +// PlayerSettings.Android.useCustomKeystore = false; +// #endif +// +// if (!File.Exists(KEYSTORE)) { +// Console.WriteLine($":: {KEYSTORE} not found, skipping setup, using Unity's default keystore"); +// return; +// } +// +// PlayerSettings.Android.keystoreName = KEYSTORE; +// +// string keystorePass; +// string keystoreAliasPass; +// +// if (TryGetEnv(KEY_ALIAS_NAME, out string keyaliasName)) { +// PlayerSettings.Android.keyaliasName = keyaliasName; +// Console.WriteLine($":: using ${KEY_ALIAS_NAME} env var on PlayerSettings"); +// } else { +// Console.WriteLine($":: ${KEY_ALIAS_NAME} env var not set, using Project's PlayerSettings"); +// } +// +// if (!TryGetEnv(KEYSTORE_PASS, out keystorePass)) { +// Console.WriteLine($":: ${KEYSTORE_PASS} env var not set, skipping setup, using Unity's default keystore"); +// return; +// } +// +// if (!TryGetEnv(KEY_ALIAS_PASS, out keystoreAliasPass)) { +// Console.WriteLine($":: ${KEY_ALIAS_PASS} env var not set, skipping setup, using Unity's default keystore"); +// return; +// } +// #if UNITY_2019_1_OR_NEWER +// PlayerSettings.Android.useCustomKeystore = true; +// #endif +// PlayerSettings.Android.keystorePass = keystorePass; +// PlayerSettings.Android.keyaliasPass = keystoreAliasPass; +// } +} diff --git a/Assets/Scripts/Editor/BuildCommand.cs.meta b/Assets/Scripts/Editor/BuildCommand.cs.meta new file mode 100644 index 0000000..9f1ca5f --- /dev/null +++ b/Assets/Scripts/Editor/BuildCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 500342c176fc01e42bcd63303276b79a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ci/before_script.sh b/ci/before_script.sh new file mode 100644 index 0000000..1e1effa --- /dev/null +++ b/ci/before_script.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -e +set -x +mkdir -p /root/.cache/unity3d +mkdir -p /root/.local/share/unity3d/Unity/ +set +x + +unity_license_destination=/root/.local/share/unity3d/Unity/Unity_lic.ulf +android_keystore_destination=keystore.keystore + + +upper_case_build_target=${BUILD_TARGET^^}; + +if [ "$upper_case_build_target" = "ANDROID" ] +then + if [ -n $ANDROID_KEYSTORE_BASE64 ] + then + echo "'\$ANDROID_KEYSTORE_BASE64' found, decoding content into ${android_keystore_destination}" + echo $ANDROID_KEYSTORE_BASE64 | base64 --decode > ${android_keystore_destination} + else + echo '$ANDROID_KEYSTORE_BASE64'" env var not found, building with Unity's default debug keystore" + fi +fi + +if [ -n "$UNITY_LICENSE" ] +then + echo "Writing '\$UNITY_LICENSE' to license file ${unity_license_destination}" + echo "${UNITY_LICENSE}" | tr -d '\r' > ${unity_license_destination} +else + echo "'\$UNITY_LICENSE' env var not found" +fi diff --git a/ci/build.sh b/ci/build.sh new file mode 100644 index 0000000..d92a8c5 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -e +set -x + +echo "Building for $BUILD_TARGET" + +export BUILD_PATH=$UNITY_DIR/Builds/$BUILD_TARGET/ +mkdir -p $BUILD_PATH + +${UNITY_EXECUTABLE:-xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' unity-editor} \ + -projectPath $UNITY_DIR \ + -quit \ + -batchmode \ + -nographics \ + -buildTarget $BUILD_TARGET \ + -customBuildTarget $BUILD_TARGET \ + -customBuildName $BUILD_NAME \ + -customBuildPath $BUILD_PATH \ + -executeMethod BuildCommand.PerformBuild \ + -logFile /dev/stdout + +UNITY_EXIT_CODE=$? + +if [ $UNITY_EXIT_CODE -eq 0 ]; then + echo "Run succeeded, no failures occurred"; +elif [ $UNITY_EXIT_CODE -eq 2 ]; then + echo "Run succeeded, some tests failed"; +elif [ $UNITY_EXIT_CODE -eq 3 ]; then + echo "Run failure (other failure)"; +else + echo "Unexpected exit code $UNITY_EXIT_CODE"; +fi + +ls -la $BUILD_PATH +[ -n "$(ls -A $BUILD_PATH)" ] # fail job if build folder is empty diff --git a/ci/docker_build.sh b/ci/docker_build.sh new file mode 100644 index 0000000..3088dfa --- /dev/null +++ b/ci/docker_build.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e + +docker run \ + -e BUILD_NAME \ + -e UNITY_LICENSE \ + -e BUILD_TARGET \ + -e UNITY_USERNAME \ + -e UNITY_PASSWORD \ + -w /project/ \ + -v $UNITY_DIR:/project/ \ + $IMAGE_NAME \ + /bin/bash -c "/project/ci/before_script.sh && /project/ci/build.sh" diff --git a/ci/docker_test.sh b/ci/docker_test.sh new file mode 100644 index 0000000..1b846af --- /dev/null +++ b/ci/docker_test.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +docker run \ + -e UNITY_LICENSE \ + -e TEST_PLATFORM \ + -e UNITY_USERNAME \ + -e UNITY_PASSWORD \ + -w /project/ \ + -v $UNITY_DIR:/project/ \ + $IMAGE_NAME \ + /bin/bash -c "/project/ci/before_script.sh && /project/ci/test.sh" diff --git a/ci/get_activation_file.sh b/ci/get_activation_file.sh new file mode 100644 index 0000000..2ace508 --- /dev/null +++ b/ci/get_activation_file.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +activation_file=${UNITY_ACTIVATION_FILE:-./unity3d.alf} + +if [[ -z "${UNITY_USERNAME}" ]] || [[ -z "${UNITY_PASSWORD}" ]]; then + echo "UNITY_USERNAME or UNITY_PASSWORD environment variables are not set, please refer to instructions in the readme and add these to your secret environment variables." + exit 1 +fi + +xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \ + unity-editor \ + -logFile /dev/stdout \ + -batchmode \ + -nographics \ + -username "$UNITY_USERNAME" -password "$UNITY_PASSWORD" | + tee ./unity-output.log + +cat ./unity-output.log | + grep 'LICENSE SYSTEM .* Posting *' | + sed 's/.*Posting *//' > "${activation_file}" + +# Fail job if unity.alf is empty +ls "${UNITY_ACTIVATION_FILE:-./unity3d.alf}" +exit_code=$? + +if [[ ${exit_code} -eq 0 ]]; then + echo "" + echo "" + echo "### Congratulations! ###" + echo "${activation_file} was generated successfully!" + echo "" + echo "### Next steps ###" + echo "" + echo "Complete the activation process manually" + echo "" + echo " 1. Download the artifact which should contain ${activation_file}" + echo " 2. Visit https://license.unity3d.com/manual" + echo " 3. Upload ${activation_file} in the form" + echo " 4. Answer questions (unity pro vs personal edition, both will work, just pick the one you use)" + echo " 5. Download 'Unity_v2019.x.ulf' file (year should match your unity version here, 'Unity_v2018.x.ulf' for 2018, etc.)" + echo " 6. Copy the content of 'Unity_v2019.x.ulf' license file to your CI's environment variable 'UNITY_LICENSE'. (Open your project's parameters > CI/CD > Variables and add 'UNITY_LICENSE' as the key and paste the content of the license file into the value)" + echo "" + echo "Once you're done, hit retry on the pipeline where other jobs failed, or just push another commit. Things should be green" + echo "" + echo "(optional) For more details on why this is not fully automated, visit https://gitlab.com/gableroux/unity3d-gitlab-ci-example/issues/73" +else + echo "License file could not be found at ${UNITY_ACTIVATION_FILE:-./unity3d.alf}" +fi +exit $exit_code diff --git a/ci/nunit-transforms/LICENSE.txt b/ci/nunit-transforms/LICENSE.txt new file mode 100644 index 0000000..5aa1d8f --- /dev/null +++ b/ci/nunit-transforms/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 Paul Hicks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ci/nunit-transforms/nunit3-junit.xslt b/ci/nunit-transforms/nunit3-junit.xslt new file mode 100644 index 0000000..08e046a --- /dev/null +++ b/ci/nunit-transforms/nunit3-junit.xslt @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:output method="xml" indent="yes"/> + + <xsl:template match="/test-run"> + <testsuites tests="{@testcasecount}" failures="{@failed}" disabled="{@skipped}" time="{@duration}"> + <xsl:apply-templates/> + </testsuites> + </xsl:template> + + <xsl:template match="test-suite"> + <xsl:if test="test-case"> + <testsuite tests="{@testcasecount}" time="{@duration}" errors="{@testcasecount - @passed - @skipped - @failed}" failures="{@failed}" skipped="{@skipped}" timestamp="{@start-time}"> + <xsl:attribute name="name"> + <xsl:for-each select="ancestor-or-self::test-suite/@name"> + <xsl:value-of select="concat(., '.')"/> + </xsl:for-each> + </xsl:attribute> + <xsl:apply-templates select="test-case"/> + </testsuite> + <xsl:apply-templates select="test-suite"/> + </xsl:if> + <xsl:if test="not(test-case)"> + <xsl:apply-templates/> + </xsl:if> + </xsl:template> + + <xsl:template match="test-case"> + <testcase name="{@name}" assertions="{@asserts}" time="{@duration}" status="{@result}" classname="{@classname}"> + <xsl:if test="@runstate = 'Skipped' or @runstate = 'Ignored'"> + <skipped/> + </xsl:if> + + <xsl:apply-templates/> + </testcase> + </xsl:template> + + <xsl:template match="command-line"/> + <xsl:template match="settings"/> + + <xsl:template match="output"> + <system-out> + <xsl:value-of select="."/> + </system-out> + </xsl:template> + + <xsl:template match="stack-trace"> + </xsl:template> + + <xsl:template match="test-case/failure"> + <failure message="{./message}"> + <xsl:value-of select="./stack-trace"/> + </failure> + </xsl:template> + + <xsl:template match="test-suite/failure"/> + + <xsl:template match="test-case/reason"> + <skipped message="{./message}"/> + </xsl:template> + + <xsl:template match="test-case/assertions"> + </xsl:template> + + <xsl:template match="test-suite/reason"/> + + <xsl:template match="properties"/> +</xsl:stylesheet> + diff --git a/ci/test.sh b/ci/test.sh new file mode 100644 index 0000000..e45251c --- /dev/null +++ b/ci/test.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -x + +echo "Testing for $TEST_PLATFORM, Unit Type: $TESTING_TYPE" + +CODE_COVERAGE_PACKAGE="com.unity.testtools.codecoverage" +PACKAGE_MANIFEST_PATH="Packages/manifest.json" + +${UNITY_EXECUTABLE:-xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' unity-editor} \ + -projectPath $UNITY_DIR \ + -runTests \ + -testPlatform $TEST_PLATFORM \ + -testResults $UNITY_DIR/$TEST_PLATFORM-results.xml \ + -logFile /dev/stdout \ + -batchmode \ + -nographics \ + -enableCodeCoverage \ + -coverageResultsPath $UNITY_DIR/$TEST_PLATFORM-coverage \ + -coverageOptions "generateAdditionalMetrics;generateHtmlReport;generateHtmlReportHistory;generateBadgeReport;" \ + -debugCodeOptimization + +UNITY_EXIT_CODE=$? + +if [ $UNITY_EXIT_CODE -eq 0 ]; then + echo "Run succeeded, no failures occurred"; +elif [ $UNITY_EXIT_CODE -eq 2 ]; then + echo "Run succeeded, some tests failed"; + if [ $TESTING_TYPE == 'JUNIT' ]; then + echo "Converting results to JUNit for analysis"; + saxonb-xslt -s $UNITY_DIR/$TEST_PLATFORM-results.xml -xsl $CI_PROJECT_DIR/ci/nunit-transforms/nunit3-junit.xslt >$UNITY_DIR/$TEST_PLATFORM-junit-results.xml + fi +elif [ $UNITY_EXIT_CODE -eq 3 ]; then + echo "Run failure (other failure)"; + if [ $TESTING_TYPE == 'JUNIT' ]; then + echo "Not converting results to JUNit"; + fi +else + echo "Unexpected exit code $UNITY_EXIT_CODE"; + if [ $TESTING_TYPE == 'JUNIT' ]; then + echo "Not converting results to JUNit"; + fi +fi + +if grep $CODE_COVERAGE_PACKAGE $PACKAGE_MANIFEST_PATH; then + cat $UNITY_DIR/$TEST_PLATFORM-coverage/Report/Summary.xml | grep Linecoverage + mv $UNITY_DIR/$TEST_PLATFORM-coverage/$CI_PROJECT_NAME-opencov/*Mode/TestCoverageResults_*.xml $UNITY_DIR/$TEST_PLATFORM-coverage/coverage.xml + rm -r $UNITY_DIR/$TEST_PLATFORM-coverage/$CI_PROJECT_NAME-opencov/ +else + { + echo -e "\033[33mCode Coverage package not found in $PACKAGE_MANIFEST_PATH. Please install the package \"Code Coverage\" through Unity's Package Manager to enable coverage reports.\033[0m" + } 2> /dev/null +fi + +cat $UNITY_DIR/$TEST_PLATFORM-results.xml | grep test-run | grep Passed +exit $UNITY_EXIT_CODE -- GitLab