Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/model/docker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ describe("Docker", () => {
expect(command).toContain('--env UNITY_LICENSE="ci-stub-license"');
// The line-continuation strip (` \\\n` -> space) must not eat an escaped backslash at the end of a value.
expect(command).toContain('--env CUSTOM_PARAMETERS="trailing backslash \\\\" --env');
expect(command).toContain('--volume "/home/runner":"/root:z"');
expect(command).toContain('--volume "/home/runner/work/cli/cli":"/github/workspace:z"');
expect(command).toContain('--volume "/home/runner:/root:z"');
expect(command).toContain('--volume "/home/runner/work/cli/cli:/github/workspace:z"');
expect(command).toContain("game-ci/unity-editor-stub:latest");
expect(command).toContain("/bin/bash /entrypoint.sh");
expect(command).not.toContain("\n");
Expand Down Expand Up @@ -376,7 +376,7 @@ describe("Docker", () => {
engine: "unity",
});

expect(command).toContain("--volume /home/runner/.ssh/keys:/root/.ssh:ro");
expect(command).toContain('--volume "/home/runner/.ssh/keys:/root/.ssh:ro"');
expect(command).not.toContain("known_hosts");
});

Expand All @@ -393,7 +393,26 @@ describe("Docker", () => {
engine: "unity",
});

expect(command).toContain("--volume /home/runner/.ssh/keys:/root/.ssh:ro");
expect(command).toContain('--volume "/home/runner/.ssh/keys:/root/.ssh:ro"');
});

it("quotes Linux volume mounts completely so paths containing spaces remain a single argument on Windows/PowerShell", () => {
const command = (Docker as any).getLinuxCommand("game-ci/unity-editor-stub:latest", {
hostOS: "windows",
currentWorkDir: "C:/Program Files/GitHub/cli",
homeDir: "C:/Program Files/Users/runner",
cliDistPath: "C:/Program Files/GitHub/cli/dist",
sshAgent: "C:/Program Files/ssh/agent.sock",
sshPublicKeysDirectoryPath: "C:/Program Files/ssh/keys",
gitPrivateToken: "",
dockerWorkspacePath: "/github/workspace",
engine: "unity",
});

expect(command).toContain('--volume "C:/Program Files/Users/runner:/root:z"');
expect(command).toContain('--volume "C:/Program Files/GitHub/cli:/github/workspace:z"');
expect(command).toContain('--volume "C:/Program Files/ssh/agent.sock:/ssh-agent"');
expect(command).toContain('--volume "C:/Program Files/ssh/keys:/root/.ssh:ro"');
});

