mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-09-29 12:07:05 -07:00
fix: download and extract the release archive, not a bare binary
The compiled game-ci binary was never actually self-contained - see game-ci/cli#73. It now ships as an archive (.tar.gz / .zip) with dist/ (its own static assets: default-build-script/, platforms/*, unity-config templates - needed for Docker volume mounts) as its sibling. download-cli.ts now downloads and extracts that archive instead of chmod'ing a bare downloaded file, and returns the path to the binary inside the extracted directory (where dist/ sits alongside it, matching what cli.ts now expects on disk).
This commit is contained in:
+42
-24
@@ -139,7 +139,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.downloadCli = exports.assetNameFor = void 0;
|
exports.downloadCli = exports.binaryNameFor = exports.assetNameFor = void 0;
|
||||||
const fs = __importStar(__nccwpck_require__(3977));
|
const fs = __importStar(__nccwpck_require__(3977));
|
||||||
const os = __importStar(__nccwpck_require__(612));
|
const os = __importStar(__nccwpck_require__(612));
|
||||||
const path = __importStar(__nccwpck_require__(9411));
|
const path = __importStar(__nccwpck_require__(9411));
|
||||||
@@ -156,12 +156,26 @@ function assetNameFor(platform, arch) {
|
|||||||
const target = targets[platform]?.[arch];
|
const target = targets[platform]?.[arch];
|
||||||
if (!target)
|
if (!target)
|
||||||
throw new Error(`Unsupported platform/arch for the game-ci CLI: ${platform}/${arch}`);
|
throw new Error(`Unsupported platform/arch for the game-ci CLI: ${platform}/${arch}`);
|
||||||
return platform === 'win32' ? `game-ci-${target}.exe` : `game-ci-${target}`;
|
const extension = platform === 'win32' ? 'zip' : 'tar.gz';
|
||||||
|
return `game-ci-${target}.${extension}`;
|
||||||
}
|
}
|
||||||
exports.assetNameFor = assetNameFor;
|
exports.assetNameFor = assetNameFor;
|
||||||
|
/** The binary's name once extracted - matches release-cli.yml's per-platform `binary` matrix value. */
|
||||||
|
function binaryNameFor(platform) {
|
||||||
|
return platform === 'win32' ? 'game-ci.exe' : 'game-ci';
|
||||||
|
}
|
||||||
|
exports.binaryNameFor = binaryNameFor;
|
||||||
/**
|
/**
|
||||||
* Downloads (or reuses a cached copy of) the game-ci CLI binary matching the
|
* Downloads (or reuses a cached copy of) the game-ci CLI release archive
|
||||||
* current runner, and returns its path.
|
* matching the current runner, extracts it, and returns the path to the
|
||||||
|
* binary inside.
|
||||||
|
*
|
||||||
|
* The archive - not a bare binary - is what's published: cli.ts resolves
|
||||||
|
* its own static assets (default-build-script/, platforms/*,
|
||||||
|
* unity-config/services-config.json.template, all needed for Docker
|
||||||
|
* volume mounts) relative to its own directory on disk, and those assets
|
||||||
|
* aren't embedded in the compiled binary itself. dist/ ships as the
|
||||||
|
* binary's sibling inside the archive - see game-ci/cli#73.
|
||||||
*
|
*
|
||||||
* Pinned versions are cached via @actions/cache (GitHub's cache service),
|
* Pinned versions are cached via @actions/cache (GitHub's cache service),
|
||||||
* so repeat jobs on ephemeral, GitHub-hosted runners skip the download
|
* so repeat jobs on ephemeral, GitHub-hosted runners skip the download
|
||||||
@@ -175,9 +189,10 @@ exports.assetNameFor = assetNameFor;
|
|||||||
*/
|
*/
|
||||||
async function downloadCli(version) {
|
async function downloadCli(version) {
|
||||||
const asset = assetNameFor(process.platform, process.arch);
|
const asset = assetNameFor(process.platform, process.arch);
|
||||||
|
const binaryName = binaryNameFor(process.platform);
|
||||||
const isPinned = version !== 'latest';
|
const isPinned = version !== 'latest';
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
const cached = await restoreFromCache(version, asset);
|
const cached = await restoreFromCache(version, binaryName);
|
||||||
if (cached)
|
if (cached)
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
@@ -185,49 +200,52 @@ async function downloadCli(version) {
|
|||||||
? `https://github.com/${CLI_REPO}/releases/download/${version}/${asset}`
|
? `https://github.com/${CLI_REPO}/releases/download/${version}/${asset}`
|
||||||
: `https://github.com/${CLI_REPO}/releases/latest/download/${asset}`;
|
: `https://github.com/${CLI_REPO}/releases/latest/download/${asset}`;
|
||||||
core.info(`Downloading game-ci CLI (${version}) from ${url}`);
|
core.info(`Downloading game-ci CLI (${version}) from ${url}`);
|
||||||
const downloadedPath = await tc.downloadTool(url);
|
const archivePath = await tc.downloadTool(url);
|
||||||
|
const extractedDir = process.platform === 'win32' ? await tc.extractZip(archivePath) : await tc.extractTar(archivePath);
|
||||||
|
const binaryPath = path.join(extractedDir, binaryName);
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
await fs.chmod(downloadedPath, 0o755);
|
await fs.chmod(binaryPath, 0o755);
|
||||||
}
|
}
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
await saveToCache(version, asset, downloadedPath);
|
await saveToCache(version, binaryName, extractedDir);
|
||||||
}
|
}
|
||||||
return downloadedPath;
|
return binaryPath;
|
||||||
}
|
}
|
||||||
exports.downloadCli = downloadCli;
|
exports.downloadCli = downloadCli;
|
||||||
function cachePathFor(version, asset) {
|
function cacheDirFor(version) {
|
||||||
return path.join(os.tmpdir(), 'game-ci-cli-cache', version, asset);
|
return path.join(os.tmpdir(), 'game-ci-cli-cache', version);
|
||||||
}
|
}
|
||||||
function cacheKeyFor(version, asset) {
|
function cacheKeyFor(version, binaryName) {
|
||||||
return `game-ci-cli-${version}-${asset}`;
|
return `game-ci-cli-${version}-${binaryName}`;
|
||||||
}
|
}
|
||||||
async function restoreFromCache(version, asset) {
|
async function restoreFromCache(version, binaryName) {
|
||||||
if (!cache.isFeatureAvailable())
|
if (!cache.isFeatureAvailable())
|
||||||
return null;
|
return null;
|
||||||
const cachePath = cachePathFor(version, asset);
|
const cacheDir = cacheDirFor(version);
|
||||||
try {
|
try {
|
||||||
const hitKey = await cache.restoreCache([cachePath], cacheKeyFor(version, asset));
|
const hitKey = await cache.restoreCache([cacheDir], cacheKeyFor(version, binaryName));
|
||||||
if (!hitKey)
|
if (!hitKey)
|
||||||
return null;
|
return null;
|
||||||
|
const binaryPath = path.join(cacheDir, binaryName);
|
||||||
// Cache restore doesn't guarantee the executable bit survives.
|
// Cache restore doesn't guarantee the executable bit survives.
|
||||||
if (process.platform !== 'win32')
|
if (process.platform !== 'win32')
|
||||||
await fs.chmod(cachePath, 0o755);
|
await fs.chmod(binaryPath, 0o755);
|
||||||
core.info(`Restored game-ci CLI ${version} from cache`);
|
core.info(`Restored game-ci CLI ${version} from cache`);
|
||||||
return cachePath;
|
return binaryPath;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
core.warning(`Failed to restore game-ci CLI from cache: ${error.message}`);
|
core.warning(`Failed to restore game-ci CLI from cache: ${error.message}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function saveToCache(version, asset, downloadedPath) {
|
async function saveToCache(version, binaryName, extractedDir) {
|
||||||
if (!cache.isFeatureAvailable())
|
if (!cache.isFeatureAvailable())
|
||||||
return;
|
return;
|
||||||
const cachePath = cachePathFor(version, asset);
|
const cacheDir = cacheDirFor(version);
|
||||||
try {
|
try {
|
||||||
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
await fs.mkdir(path.dirname(cacheDir), { recursive: true });
|
||||||
await fs.copyFile(downloadedPath, cachePath);
|
await fs.cp(extractedDir, cacheDir, { recursive: true });
|
||||||
await cache.saveCache([cachePath], cacheKeyFor(version, asset));
|
await cache.saveCache([cacheDir], cacheKeyFor(version, binaryName));
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
// A cache miss on save (e.g. another concurrent job already saved this
|
// A cache miss on save (e.g. another concurrent job already saved this
|
||||||
@@ -336,7 +354,7 @@ if (process.env.NODE_ENV !== 'test') {
|
|||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.resolveProjectPath = void 0;
|
exports.resolveProjectPath = void 0;
|
||||||
function resolveProjectPath({ input, existsSync, joinPath }) {
|
function resolveProjectPath({ input, existsSync, joinPath, }) {
|
||||||
if (input)
|
if (input)
|
||||||
return input.replace(/\/$/, '');
|
return input.replace(/\/$/, '');
|
||||||
const hasTestProject = existsSync(joinPath('test-project', 'ProjectSettings', 'ProjectVersion.txt'));
|
const hasTestProject = existsSync(joinPath('test-project', 'ProjectSettings', 'ProjectVersion.txt'));
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+22
-11
@@ -1,25 +1,25 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { assetNameFor } from './download-cli';
|
import { assetNameFor, binaryNameFor } from './download-cli';
|
||||||
|
|
||||||
describe('assetNameFor', () => {
|
describe('assetNameFor', () => {
|
||||||
it('maps linux x64', () => {
|
it('maps linux x64 to a .tar.gz archive', () => {
|
||||||
expect(assetNameFor('linux', 'x64')).toBe('game-ci-linux-x64');
|
expect(assetNameFor('linux', 'x64')).toBe('game-ci-linux-x64.tar.gz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maps linux arm64', () => {
|
it('maps linux arm64 to a .tar.gz archive', () => {
|
||||||
expect(assetNameFor('linux', 'arm64')).toBe('game-ci-linux-arm64');
|
expect(assetNameFor('linux', 'arm64')).toBe('game-ci-linux-arm64.tar.gz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maps darwin x64', () => {
|
it('maps darwin x64 to a .tar.gz archive', () => {
|
||||||
expect(assetNameFor('darwin', 'x64')).toBe('game-ci-macos-x64');
|
expect(assetNameFor('darwin', 'x64')).toBe('game-ci-macos-x64.tar.gz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maps darwin arm64', () => {
|
it('maps darwin arm64 to a .tar.gz archive', () => {
|
||||||
expect(assetNameFor('darwin', 'arm64')).toBe('game-ci-macos-arm64');
|
expect(assetNameFor('darwin', 'arm64')).toBe('game-ci-macos-arm64.tar.gz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maps win32 x64 with an .exe suffix', () => {
|
it('maps win32 x64 to a .zip archive', () => {
|
||||||
expect(assetNameFor('win32', 'x64')).toBe('game-ci-windows-x64.exe');
|
expect(assetNameFor('win32', 'x64')).toBe('game-ci-windows-x64.zip');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws for an unsupported platform/arch combination', () => {
|
it('throws for an unsupported platform/arch combination', () => {
|
||||||
@@ -27,3 +27,14 @@ describe('assetNameFor', () => {
|
|||||||
expect(() => assetNameFor('freebsd', 'x64')).toThrow(/unsupported/i);
|
expect(() => assetNameFor('freebsd', 'x64')).toThrow(/unsupported/i);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('binaryNameFor', () => {
|
||||||
|
it('is game-ci.exe on win32', () => {
|
||||||
|
expect(binaryNameFor('win32')).toBe('game-ci.exe');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is game-ci on every other platform', () => {
|
||||||
|
expect(binaryNameFor('linux')).toBe('game-ci');
|
||||||
|
expect(binaryNameFor('darwin')).toBe('game-ci');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+48
-22
@@ -18,12 +18,27 @@ export function assetNameFor(platform: NodeJS.Platform, arch: string): string {
|
|||||||
if (!target)
|
if (!target)
|
||||||
throw new Error(`Unsupported platform/arch for the game-ci CLI: ${platform}/${arch}`);
|
throw new Error(`Unsupported platform/arch for the game-ci CLI: ${platform}/${arch}`);
|
||||||
|
|
||||||
return platform === 'win32' ? `game-ci-${target}.exe` : `game-ci-${target}`;
|
const extension = platform === 'win32' ? 'zip' : 'tar.gz';
|
||||||
|
|
||||||
|
return `game-ci-${target}.${extension}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The binary's name once extracted - matches release-cli.yml's per-platform `binary` matrix value. */
|
||||||
|
export function binaryNameFor(platform: NodeJS.Platform): string {
|
||||||
|
return platform === 'win32' ? 'game-ci.exe' : 'game-ci';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads (or reuses a cached copy of) the game-ci CLI binary matching the
|
* Downloads (or reuses a cached copy of) the game-ci CLI release archive
|
||||||
* current runner, and returns its path.
|
* matching the current runner, extracts it, and returns the path to the
|
||||||
|
* binary inside.
|
||||||
|
*
|
||||||
|
* The archive - not a bare binary - is what's published: cli.ts resolves
|
||||||
|
* its own static assets (default-build-script/, platforms/*,
|
||||||
|
* unity-config/services-config.json.template, all needed for Docker
|
||||||
|
* volume mounts) relative to its own directory on disk, and those assets
|
||||||
|
* aren't embedded in the compiled binary itself. dist/ ships as the
|
||||||
|
* binary's sibling inside the archive - see game-ci/cli#73.
|
||||||
*
|
*
|
||||||
* Pinned versions are cached via @actions/cache (GitHub's cache service),
|
* Pinned versions are cached via @actions/cache (GitHub's cache service),
|
||||||
* so repeat jobs on ephemeral, GitHub-hosted runners skip the download
|
* so repeat jobs on ephemeral, GitHub-hosted runners skip the download
|
||||||
@@ -37,10 +52,11 @@ export function assetNameFor(platform: NodeJS.Platform, arch: string): string {
|
|||||||
*/
|
*/
|
||||||
export async function downloadCli(version: string): Promise<string> {
|
export async function downloadCli(version: string): Promise<string> {
|
||||||
const asset = assetNameFor(process.platform, process.arch);
|
const asset = assetNameFor(process.platform, process.arch);
|
||||||
|
const binaryName = binaryNameFor(process.platform);
|
||||||
const isPinned = version !== 'latest';
|
const isPinned = version !== 'latest';
|
||||||
|
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
const cached = await restoreFromCache(version, asset);
|
const cached = await restoreFromCache(version, binaryName);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,54 +65,64 @@ export async function downloadCli(version: string): Promise<string> {
|
|||||||
: `https://github.com/${CLI_REPO}/releases/latest/download/${asset}`;
|
: `https://github.com/${CLI_REPO}/releases/latest/download/${asset}`;
|
||||||
|
|
||||||
core.info(`Downloading game-ci CLI (${version}) from ${url}`);
|
core.info(`Downloading game-ci CLI (${version}) from ${url}`);
|
||||||
const downloadedPath = await tc.downloadTool(url);
|
const archivePath = await tc.downloadTool(url);
|
||||||
|
const extractedDir =
|
||||||
|
process.platform === 'win32'
|
||||||
|
? await tc.extractZip(archivePath)
|
||||||
|
: await tc.extractTar(archivePath);
|
||||||
|
|
||||||
|
const binaryPath = path.join(extractedDir, binaryName);
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
await fs.chmod(downloadedPath, 0o755);
|
await fs.chmod(binaryPath, 0o755);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
await saveToCache(version, asset, downloadedPath);
|
await saveToCache(version, binaryName, extractedDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadedPath;
|
return binaryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cachePathFor(version: string, asset: string): string {
|
function cacheDirFor(version: string): string {
|
||||||
return path.join(os.tmpdir(), 'game-ci-cli-cache', version, asset);
|
return path.join(os.tmpdir(), 'game-ci-cli-cache', version);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheKeyFor(version: string, asset: string): string {
|
function cacheKeyFor(version: string, binaryName: string): string {
|
||||||
return `game-ci-cli-${version}-${asset}`;
|
return `game-ci-cli-${version}-${binaryName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restoreFromCache(version: string, asset: string): Promise<string | null> {
|
async function restoreFromCache(version: string, binaryName: string): Promise<string | null> {
|
||||||
if (!cache.isFeatureAvailable()) return null;
|
if (!cache.isFeatureAvailable()) return null;
|
||||||
|
|
||||||
const cachePath = cachePathFor(version, asset);
|
const cacheDir = cacheDirFor(version);
|
||||||
try {
|
try {
|
||||||
const hitKey = await cache.restoreCache([cachePath], cacheKeyFor(version, asset));
|
const hitKey = await cache.restoreCache([cacheDir], cacheKeyFor(version, binaryName));
|
||||||
if (!hitKey) return null;
|
if (!hitKey) return null;
|
||||||
|
|
||||||
|
const binaryPath = path.join(cacheDir, binaryName);
|
||||||
// Cache restore doesn't guarantee the executable bit survives.
|
// Cache restore doesn't guarantee the executable bit survives.
|
||||||
if (process.platform !== 'win32') await fs.chmod(cachePath, 0o755);
|
if (process.platform !== 'win32') await fs.chmod(binaryPath, 0o755);
|
||||||
|
|
||||||
core.info(`Restored game-ci CLI ${version} from cache`);
|
core.info(`Restored game-ci CLI ${version} from cache`);
|
||||||
return cachePath;
|
return binaryPath;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
core.warning(`Failed to restore game-ci CLI from cache: ${error.message}`);
|
core.warning(`Failed to restore game-ci CLI from cache: ${error.message}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveToCache(version: string, asset: string, downloadedPath: string): Promise<void> {
|
async function saveToCache(
|
||||||
|
version: string,
|
||||||
|
binaryName: string,
|
||||||
|
extractedDir: string,
|
||||||
|
): Promise<void> {
|
||||||
if (!cache.isFeatureAvailable()) return;
|
if (!cache.isFeatureAvailable()) return;
|
||||||
|
|
||||||
const cachePath = cachePathFor(version, asset);
|
const cacheDir = cacheDirFor(version);
|
||||||
try {
|
try {
|
||||||
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
await fs.mkdir(path.dirname(cacheDir), { recursive: true });
|
||||||
await fs.copyFile(downloadedPath, cachePath);
|
await fs.cp(extractedDir, cacheDir, { recursive: true });
|
||||||
await cache.saveCache([cachePath], cacheKeyFor(version, asset));
|
await cache.saveCache([cacheDir], cacheKeyFor(version, binaryName));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// A cache miss on save (e.g. another concurrent job already saved this
|
// A cache miss on save (e.g. another concurrent job already saved this
|
||||||
// key) isn't fatal - the download itself already succeeded.
|
// key) isn't fatal - the download itself already succeeded.
|
||||||
|
|||||||
Reference in New Issue
Block a user