Files
unity-builder/src/model/input.ts
frostebite dd95ad9604 fix(licensing): add unityLicensingToolset input + fix HOME/USER under runAsHostUser
Addresses #739. Two opt-in, backwards-compatible changes for users on Linux
Docker builds with a Unity floating-license server:

1. Floating license servers that host multiple toolsets had no way to tell
   the Licensing Client which toolset to request — Unity could fall through
   to an entitlement that lacks build-target support (e.g. Android), then
   silently produce a Linux Standalone artifact. The action now accepts an
   optional unityLicensingToolset input that is written into
   services-config.json. When unset, the rendered config is byte-for-byte
   identical to before.

2. With runAsHostUser: true, su was invoked without explicit HOME/USER, so
   the host user inherited root's environment (HOME=/root, USER unset). The
   Unity Licensing Client, which writes to ~/.config/unity3d, could not
   resolve a writable home directory, leading to intermittent license
   activation failures. Set HOME/USER/LOGNAME explicitly before sourcing
   build steps. Change lives entirely inside the existing runAsHostUser
   branch.
2026-05-07 20:00:10 +01:00

311 lines
7.8 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
import { PluginOptions } from './plugin-options';
import Platform from './platform';
import GitHub from './github';
import os from 'node:os';
import * as core from '@actions/core';
export type InputKey = keyof typeof Input;
/**
* Input variables specified in workflows using "with" prop.
*
* Note that input is always passed as a string, even booleans.
*
* Only core build inputs belong here. Orchestrator/plugin inputs are read
* directly by the @game-ci/orchestrator plugin via core.getInput() / env vars.
*/
class Input {
public static getInput(query: string): string | undefined {
if (GitHub.githubInputEnabled) {
const coreInput = core.getInput(query);
if (coreInput && coreInput !== '') {
return coreInput;
}
}
const alternativeQuery = Input.ToEnvVarFormat(query);
// Query input sources
if (PluginOptions.query(query, alternativeQuery)) {
return PluginOptions.query(query, alternativeQuery);
}
if (process.env[query] !== undefined) {
return process.env[query]!;
}
if (alternativeQuery !== query && process.env[alternativeQuery] !== undefined) {
return process.env[alternativeQuery]!;
}
}
static get githubRepo(): string | undefined {
return Input.getInput('GITHUB_REPOSITORY') ?? Input.getInput('GITHUB_REPO') ?? undefined;
}
static get branch(): string {
if (Input.getInput(`GITHUB_REF`)) {
return Input.getInput(`GITHUB_REF`)!
.replace('refs/', '')
.replace(`head/`, '')
.replace(`heads/`, '');
} else if (Input.getInput('branch')) {
return Input.getInput('branch')!;
} else {
return '';
}
}
static get gitSha(): string {
if (Input.getInput(`GITHUB_SHA`)) {
return Input.getInput(`GITHUB_SHA`)!;
} else if (Input.getInput(`GitSHA`)) {
return Input.getInput(`GitSHA`)!;
}
return '';
}
static get runNumber(): string {
return Input.getInput('GITHUB_RUN_NUMBER') ?? '0';
}
static get targetPlatform(): string {
return Input.getInput('targetPlatform') ?? Platform.default;
}
static get unityVersion(): string {
return Input.getInput('unityVersion') ?? 'auto';
}
static get customImage(): string {
return Input.getInput('customImage') ?? '';
}
static get projectPath(): string {
const input = Input.getInput('projectPath');
let rawProjectPath;
if (input) {
rawProjectPath = input;
} else if (
fs.existsSync(path.join('test-project', 'ProjectSettings', 'ProjectVersion.txt')) &&
!fs.existsSync(path.join('ProjectSettings', 'ProjectVersion.txt'))
) {
rawProjectPath = 'test-project';
} else {
rawProjectPath = '.';
}
return rawProjectPath.replace(/\/$/, '');
}
static get buildProfile(): string {
return Input.getInput('buildProfile') ?? '';
}
static get runnerTempPath(): string {
return Input.getInput('RUNNER_TEMP') ?? '';
}
static get buildName(): string {
return Input.getInput('buildName') ?? Input.targetPlatform;
}
static get buildsPath(): string {
return Input.getInput('buildsPath') ?? 'build';
}
static get unityLicensingServer(): string {
return Input.getInput('unityLicensingServer') ?? '';
}
static get unityLicensingToolset(): string {
return Input.getInput('unityLicensingToolset') ?? '';
}
static get buildMethod(): string {
return Input.getInput('buildMethod') ?? ''; // Processed in docker file
}
static get manualExit(): boolean {
const input = Input.getInput('manualExit') ?? false;
return input === 'true';
}
static get enableGpu(): boolean {
const input = Input.getInput('enableGpu') ?? false;
return input === 'true';
}
static get customParameters(): string {
return Input.getInput('customParameters') ?? '';
}
static get useHostNetwork(): boolean {
const input = Input.getInput('useHostNetwork') ?? false;
return input === 'true';
}
static get versioningStrategy(): string {
return Input.getInput('versioning') ?? 'Semantic';
}
static get specifiedVersion(): string {
return Input.getInput('version') ?? '';
}
static get androidVersionCode(): string {
return Input.getInput('androidVersionCode') ?? '';
}
static get androidExportType(): string {
return Input.getInput('androidExportType') ?? 'androidPackage';
}
static get androidKeystoreName(): string {
return Input.getInput('androidKeystoreName') ?? '';
}
static get androidKeystoreBase64(): string {
return Input.getInput('androidKeystoreBase64') ?? '';
}
static get androidKeystorePass(): string {
return Input.getInput('androidKeystorePass') ?? '';
}
static get androidKeyaliasName(): string {
return Input.getInput('androidKeyaliasName') ?? '';
}
static get androidKeyaliasPass(): string {
return Input.getInput('androidKeyaliasPass') ?? '';
}
static get androidTargetSdkVersion(): string {
return Input.getInput('androidTargetSdkVersion') ?? '';
}
static get androidSymbolType(): string {
return Input.getInput('androidSymbolType') ?? 'none';
}
static get sshAgent(): string {
return Input.getInput('sshAgent') ?? '';
}
static get sshPublicKeysDirectoryPath(): string {
return Input.getInput('sshPublicKeysDirectoryPath') ?? '';
}
static get gitPrivateToken(): string | undefined {
return Input.getInput('gitPrivateToken');
}
static get runAsHostUser(): string {
return Input.getInput('runAsHostUser')?.toLowerCase() ?? 'false';
}
static get chownFilesTo() {
return Input.getInput('chownFilesTo') ?? '';
}
static get allowDirtyBuild(): boolean {
const input = Input.getInput('allowDirtyBuild') ?? false;
return input === 'true';
}
static get cacheUnityInstallationOnMac(): boolean {
const input = Input.getInput('cacheUnityInstallationOnMac') ?? false;
return input === 'true';
}
static get unityHubVersionOnMac(): string {
const input = Input.getInput('unityHubVersionOnMac') ?? '';
return input !== '' ? input : '';
}
static get unitySerial(): string | undefined {
return Input.getInput('UNITY_SERIAL');
}
static get unityLicense(): string | undefined {
return Input.getInput('UNITY_LICENSE');
}
static get dockerWorkspacePath(): string {
return Input.getInput('dockerWorkspacePath') ?? '/github/workspace';
}
static get dockerCpuLimit(): string {
return Input.getInput('dockerCpuLimit') ?? os.cpus().length.toString();
}
static get dockerMemoryLimit(): string {
const bytesInMegabyte = 1024 * 1024;
let memoryMultiplier;
switch (os.platform()) {
case 'linux':
memoryMultiplier = 0.95;
break;
case 'win32':
memoryMultiplier = 0.8;
break;
default:
memoryMultiplier = 0.75;
break;
}
return (
Input.getInput('dockerMemoryLimit') ??
`${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`
);
}
static get dockerIsolationMode(): string {
return Input.getInput('dockerIsolationMode') ?? 'default';
}
static get containerRegistryRepository(): string {
return Input.getInput('containerRegistryRepository') ?? 'unityci/editor';
}
static get containerRegistryImageVersion(): string {
return Input.getInput('containerRegistryImageVersion') ?? '3';
}
static get skipActivation(): string {
return Input.getInput('skipActivation')?.toLowerCase() ?? 'false';
}
static get linux64RemoveExecutableExtension(): boolean {
const input = Input.getInput('linux64RemoveExecutableExtension') ?? 'false';
return input === 'true';
}
public static ToEnvVarFormat(input: string) {
if (input.toUpperCase() === input) {
return input;
}
return input
.replace(/([A-Z])/g, ' $1')
.trim()
.toUpperCase()
.replace(/ /g, '_');
}
}
export default Input;