|
| 1 | +import { |
| 2 | + UnityToolRunner, |
| 3 | + UnityPathTools, |
| 4 | + UnityVersionInfoResult, |
| 5 | + Utilities, |
| 6 | + UnityPackageManagerTools, |
| 7 | + UnityVersionTools, |
| 8 | +} from "@dinomite-studios/unity-azure-pipelines-tasks-lib"; |
| 9 | +import fs = require("fs-extra"); |
| 10 | +import tl = require("azure-pipelines-task-lib/task"); |
| 11 | +import path = require("path"); |
| 12 | +import { |
| 13 | + outputFileNameInputVariableName, |
| 14 | + unityProjectPathInputVariableName, |
| 15 | + versionSelectionModeVariableName, |
| 16 | + outputPathInputVariableName, |
| 17 | + unityEditorsPathModeInputVariableName, |
| 18 | + customUnityEditorsPathInputVariableName, |
| 19 | + versionInputVariableName, |
| 20 | + cleanBuildInputVariableName, |
| 21 | + buildFlowInputVariableName, |
| 22 | + buildTargetInputVariableName, |
| 23 | + buildProfileInputVariableName, |
| 24 | + additionalCmdArgsInputVariableName, |
| 25 | + buildScriptTypeInputVariableName, |
| 26 | + signAppBundleInputVariableName, |
| 27 | + keystoreNameInputVariableName, |
| 28 | + keystorePassInputVariableName, |
| 29 | + keystoreAliasNameInputVariableName, |
| 30 | + keystoreAliasPassInputVariableName, |
| 31 | + buildAppBundleInputVariableName, |
| 32 | + inlineBuildScriptInputVariableName, |
| 33 | + scriptExecuteMethodInputVariableName, |
| 34 | + editorLogFilePathOutputVariableName, |
| 35 | +} from "./variables"; |
| 36 | + |
| 37 | +export class UnityBuildProject { |
| 38 | + public static async run(): Promise<number> { |
| 39 | + // Setup and read inputs. |
| 40 | + const outputFileName = |
| 41 | + tl.getInput(outputFileNameInputVariableName) ?? "drop"; |
| 42 | + const projectPath = |
| 43 | + tl.getPathInput(unityProjectPathInputVariableName) ?? ""; |
| 44 | + const versionSelectionMode = tl.getInput( |
| 45 | + versionSelectionModeVariableName, |
| 46 | + true |
| 47 | + )!; |
| 48 | + const outputPath = tl.getPathInput(outputPathInputVariableName) ?? ""; |
| 49 | + const unityEditorsPath = UnityPathTools.getUnityEditorsPath( |
| 50 | + tl.getInput(unityEditorsPathModeInputVariableName, true)!, |
| 51 | + tl.getInput(customUnityEditorsPathInputVariableName) |
| 52 | + ); |
| 53 | + |
| 54 | + let unityVersion: UnityVersionInfoResult; |
| 55 | + if (versionSelectionMode === "specify") { |
| 56 | + let customVersion = tl.getInput(versionInputVariableName, true)!; |
| 57 | + unityVersion = { |
| 58 | + info: { |
| 59 | + isAlpha: false, |
| 60 | + isBeta: false, |
| 61 | + version: customVersion, |
| 62 | + revision: undefined, |
| 63 | + }, |
| 64 | + error: undefined, |
| 65 | + }; |
| 66 | + } else { |
| 67 | + unityVersion = getUnityEditorVersion(); |
| 68 | + } |
| 69 | + |
| 70 | + const unityExecutablePath = UnityPathTools.getUnityExecutableFullPath( |
| 71 | + unityEditorsPath, |
| 72 | + unityVersion.info! |
| 73 | + ); |
| 74 | + const cleanBuild = tl.getVariable(cleanBuildInputVariableName); |
| 75 | + |
| 76 | + // Set output variable values. |
| 77 | + const logFilesDirectory = path.join( |
| 78 | + tl.getVariable("Agent.TempDirectory")!, |
| 79 | + "Logs" |
| 80 | + ); |
| 81 | + const logFilePath = path.join( |
| 82 | + logFilesDirectory, |
| 83 | + `UnityBuildLog_${Utilities.getLogFileNameTimeStamp()}.log` |
| 84 | + ); |
| 85 | + tl.setVariable(editorLogFilePathOutputVariableName, logFilePath); |
| 86 | + |
| 87 | + // If clean was specified by the user, delete the existing output directory, if it exists |
| 88 | + if (cleanBuild === "true") { |
| 89 | + fs.removeSync(outputPath); |
| 90 | + } |
| 91 | + |
| 92 | + // No matter if clean build or not, make sure the output diretory exists |
| 93 | + tl.mkdirP(outputPath); |
| 94 | + tl.checkPath(outputPath, "Build Output Directory"); |
| 95 | + |
| 96 | + // Execute Unity command line. |
| 97 | + const buildFlow = tl.getInput(buildFlowInputVariableName) ?? "platform"; |
| 98 | + const unityCmd = tl |
| 99 | + .tool(unityExecutablePath) |
| 100 | + .arg("-batchmode") |
| 101 | + .arg(buildFlow === "platform" ? "-buildTarget" : "-activeBuildProfile") |
| 102 | + .arg( |
| 103 | + tl.getInput( |
| 104 | + buildFlow === "platform" |
| 105 | + ? buildTargetInputVariableName |
| 106 | + : buildProfileInputVariableName, |
| 107 | + true |
| 108 | + )! |
| 109 | + ) |
| 110 | + .arg("-projectPath") |
| 111 | + .arg(projectPath) |
| 112 | + .arg("-logfile") |
| 113 | + .arg(logFilePath); |
| 114 | + |
| 115 | + const additionalArgs = |
| 116 | + tl.getInput(additionalCmdArgsInputVariableName) ?? ""; |
| 117 | + if (additionalArgs !== "") { |
| 118 | + unityCmd.line(additionalArgs); |
| 119 | + } |
| 120 | + |
| 121 | + // Perform setup depending on build script type selected |
| 122 | + const buildScriptType = |
| 123 | + tl.getInput(buildScriptTypeInputVariableName) ?? "default"; |
| 124 | + |
| 125 | + if (buildScriptType === "default") { |
| 126 | + // When using default build scripts we rely on a Utility package being installed to the project via the Unity Package Manager. |
| 127 | + // By adding it to the manifest before opening the project, Unity will load the package before trying to build the project. |
| 128 | + UnityPackageManagerTools.addPackageToProject( |
| 129 | + projectPath, |
| 130 | + "games.dinomite.azurepipelines", |
| 131 | + "https://github.com/Dinomite-Studios/games.dinomite.azurepipelines.git#v1.0.14" |
| 132 | + ); |
| 133 | + unityCmd.arg("-executeMethod").arg("AzurePipelinesBuild.PerformBuild"); |
| 134 | + unityCmd.arg("-outputFileName").arg(outputFileName); |
| 135 | + unityCmd.arg("-outputPath").arg(outputPath); |
| 136 | + |
| 137 | + if (tl.getBoolInput(signAppBundleInputVariableName)) { |
| 138 | + unityCmd |
| 139 | + .arg("-keystoreName") |
| 140 | + .arg(tl.getPathInput(keystoreNameInputVariableName) ?? ""); |
| 141 | + unityCmd |
| 142 | + .arg("-keystorePass") |
| 143 | + .arg(tl.getInput(keystorePassInputVariableName) ?? ""); |
| 144 | + unityCmd |
| 145 | + .arg("-keystoreAliasName") |
| 146 | + .arg(tl.getInput(keystoreAliasNameInputVariableName) ?? ""); |
| 147 | + |
| 148 | + // The alias password is optional and should only be passed, if not empty or undefined. |
| 149 | + const keystoreAliasPass = |
| 150 | + tl.getInput(keystoreAliasPassInputVariableName) ?? ""; |
| 151 | + if (keystoreAliasPass) { |
| 152 | + unityCmd.arg("-keystoreAliasPass").arg(keystoreAliasPass); |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + if (tl.getBoolInput(buildAppBundleInputVariableName)) { |
| 157 | + unityCmd.arg("-buildAppBundle"); |
| 158 | + } |
| 159 | + } else if (buildScriptType === "inline") { |
| 160 | + // Create a C# script file in a Editor folder at the root Assets directory level. Then write |
| 161 | + // the default or the user's script into it. Unity will then compile it on launch and make sure it's available. |
| 162 | + const projectAssetsEditorFolderPath = path.join( |
| 163 | + `${projectPath}`, |
| 164 | + "Assets", |
| 165 | + "Editor" |
| 166 | + ); |
| 167 | + tl.mkdirP(projectAssetsEditorFolderPath); |
| 168 | + tl.cd(projectAssetsEditorFolderPath); |
| 169 | + tl.writeFile( |
| 170 | + "AzureDevOps.cs", |
| 171 | + tl.getInput(inlineBuildScriptInputVariableName)! |
| 172 | + ); |
| 173 | + tl.cd(projectPath); |
| 174 | + |
| 175 | + // Tell Unity which method to execute for build. |
| 176 | + unityCmd |
| 177 | + .arg("-executeMethod") |
| 178 | + .arg(tl.getInput(scriptExecuteMethodInputVariableName)!); |
| 179 | + } else if (buildScriptType === "existing") { |
| 180 | + // If the user already has an existing build script we only need the method to execute. |
| 181 | + unityCmd |
| 182 | + .arg("-executeMethod") |
| 183 | + .arg(tl.getInput(scriptExecuteMethodInputVariableName)!) |
| 184 | + .arg("-quit"); |
| 185 | + } else { |
| 186 | + throw `Unsupported build script type ${buildScriptType}`; |
| 187 | + } |
| 188 | + |
| 189 | + const result = await UnityToolRunner.run(unityCmd, logFilePath); |
| 190 | + return result; |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +function getUnityEditorVersion(): UnityVersionInfoResult { |
| 195 | + const projectPath = tl.getPathInput("unityProjectPath") ?? ""; |
| 196 | + console.log(`${tl.loc("projectPathInfo")} ${projectPath}`); |
| 197 | + |
| 198 | + const unityVersion = |
| 199 | + UnityVersionTools.determineProjectVersionFromFile(projectPath); |
| 200 | + if (unityVersion.error) { |
| 201 | + const error = `${tl.loc("failGetUnityEditorVersion")} | ${ |
| 202 | + unityVersion.error |
| 203 | + }`; |
| 204 | + console.error(error); |
| 205 | + throw new Error(error); |
| 206 | + } |
| 207 | + |
| 208 | + const successGetVersionLog = `${tl.loc("successGetUnityEditorVersion")} ${ |
| 209 | + unityVersion.info!.version |
| 210 | + }${ |
| 211 | + unityVersion.info!.revision |
| 212 | + ? `, revision=${unityVersion.info!.revision}` |
| 213 | + : "" |
| 214 | + }, alpha=${unityVersion.info!.isAlpha}, beta=${unityVersion.info!.isBeta}`; |
| 215 | + console.log(successGetVersionLog); |
| 216 | + |
| 217 | + if (unityVersion.info!.isAlpha || unityVersion.info!.isBeta) { |
| 218 | + console.warn(tl.loc("warningAlphaBetaVersion")); |
| 219 | + } |
| 220 | + |
| 221 | + return unityVersion; |
| 222 | +} |
0 commit comments