it("applies docker resource limits and isolation mode on Windows", () => {
Expand Down
10 changes: 5 additions & 5 deletions src/model/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ class Docker {
dockerMemoryLimit ? `--memory=${dockerMemoryLimit}` : "",
resolveShmSize(dockerShmSize) ? `--shm-size=${resolveShmSize(dockerShmSize)}` : "",
useHostNetwork ? "--net=host" : "",
`--volume "${home}":"/root:z"`,
`--volume "${currentWorkDir}":"${dockerWorkspacePath}:z"`,
`--volume "${home}:/root:z"`,
`--volume "${currentWorkDir}:${dockerWorkspacePath}:z"`,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
isUnityDefaultFlow ? `--volume "${cliDistPath}/default-build-script:/UnityBuilderAction:z"` : "",
// Mounted for BOTH flows, unlike default-build-script: the settings
// applier has to be available even when the user brings their own
Expand All @@ -301,9 +301,9 @@ class Docker {
isUnityDefaultFlow && runTests
? `--volume "${cliDistPath}/test-standalone-scripts:/UnityTestRunnerAction:z"`
: "",
sshAgent ? `--volume ${sshAgent}:/ssh-agent` : "",
sshAgent && !sshPublicKeysDirectoryPath ? "--volume /home/runner/.ssh/known_hosts:/root/.ssh/known_hosts:ro" : "",
sshPublicKeysDirectoryPath ? `--volume ${sshPublicKeysDirectoryPath}:/root/.ssh:ro` : "",
sshAgent ? `--volume "${sshAgent}:/ssh-agent"` : "",
sshAgent && !sshPublicKeysDirectoryPath ? '--volume "/home/runner/.ssh/known_hosts:/root/.ssh/known_hosts:ro"' : "",
sshPublicKeysDirectoryPath ? `--volume "${sshPublicKeysDirectoryPath}:/root/.ssh:ro"` : "",
image,
isUnityDefaultFlow ? "/bin/bash /entrypoint.sh" : wrappedCommands!,
]
Expand Down
70 changes: 70 additions & 0 deletions src/model/unity/runner/runner-image-tag.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,74 @@ describe('RunnerImageTag', () => {
expect(image.imageRollingVersion).toStrictEqual(5);
});
});

describe('cross-OS container (Linux containers on Windows host)', () => {
it('uses ubuntu prefix when hostOS is linux, even on win32 hostPlatform', () => {
const image = new RunnerImageTag({
engineVersion: '6000.3.2f1',
targetPlatform: 'StandaloneWindows64',
hostPlatform: 'win32',
hostOS: 'linux',
});

expect(image.toString()).toContain('ubuntu-');
expect(image.toString()).not.toContain('windows-6000');
});

it('selects windows-mono (not windows-il2cpp) for StandaloneWindows64 in a Linux container', () => {
const image = new RunnerImageTag({
engineVersion: '6000.3.2f1',
targetPlatform: 'StandaloneWindows64',
hostPlatform: 'win32',
hostOS: 'linux',
});

expect(image.toString()).toContain('windows-mono');
expect(image.toString()).not.toContain('windows-il2cpp');
});

it('falls back to hostPlatform when hostOS is not set (backward compat)', () => {
const image = new RunnerImageTag({
engineVersion: '2099.1.1111',
targetPlatform: 'Test',
hostPlatform: 'win32',
});

expect(image.toString()).toContain('windows-');
});

it('uses windows prefix when hostOS is windows', () => {
const image = new RunnerImageTag({
engineVersion: '6000.3.2f1',
targetPlatform: 'StandaloneWindows64',
hostPlatform: 'win32',
hostOS: 'windows',
});

expect(image.toString()).toContain('windows-');
expect(image.toString()).toContain('windows-il2cpp');
});
});

describe('toNodePlatform', () => {
it('maps "windows" to "win32"', () => {
expect(RunnerImageTag.toNodePlatform('windows', 'win32')).toBe('win32');
});

it('maps "linux" to "linux"', () => {
expect(RunnerImageTag.toNodePlatform('linux', 'win32')).toBe('linux');
});

it('maps "darwin" to "darwin"', () => {
expect(RunnerImageTag.toNodePlatform('darwin', 'darwin')).toBe('darwin');
});

it('falls back to hostPlatform when hostOS is undefined', () => {
expect(RunnerImageTag.toNodePlatform(undefined, 'win32')).toBe('win32');
});

it('falls back to hostPlatform when hostOS is unrecognized', () => {
expect(RunnerImageTag.toNodePlatform('freebsd', 'linux')).toBe('linux');
});
});
});
23 changes: 21 additions & 2 deletions src/model/unity/runner/runner-image-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class RunnerImageTag {
const {
engineVersion = '2019.2.11f1',
hostPlatform,
hostOS,
targetPlatform,
customImage,
containerRegistryRepository = 'unityci/editor',
Expand All @@ -25,6 +26,11 @@ class RunnerImageTag {
throw new Error(`Invalid version "${engineVersion}".`);
}

// When the container OS differs from the host OS (e.g. --container-os=linux
// on a Windows host running Docker Desktop in Linux-containers mode), image
// tags and build-module selection must match the *container* OS, not the host.
const containerPlatform = RunnerImageTag.toNodePlatform(hostOS, hostPlatform);

// Split on the last '/' so registries with a host+path prefix (e.g.
// ghcr.io/example/editor) keep that whole prefix as the repository,
// with only the final segment treated as the image name.
Expand All @@ -38,16 +44,29 @@ class RunnerImageTag {
this.name = name;
this.engineVersion = engineVersion;
this.targetPlatform = targetPlatform;
this.imagePlatformPrefix = RunnerImageTag.getImagePlatformPrefixes(hostPlatform);
this.imagePlatformPrefix = RunnerImageTag.getImagePlatformPrefixes(containerPlatform);
this.builderPlatform = RunnerImageTag.getTargetPlatformToTargetPlatformSuffixMap(
hostPlatform,
containerPlatform,
targetPlatform,
engineVersion,
);
// Rolls forward automatically within the pinned major (non-breaking updates).
this.imageRollingVersion = Number(containerRegistryImageVersion);
}

/**
* Maps the resolved hostOS ('linux', 'windows', 'darwin') back to the
* Node-style platform string ('linux', 'win32', 'darwin') that
* getImagePlatformPrefixes and getTargetPlatformToTargetPlatformSuffixMap
* expect. Falls back to hostPlatform when hostOS is not set (backward
* compat with callers that only supply hostPlatform).
*/
static toNodePlatform(hostOS: string | undefined, hostPlatform: string | undefined): string {
if (hostOS === 'windows') return 'win32';
if (hostOS === 'linux' || hostOS === 'darwin') return hostOS;
return hostPlatform ?? '';
}

static get versionPattern() {
// Matches both the legacy YYYY.M.PPl# scheme (e.g. "2021.3.45f1") and
// Unity 6's "6000.M.PPl#" scheme (e.g. "6000.0.36f1") - previously only
Expand Down
Loading