diff --git a/dist/index.js b/dist/index.js index 02e4d42b..0e24c3e9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -14309,7 +14309,8 @@ class System { return new Promise((resolve5, reject) => { const proc = spawn(shell, shellArgs, { cwd: options.cwd, - stdio: ["inherit", "pipe", "pipe"] + stdio: ["inherit", "pipe", "pipe"], + env: options.env ? { ...process.env, ...options.env } : process.env }); const runResult = { output: "", error: "" }; proc.stdout.on("data", (chunk) => { @@ -16815,12 +16816,33 @@ class PlatformSetup { // src/model/mac-builder.ts init_system(); init_unity_build_validation(); +init_image_environment_factory(); +init_environment(); class MacBuilder { + static buildEnv(options) { + const { currentWorkDir, cliDistPath } = options; + const extraVariables = options.engine === "unity" ? UnityEnvironment.getVariables(options) : []; + const variables = ImageEnvironmentFactory.getEnvironmentVariables(options, extraVariables); + const env3 = {}; + for (const { name, value } of variables) { + if (value === "" || value === undefined) + continue; + env3[name] = value.toString(); + } + if (currentWorkDir) + env3.GITHUB_WORKSPACE = currentWorkDir; + if (cliDistPath) + env3.ACTION_FOLDER = cliDistPath; + return env3; + } static async run(options, silent = false) { const { cliDistPath, engine } = options; log.warning("running the process"); - const macRun = await System.run(`bash ${cliDistPath}/platforms/mac/entrypoint.sh`, undefined, { silent }); + const macRun = await System.run(`bash ${cliDistPath}/platforms/mac/entrypoint.sh`, undefined, { + silent, + env: MacBuilder.buildEnv(options) + }); switch (engine) { case "unity": UnityBuildValidation.validateBuild(macRun.output); @@ -17683,6 +17705,11 @@ class UnityCliAdapter { const result = await System.run(cliCommand, undefined, { silent: true }); return { success: result.status?.success ?? false, output: result.output }; } + static async test(extraArgs = []) { + const cliCommand = ["unity", "test", ...extraArgs].join(" "); + const result = await System.run(cliCommand, undefined, { silent: true }); + return { success: result.status?.success ?? false, output: result.output }; + } } // src/command-options/unity-run-options.ts @@ -17727,6 +17754,43 @@ class UnityRunCommand extends CommandBase { } } +// src/command-options/unity-test-options.ts +class UnityTestOptions { + static configure(yargs) { + yargs.option("unityCliArgs", { + description: String.dedent` + Raw arguments passed through to \`unity test\` verbatim, space-separated. + Unity's own CLI reference doesn't publish a flag table for this command; + run \`unity test --help\` on the installed binary for the authoritative + list (see docs.unity.com/en-us/unity-cli).`, + type: "string", + demandOption: false, + default: "" + }); + } +} + +// src/command/test/unity-test-command.ts +class UnityTestCommand extends CommandBase { + async execute(options) { + const extraArgs = String(options.unityCliArgs || "").split(" ").map((arg) => arg.trim()).filter(Boolean); + const available = await UnityCliAdapter.isAvailable(); + if (!available) { + throw new Error("test: requires Unity's official `unity` CLI binary on PATH " + "(https://docs.unity.com/en-us/unity-cli). Not found in this environment."); + } + try { + const result = await UnityCliAdapter.test(extraArgs); + log.info(result.output); + return result.success; + } catch (error) { + throw new Error(`test: 'unity test' failed: ${error.message}`); + } + } + async configureOptions(yargs) { + await UnityTestOptions.configure(yargs); + } +} + // src/plugin/builtin/unity-plugin.ts var unityPlugin = { name: "unity", @@ -17754,6 +17818,8 @@ var unityPlugin = { return new UnityOrchestrateCommand(command2); case "run": return new UnityRunCommand(command2); + case "test": + return new UnityTestCommand(command2); case "remote": switch (subCommands[0]) { case "run": diff --git a/dist/platforms/mac/steps/activate.sh b/dist/platforms/mac/steps/activate.sh index c9511eb5..6a2ffa95 100644 --- a/dist/platforms/mac/steps/activate.sh +++ b/dist/platforms/mac/steps/activate.sh @@ -4,21 +4,59 @@ echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory." pushd "$ACTIVATE_LICENSE_PATH" -echo "Requesting activation" - -# Activate license -/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \ - -logFile - \ - -batchmode \ - -nographics \ - -quit \ - -serial "$UNITY_SERIAL" \ - -username "$UNITY_EMAIL" \ - -password "$UNITY_PASSWORD" \ - -projectPath "$ACTIVATE_LICENSE_PATH" - -# Store the exit code from the verify command -UNITY_EXIT_CODE=$? +if [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then + # + # SERIAL LICENSE MODE + # + echo "Requesting activation" + + # Activate license + /Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \ + -logFile - \ + -batchmode \ + -nographics \ + -quit \ + -serial "$UNITY_SERIAL" \ + -username "$UNITY_EMAIL" \ + -password "$UNITY_PASSWORD" \ + -projectPath "$ACTIVATE_LICENSE_PATH" + + # Store the exit code from the verify command + UNITY_EXIT_CODE=$? +elif [[ -n "$UNITY_LICENSING_SERVER" ]]; then + # + # Custom Unity License Server + # + # This platform previously had no floating-license support at all - + # UNITY_LICENSING_SERVER was silently ignored and activation always + # attempted (empty) serial mode instead (game-ci/cli, found while + # auditing for divergence from unity-builder's real source). + echo "Requesting floating license" + + /Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/Frameworks/UnityLicensingClient.app/Contents/MacOS/Unity.Licensing.Client \ + --acquire-floating > license.txt + UNITY_EXIT_CODE=$? + + if [ $UNITY_EXIT_CODE -eq 0 ]; then + PARSEDFILE=$(grep -oE '\"[^"]*\"' < license.txt | tr -d '"') + export FLOATING_LICENSE + FLOATING_LICENSE=$(sed -n 2p <<< "$PARSEDFILE") + FLOATING_LICENSE_TIMEOUT=$(sed -n 4p <<< "$PARSEDFILE") + + echo "Acquired floating license: \"$FLOATING_LICENSE\" with timeout $FLOATING_LICENSE_TIMEOUT" + fi +else + # + # NO LICENSE ACTIVATION STRATEGY MATCHED + # + echo "License activation strategy could not be determined." + echo "" + echo "Visit https://game.ci/docs/github/activation for more" + echo "details on how to set up one of the possible activation strategies." + + # Immediately exit as no UNITY_EXIT_CODE can be derived. + exit 1; +fi # # Display information about the result diff --git a/src/model/mac-builder.test.ts b/src/model/mac-builder.test.ts new file mode 100644 index 00000000..34c6c1a7 --- /dev/null +++ b/src/model/mac-builder.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'bun:test'; +import { MacBuilder } from './mac-builder.ts'; + +describe('MacBuilder', () => { + describe('buildEnv (private, accessed via any-cast)', () => { + const buildEnv = (options: any): Record => (MacBuilder as any).buildEnv(options); + + it('includes generic build options as env vars', () => { + const env = buildEnv({ + currentWorkDir: '/Users/runner/work/repo/repo', + cliDistPath: '/Users/runner/work/repo/repo/dist', + projectPath: 'test-project', + targetPlatform: 'StandaloneOSX', + buildName: 'StandaloneOSX', + }); + + expect(env.PROJECT_PATH).toBe('test-project'); + expect(env.BUILD_TARGET).toBe('StandaloneOSX'); + expect(env.BUILD_NAME).toBe('StandaloneOSX'); + }); + + it('sets GITHUB_WORKSPACE and ACTION_FOLDER from currentWorkDir/cliDistPath', () => { + const env = buildEnv({ + currentWorkDir: '/Users/runner/work/repo/repo', + cliDistPath: '/Users/runner/work/repo/repo/dist', + }); + + expect(env.GITHUB_WORKSPACE).toBe('/Users/runner/work/repo/repo'); + expect(env.ACTION_FOLDER).toBe('/Users/runner/work/repo/repo/dist'); + }); + + it('includes Unity-specific env vars only when engine is unity', () => { + const unityEnv = buildEnv({ engine: 'unity', unitySerial: 'F4-1234-1234-1234' }); + expect(unityEnv.UNITY_SERIAL).toBe('F4-1234-1234-1234'); + + const godotEnv = buildEnv({ engine: 'godot', unitySerial: 'F4-1234-1234-1234' }); + expect(godotEnv.UNITY_SERIAL).toBeUndefined(); + }); + + it('omits empty/undefined values entirely', () => { + const env = buildEnv({ projectPath: '', buildName: undefined }); + expect('PROJECT_PATH' in env).toBe(false); + expect('BUILD_NAME' in env).toBe(false); + }); + }); +}); diff --git a/src/model/mac-builder.ts b/src/model/mac-builder.ts index af55bb7a..be4da7bf 100644 --- a/src/model/mac-builder.ts +++ b/src/model/mac-builder.ts @@ -1,12 +1,41 @@ import type { Options } from '../dependencies.ts'; import { System } from './system/system.ts'; import { UnityBuildValidation } from './unity/build-validation/unity-build-validation.ts'; +import { ImageEnvironmentFactory } from './image-environment-factory.ts'; +import { UnityEnvironment } from '../logic/unity/environment.ts'; class MacBuilder { + /** + * Native mac builds run entrypoint.sh as a plain child process (no Docker + * --env flags), so parsed CLI options were never actually reaching the + * build - only vars a user separately exported in their shell mattered. + * Reuses the same env var set Docker builds already send, this time + * passed directly via System.run's `env` option. + */ + private static buildEnv(options: Options): Record { + const { currentWorkDir, cliDistPath } = options; + const extraVariables = options.engine === 'unity' ? UnityEnvironment.getVariables(options) : []; + const variables = ImageEnvironmentFactory.getEnvironmentVariables(options, extraVariables); + const env: Record = {}; + for (const { name, value } of variables) { + if (value === '' || value === undefined) continue; + env[name] = value.toString(); + } + // Not part of ImageEnvironmentFactory's set - Docker builds get these via + // explicit --env flags / volume-mount paths instead; the native mac path + // needs the real host values since there's no container remapping. + if (currentWorkDir) env.GITHUB_WORKSPACE = currentWorkDir; + if (cliDistPath) env.ACTION_FOLDER = cliDistPath; + return env; + } + public static async run(options: Options, silent = false) { const { cliDistPath, engine } = options; log.warning('running the process'); - const macRun = await System.run(`bash ${cliDistPath}/platforms/mac/entrypoint.sh`, undefined, { silent }); + const macRun = await System.run(`bash ${cliDistPath}/platforms/mac/entrypoint.sh`, undefined, { + silent, + env: MacBuilder.buildEnv(options), + }); switch (engine) { case 'unity': diff --git a/src/model/system/system.test.ts b/src/model/system/system.test.ts index cbfc5c34..18af0e0d 100644 --- a/src/model/system/system.test.ts +++ b/src/model/system/system.test.ts @@ -4,9 +4,35 @@ // Skipping until rewritten with bun:test mock APIs. import { describe, test, expect } from 'bun:test'; +import { System } from './system.ts'; describe.skip('System (unit)', () => { test('placeholder', () => { expect(true).toBe(true); }); }); + +describe('System.run env option', () => { + // Uses `node -e` instead of shell-native env var syntax (`$VAR` vs `$env:VAR`) + // so this works identically under sh (Linux/Mac) and powershell (Windows). + const printEnvVarCommand = (name: string) => `node -e "process.stdout.write(process.env.${name} || '')"`; + + test('merges options.env on top of the current process env for the spawned command', async () => { + const result = await System.run(printEnvVarCommand('SOME_TEST_VAR'), undefined, { + silent: true, + env: { SOME_TEST_VAR: 'from-options-env' }, + }); + + expect(result.output.trim()).toBe('from-options-env'); + }); + + test('inherits the current process env when options.env is not given', async () => { + process.env.SOME_INHERITED_VAR = 'inherited-value'; + try { + const result = await System.run(printEnvVarCommand('SOME_INHERITED_VAR'), undefined, { silent: true }); + expect(result.output.trim()).toBe('inherited-value'); + } finally { + delete process.env.SOME_INHERITED_VAR; + } + }); +}); diff --git a/src/model/system/system.ts b/src/model/system/system.ts index 622fdce3..91f7f4d8 100644 --- a/src/model/system/system.ts +++ b/src/model/system/system.ts @@ -3,6 +3,8 @@ import { spawn } from 'node:child_process'; export interface RunOptions { cwd?: string; silent?: boolean; + /** Extra env vars merged on top of the current process's env for the spawned command. */ + env?: Record; } export interface RunResult { @@ -52,6 +54,7 @@ class System { const proc = spawn(shell, shellArgs, { cwd: options.cwd, stdio: ['inherit', 'pipe', 'pipe'], + env: options.env ? { ...process.env, ...options.env } : process.env, }); const runResult: RunResult = { output: '', error: '' };