From 373d987823c8bfd37b9a037d747a40bfc9b4e7ca Mon Sep 17 00:00:00 2001 From: Andrew Kahr <22359829+AndrewKahr@users.noreply.github.com> Date: Sat, 18 Dec 2021 21:16:35 -0800 Subject: [PATCH] Implemented logic for windows based docker builds. Moved dockerfiles and scripts to platform specific folders. --- .../Editor/UnityBuilderAction/Builder.cs | 6 +- dist/{ => platforms/ubuntu}/Dockerfile | 4 +- dist/{ => platforms/ubuntu}/entrypoint.sh | 0 dist/{ => platforms/ubuntu}/steps/activate.sh | 0 dist/{ => platforms/ubuntu}/steps/build.sh | 0 .../ubuntu}/steps/return_license.sh | 0 .../ubuntu}/steps/set_gitcredential.sh | 0 dist/platforms/windows/Dockerfile | 18 +++ dist/platforms/windows/entrypoint.ps1 | 14 ++ dist/platforms/windows/steps/activate.ps1 | 6 + dist/platforms/windows/steps/build.ps1 | 147 ++++++++++++++++++ .../windows/steps/return_license.ps1 | 6 + src/model/action.ts | 13 +- src/model/docker.ts | 135 +++++++++++++++- src/model/image-tag.ts | 37 ++++- 15 files changed, 376 insertions(+), 10 deletions(-) rename dist/{ => platforms/ubuntu}/Dockerfile (87%) rename dist/{ => platforms/ubuntu}/entrypoint.sh (100%) mode change 100755 => 100644 rename dist/{ => platforms/ubuntu}/steps/activate.sh (100%) mode change 100755 => 100644 rename dist/{ => platforms/ubuntu}/steps/build.sh (100%) mode change 100755 => 100644 rename dist/{ => platforms/ubuntu}/steps/return_license.sh (100%) mode change 100755 => 100644 rename dist/{ => platforms/ubuntu}/steps/set_gitcredential.sh (100%) mode change 100755 => 100644 create mode 100644 dist/platforms/windows/Dockerfile create mode 100644 dist/platforms/windows/entrypoint.ps1 create mode 100644 dist/platforms/windows/steps/activate.ps1 create mode 100644 dist/platforms/windows/steps/build.ps1 create mode 100644 dist/platforms/windows/steps/return_license.ps1 diff --git a/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs b/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs index 5510ecda..50e25fb7 100644 --- a/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs +++ b/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs @@ -39,12 +39,14 @@ namespace UnityBuilderAction // Set version for this build VersionApplicator.SetVersion(options["buildVersion"]); - VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]); // Apply Android settings if (buildPlayerOptions.target == BuildTarget.Android) + { + VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]); AndroidSettings.Apply(options); - + } + // Execute default AddressableAsset content build, if the package is installed. // Version defines would be the best solution here, but Unity 2018 doesn't support that, // so we fall back to using reflection instead. diff --git a/dist/Dockerfile b/dist/platforms/ubuntu/Dockerfile similarity index 87% rename from dist/Dockerfile rename to dist/platforms/ubuntu/Dockerfile index 85da52f6..5bbf0b43 100644 --- a/dist/Dockerfile +++ b/dist/platforms/ubuntu/Dockerfile @@ -11,9 +11,9 @@ LABEL "homepage"="http://github.com/webbertakken/unity-actions" LABEL "maintainer"="Webber Takken " ADD default-build-script /UnityBuilderAction -ADD steps /steps +ADD platforms/ubuntu/steps /steps RUN chmod -R +x /steps -ADD entrypoint.sh /entrypoint.sh +ADD platforms/ubuntu/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh RUN ls diff --git a/dist/entrypoint.sh b/dist/platforms/ubuntu/entrypoint.sh old mode 100755 new mode 100644 similarity index 100% rename from dist/entrypoint.sh rename to dist/platforms/ubuntu/entrypoint.sh diff --git a/dist/steps/activate.sh b/dist/platforms/ubuntu/steps/activate.sh old mode 100755 new mode 100644 similarity index 100% rename from dist/steps/activate.sh rename to dist/platforms/ubuntu/steps/activate.sh diff --git a/dist/steps/build.sh b/dist/platforms/ubuntu/steps/build.sh old mode 100755 new mode 100644 similarity index 100% rename from dist/steps/build.sh rename to dist/platforms/ubuntu/steps/build.sh diff --git a/dist/steps/return_license.sh b/dist/platforms/ubuntu/steps/return_license.sh old mode 100755 new mode 100644 similarity index 100% rename from dist/steps/return_license.sh rename to dist/platforms/ubuntu/steps/return_license.sh diff --git a/dist/steps/set_gitcredential.sh b/dist/platforms/ubuntu/steps/set_gitcredential.sh old mode 100755 new mode 100644 similarity index 100% rename from dist/steps/set_gitcredential.sh rename to dist/platforms/ubuntu/steps/set_gitcredential.sh diff --git a/dist/platforms/windows/Dockerfile b/dist/platforms/windows/Dockerfile new file mode 100644 index 00000000..d7824658 --- /dev/null +++ b/dist/platforms/windows/Dockerfile @@ -0,0 +1,18 @@ +ARG IMAGE +FROM $IMAGE + +LABEL "com.github.actions.name"="Unity - Builder" +LABEL "com.github.actions.description"="Build Unity projects for different platforms." +LABEL "com.github.actions.icon"="box" +LABEL "com.github.actions.color"="gray-dark" + +LABEL "repository"="http://github.com/webbertakken/unity-actions" +LABEL "homepage"="http://github.com/webbertakken/unity-actions" +LABEL "maintainer"="Webber Takken " + +ADD default-build-script c:\UnityBuilderAction +ADD platforms/windows/steps c:\steps +ADD platforms/windows/entrypoint.ps1 c:\entrypoint.ps1 +RUN ls + +ENTRYPOINT ["powershell", "c:/entrypoint.ps1"] diff --git a/dist/platforms/windows/entrypoint.ps1 b/dist/platforms/windows/entrypoint.ps1 new file mode 100644 index 00000000..e0304956 --- /dev/null +++ b/dist/platforms/windows/entrypoint.ps1 @@ -0,0 +1,14 @@ +# First we activate Unity +& "c:\steps\activate.ps1" + +# Next we import the registry keys that point Unity to the win 10 sdk +reg import c:\regkeys\winsdk.reg + +# Now we register the visual studio installation so Unity can find it +regsvr32 C:\ProgramData\Microsoft\VisualStudio\Setup\x64\Microsoft.VisualStudio.Setup.Configuration.Native.dll + +# Now we can build our project +& "c:\steps\build.ps1" + +# Finally free the seat for the activated license +& "c:\steps\return_license.ps1" diff --git a/dist/platforms/windows/steps/activate.ps1 b/dist/platforms/windows/steps/activate.ps1 new file mode 100644 index 00000000..9cd9ad3d --- /dev/null +++ b/dist/platforms/windows/steps/activate.ps1 @@ -0,0 +1,6 @@ +# Activates Unity +& "C:\Program Files\Unity\Hub\Editor\$Env:UNITY_VERSION\Editor\Unity.exe" -batchmode -quit -nographics ` + -username $Env:UNITY_USER ` + -password $Env:UNITY_PASS ` + -serial $Env:UNITY_SERIAL ` + -logfile | Out-Host diff --git a/dist/platforms/windows/steps/build.ps1 b/dist/platforms/windows/steps/build.ps1 new file mode 100644 index 00000000..a347b530 --- /dev/null +++ b/dist/platforms/windows/steps/build.ps1 @@ -0,0 +1,147 @@ +# +# Set project path +# +$Env:UNITY_PROJECT_PATH="$Env:GITHUB_WORKSPACE\$Env:PROJECT_PATH" +Write-Output "$('Using project path "')$($Env:UNITY_PROJECT_PATH)$('".')" + +# +# Display the name for the build, doubles as the output name +# + +Write-Output "$('Using build name "')$($Env:BUILD_NAME)$('".')" + +# +# Display the build's target platform; +# + +Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')" + +# +# Display build path and file +# + +Write-Output "$('Using build path "')$($Env:BUILD_PATH)$('" to save file "')$($Env:BUILD_FILE)$('".')" +$Env:BUILD_PATH_FULL="$Env:GITHUB_WORKSPACE\$Env:BUILD_PATH" +$Env:CUSTOM_BUILD_PATH="$Env:BUILD_PATH_FULL\$Env:BUILD_FILE" + +# +# Set the build method, must reference one of: +# +# - +# - +# +# For example: `BuildCommand.PerformBuild` +# +# The method must be declared static and placed in project/Assets/Editor +# +if ($Env:BUILD_METHOD) +{ + # User has provided their own build method. + # Assume they also bring their own script. + Write-Output "$('Using build method "')$($Env:BUILD_METHOD)$('".')" +} +else +{ + # User has not provided their own build command. + # + # Use the script from this action which builds the scenes that are enabled in + # the project. + # + Write-Output "Using built-in build method." + + # Create Editor directory if it does not exist + if(-Not (Test-Path -Path $Env:UNITY_PROJECT_PATH\Assets\Editor)) + { + # We use -Force to suppress output, doesn't overwrite anything + New-Item -ItemType Directory -Force -Path $Env:UNITY_PROJECT_PATH\Assets\Editor + } + + # Copy the build script of Unity Builder action + Copy-Item -Path "c:\UnityBuilderAction" -Destination $Env:UNITY_PROJECT_PATH\Assets\Editor -Recurse + + # Set the Build method to that of UnityBuilder Action + $Env:BUILD_METHOD="UnityBuilderAction.Builder.BuildProject" + + # Verify recursive paths + Get-ChildItem -Path $UNITY_PROJECT_PATH\Assets\Editor -Recurse +} + +# +# Pre-build debug information +# + +Write-Output "" +Write-Output "###########################" +Write-Output "# Custom parameters #" +Write-Output "###########################" +Write-Output "" + +Write-Output "$('"')$($Env:CUSTOM_PARAMETERS)$('"')" + +Write-Output "" +Write-Output "###########################" +Write-Output "# Current build dir #" +Write-Output "###########################" +Write-Output "" + +Write-Output "$('Creating "')$($Env:BUILD_PATH_FULL)$('" if it does not exist.')" +if (-Not (Test-Path -Path $Env:BUILD_PATH_FULL)) +{ + mkdir "$Env:BUILD_PATH_FULL" +} +Get-ChildItem $Env:BUILD_PATH_FULL + +Write-Output "" +Write-Output "###########################" +Write-Output "# Project directory #" +Write-Output "###########################" +Write-Output "" + +Get-ChildItem $Env:UNITY_PROJECT_PATH + +# +# Build +# + +Write-Output "" +Write-Output "###########################" +Write-Output "# Building project #" +Write-Output "###########################" +Write-Output "" + +& "C:\Program Files\Unity\Hub\Editor\$Env:UNITY_VERSION\Editor\Unity.exe" -quit -batchmode -nographics ` + -projectPath $Env:UNITY_PROJECT_PATH ` + -executeMethod $Env:BUILD_METHOD ` + -buildTarget $Env:BUILD_TARGET ` + -customBuildTarget $Env:BUILD_TARGET ` + -customBuildPath $Env:CUSTOM_BUILD_PATH ` + -buildVersion $Env:VERSION ` + $Env:CUSTOM_PARAMETERS ` + -logfile | Out-Host + +# Catch exit code +$Env:BUILD_EXIT_CODE=$? + +# Display results +if ($Env:BUILD_EXIT_CODE -eq 0) +{ + Write-Output "Build Succeeded!" +} else +{ + Write-Output "$('Build failed, with exit code ')$($Env:BUILD_EXIT_CODE)$('"')" +} + +# TODO: Determine if we need to set permissions on any files + +# +# Results +# + +Write-Output "" +Write-Output "###########################" +Write-Output "# Build output #" +Write-Output "###########################" +Write-Output "" + +Get-ChildItem $Env:BUILD_PATH_FULL +Write-Output "" \ No newline at end of file diff --git a/dist/platforms/windows/steps/return_license.ps1 b/dist/platforms/windows/steps/return_license.ps1 new file mode 100644 index 00000000..a284f847 --- /dev/null +++ b/dist/platforms/windows/steps/return_license.ps1 @@ -0,0 +1,6 @@ +# Returns the active Unity license +& "C:\Program Files\Unity\Hub\Editor\$Env:UNITY_VERSION\Editor\Unity.exe" -batchmode -quit -nographics ` + -username $Env:UNITY_USER ` + -password $Env:UNITY_PASS ` + -returnlicense ` + -logfile | Out-Host diff --git a/src/model/action.ts b/src/model/action.ts index b7703e8b..bee77336 100644 --- a/src/model/action.ts +++ b/src/model/action.ts @@ -2,7 +2,7 @@ import path from 'path'; class Action { static get supportedPlatforms() { - return ['linux']; + return ['linux', 'win32']; } static get isRunningLocally() { @@ -30,7 +30,16 @@ class Action { } static get dockerfile() { - return `${Action.actionFolder}/Dockerfile`; + const currentPlatform = process.platform; + switch(currentPlatform) + { + case "linux": + return `${Action.actionFolder}/platforms/ubuntu/Dockerfile`; + case "win32": + return `${Action.actionFolder}/platforms/windows/Dockerfile`; + default: + throw new Error(`No Dockerfile for currently unsupported platform: ${currentPlatform}`); + } } static get workspace() { diff --git a/src/model/docker.ts b/src/model/docker.ts index 29a8b9f0..3e73b0e3 100644 --- a/src/model/docker.ts +++ b/src/model/docker.ts @@ -1,5 +1,7 @@ import { exec } from '@actions/exec'; +import { fstat } from 'fs'; import ImageTag from './image-tag'; +const fs = require('fs'); class Docker { static async build(buildParameters, silent = false) { @@ -43,7 +45,10 @@ class Docker { chownFilesTo, } = parameters; - const command = `docker run \ + switch(process.platform) + { + case "linux": + const linuxRunCommand = `docker run \ --workdir /github/workspace \ --rm \ --env UNITY_LICENSE \ @@ -95,7 +100,133 @@ class Docker { ${sshAgent ? '--volume /home/runner/.ssh/known_hosts:/root/.ssh/known_hosts:ro' : ''} \ ${image}`; - await exec(command, undefined, { silent }); + await exec(linuxRunCommand, undefined, { silent }); + break; + case "win32": + var unitySerial = ""; + if (!process.env.UNITY_SERIAL) + { + //No serial was present so it is a personal license that we need to convert + if (!process.env.UNITY_LICENSE) + { + throw new Error(`Missing Unity License File and no Serial was found. If this + is a personal license, make sure to follow the activation + steps and set the UNITY_LICENSE GitHub secret or enter a Unity + serial number inside the UNITY_SERIAL GitHub secret.`) + } + unitySerial = this.getSerialFromLicenseFile(process.env.UNITY_LICENSE); + } else + { + unitySerial = process.env.UNITY_SERIAL!; + } + + if (!(process.env.UNITY_EMAIL && process.env.UNITY_PASSWORD)) + { + throw new Error(`Unity email and password must be set for windows based builds`); + } + + await this.setupWindowsRun(); + + this.validateWindowsPrereqs(); + + const windowsRunCommand = `docker run \ + --workdir c:/github/workspace \ + --rm \ + --env UNITY_LICENSE \ + --env UNITY_LICENSE_FILE \ + --env UNITY_EMAIL \ + --env UNITY_PASSWORD \ + --env UNITY_SERIAL="${unitySerial}" \ + --env UNITY_VERSION="${version}" \ + --env USYM_UPLOAD_AUTH_TOKEN \ + --env PROJECT_PATH="${projectPath}" \ + --env BUILD_TARGET="${platform}" \ + --env BUILD_NAME="${buildName}" \ + --env BUILD_PATH="${buildPath}" \ + --env BUILD_FILE="${buildFile}" \ + --env BUILD_METHOD="${buildMethod}" \ + --env VERSION="${buildVersion}" \ + --env ANDROID_VERSION_CODE="${androidVersionCode}" \ + --env ANDROID_KEYSTORE_NAME="${androidKeystoreName}" \ + --env ANDROID_KEYSTORE_BASE64="${androidKeystoreBase64}" \ + --env ANDROID_KEYSTORE_PASS="${androidKeystorePass}" \ + --env ANDROID_KEYALIAS_NAME="${androidKeyaliasName}" \ + --env ANDROID_KEYALIAS_PASS="${androidKeyaliasPass}" \ + --env ANDROID_TARGET_SDK_VERSION="${androidTargetSdkVersion}" \ + --env ANDROID_SDK_MANAGER_PARAMETERS="${androidSdkManagerParameters}" \ + --env CUSTOM_PARAMETERS="${customParameters}" \ + --env CHOWN_FILES_TO="${chownFilesTo}" \ + --env GITHUB_REF \ + --env GITHUB_SHA \ + --env GITHUB_REPOSITORY \ + --env GITHUB_ACTOR \ + --env GITHUB_WORKFLOW \ + --env GITHUB_HEAD_REF \ + --env GITHUB_BASE_REF \ + --env GITHUB_EVENT_NAME \ + --env GITHUB_WORKSPACE=/github/workspace \ + --env GITHUB_ACTION \ + --env GITHUB_EVENT_PATH \ + --env RUNNER_OS \ + --env RUNNER_TOOL_CACHE \ + --env RUNNER_TEMP \ + --env RUNNER_WORKSPACE \ + --env GIT_PRIVATE_TOKEN="${gitPrivateToken}" \ + --volume "${runnerTempPath}/_github_home":"c:/root" \ + --volume "${runnerTempPath}/_github_workflow":"c:/github/workflow" \ + --volume "${workspace}":"c:/github/workspace" \ + --volume "c:/regkeys":"c:/regkeys" \ + --volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \ + --volume "C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \ + --volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \ + ${image}`; + + await exec(windowsRunCommand, undefined, { silent }); + break; + default: + throw new Error(`Can't run docker on unsupported host platform`); + } + } + + //Setup prerequisite files for a windows-based docker run + static async setupWindowsRun(silent = false) { + //Need to export registry keys that point to the location of the windows 10 sdk + const makeRegKeyFolderCommand = "mkdir c:/regkeys"; + await exec(makeRegKeyFolderCommand, undefined, {silent}); + const exportRegKeysCommand = "echo Y| reg export \"HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0\" c:/regkeys/winsdk.reg"; + await exec(exportRegKeysCommand, undefined, {silent}); + } + + static validateWindowsPrereqs() { + //Check for Visual Studio on runner + if (!(fs.existsSync("C:/Program Files (x86)/Microsoft Visual Studio") && fs.existsSync("C:/ProgramData/Microsoft/VisualStudio"))) + { + throw new Error(`Visual Studio Installation not found at default location. + Make sure the runner has Visual Studio installed in the + default location`); + } + + //Check for Windows 10 SDK on runner + if(!fs.existsSync("C:/Program Files (x86)/Windows Kits")) + { + throw new Error(`Windows 10 SDK not found in default location. Make sure + the runner has a Windows 10 SDK installed in the default + location.`); + } + } + + static getSerialFromLicenseFile(license) + { + const startKey = ``; + let startIndex = license.indexOf(startKey) + startKey.length; + if (startIndex < 0) + { + throw new Error(`License File was corrupted, unable to locate serial`); + } + let endIndex = license.indexOf(endKey, startIndex); + //We substring off the first character as it is a garbage value + return atob(license.substring(startIndex, endIndex)).substring(1); } } diff --git a/src/model/image-tag.ts b/src/model/image-tag.ts index 5d070df2..b3f80d89 100644 --- a/src/model/image-tag.ts +++ b/src/model/image-tag.ts @@ -35,6 +35,7 @@ class ImageTag { webgl: 'webgl', mac: 'mac-mono', windows: 'windows-mono', + windowsIl2cpp: 'windows-il2cpp', linux: 'base', linuxIl2cpp: 'linux-il2cpp', android: 'android', @@ -44,7 +45,7 @@ class ImageTag { } static getTargetPlatformToImageSuffixMap(platform, version) { - const { generic, webgl, mac, windows, linux, linuxIl2cpp, android, ios, facebook } = ImageTag.imageSuffixes; + const { generic, webgl, mac, windows, windowsIl2cpp, linux, linuxIl2cpp, android, ios, facebook } = ImageTag.imageSuffixes; const [major, minor] = version.split('.').map((digit) => Number(digit)); // @see: https://docs.unity3d.com/ScriptReference/BuildTarget.html @@ -52,8 +53,32 @@ class ImageTag { case Platform.types.StandaloneOSX: return mac; case Platform.types.StandaloneWindows: + // Unity versions before 2019.3 do not support il2cpp + // Can only build windows-il2cpp on a windows based system + if (process.platform == "win32") + { + if (major >= 2020 || (major === 2019 && minor >= 3)) { + return windowsIl2cpp; + } else + { + throw new Error(`Windows-based builds are only supported on 2019.3.X+ versions of Unity. + If you are trying to build for windows-mono, please use a Linux based OS.`) + } + } return windows; case Platform.types.StandaloneWindows64: + // Unity versions before 2019.3 do not support il2cpp + // Can only build windows-il2cpp on a windows based system + if (process.platform == "win32") + { + if (major >= 2020 || (major === 2019 && minor >= 3)) { + return windowsIl2cpp; + } else + { + throw new Error(`Windows-based builds are only supported on 2019.3.X+ versions of Unity. + If you are trying to build for windows-mono, please use a Linux based OS.`) + } + } return windows; case Platform.types.StandaloneLinux64: { // Unity versions before 2019.3 do not support il2cpp @@ -101,7 +126,15 @@ class ImageTag { } get tag() { - return `${this.version}-${this.builderPlatform}`.replace(/-+$/, ''); + if (ImageTag.getTargetPlatformToImageSuffixMap(this.platform, this.version) === ImageTag.imageSuffixes.windowsIl2cpp) + { + //Windows based image tags are prefixed with windows- + return `windows-${this.version}-${this.builderPlatform}`.replace(/-+$/, ''); + } + else + { + return `${this.version}-${this.builderPlatform}`.replace(/-+$/, ''); + } } get image() {