mirror of
https://github.com/github/codeql-action.git
synced 2026-11-05 04:29:40 +00:00
Add function to determine Xcode version based on symlink
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
} from "./status-report";
|
||||
import { getEnv, getErrorMessage, wrapError } from "./util";
|
||||
|
||||
export type { Logger } from "./logging";
|
||||
|
||||
/** Base state that is available to an Action on startup. */
|
||||
export interface BaseState {
|
||||
/** The name of the Action. */
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import test from "ava";
|
||||
import fs from "fs";
|
||||
|
||||
import { BundlePlatform, getBundlePlatform, macOSVersion } from "./platform";
|
||||
import { initAllState } from "./testing-utils";
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import {
|
||||
BundlePlatform,
|
||||
getBundlePlatform,
|
||||
macOSVersion,
|
||||
XCODE_SELECT_LINK_PATH,
|
||||
xcodeVersion,
|
||||
} from "./platform";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
initAllState,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
for (const [platform, arch, expected] of [
|
||||
["linux", "x64", BundlePlatform.Linux64],
|
||||
@@ -54,3 +70,123 @@ test("macOSVersion returns semver if parsing succeeds", (t) => {
|
||||
t.is(version["patch"], 1);
|
||||
}
|
||||
});
|
||||
|
||||
test.serial("xcodeVersion returns undefined if symlink doesn't exist", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const statSync = sinon.stub(fs, "statSync").throws(new Error("ENOENT"));
|
||||
|
||||
t.is(xcodeVersion(logger), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Unable to determine Xcode version: ENOENT",
|
||||
]);
|
||||
});
|
||||
|
||||
test.serial("xcodeVersion returns undefined if file is not a symlink", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const statSync = sinon
|
||||
.stub(fs, "statSync")
|
||||
.returns({ isSymbolicLink: () => false } as fs.Stats);
|
||||
|
||||
t.is(xcodeVersion(logger), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"exists, but is not a symbolic link",
|
||||
]);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"xcodeVersion returns undefined if resolving the symlink returns nothing",
|
||||
(t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const statSync = sinon
|
||||
.stub(fs, "statSync")
|
||||
.returns({ isSymbolicLink: () => true } as fs.Stats);
|
||||
const readlinkSync = sinon.stub(fs, "readlinkSync").returns("");
|
||||
|
||||
t.is(xcodeVersion(logger), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"unexpectedly returned nothing",
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"xcodeVersion returns undefined if resolved path doesn't include pattern",
|
||||
(t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const statSync = sinon
|
||||
.stub(fs, "statSync")
|
||||
.returns({ isSymbolicLink: () => true } as fs.Stats);
|
||||
const readlinkSync = sinon
|
||||
.stub(fs, "readlinkSync")
|
||||
.returns("/Applications/Xcode.app/Contents/Developer");
|
||||
|
||||
t.is(xcodeVersion(logger), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"does not contain expected pattern",
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("xcodeVersion returns undefined if match can't be parsed", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const statSync = sinon
|
||||
.stub(fs, "statSync")
|
||||
.returns({ isSymbolicLink: () => true } as fs.Stats);
|
||||
const readlinkSync = sinon
|
||||
.stub(fs, "readlinkSync")
|
||||
.returns("/Applications/Xcode_00.0.app/Contents/Developer");
|
||||
|
||||
t.is(xcodeVersion(logger), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Couldn't parse '00.0' as a semantic version.",
|
||||
]);
|
||||
});
|
||||
|
||||
test.serial("xcodeVersion returns version from resolved path", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const statSync = sinon
|
||||
.stub(fs, "statSync")
|
||||
.returns({ isSymbolicLink: () => true } as fs.Stats);
|
||||
const readlinkSync = sinon
|
||||
.stub(fs, "readlinkSync")
|
||||
.returns("/Applications/Xcode_16.4.app/Contents/Developer");
|
||||
|
||||
const result = xcodeVersion(logger);
|
||||
|
||||
if (t.truthy(result)) {
|
||||
t.is(result.major, 16);
|
||||
t.is(result.minor, 4);
|
||||
}
|
||||
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import fs from "fs";
|
||||
|
||||
import * as semver from "semver";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
import type { ActionState, Logger } from "./action-common";
|
||||
import { getErrorMessage } from "./util";
|
||||
|
||||
/** Platform identifiers used in CodeQL bundle asset names. */
|
||||
export enum BundlePlatform {
|
||||
@@ -53,3 +56,67 @@ export function macOSVersion(
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/** The static path we check for a symbolic link to the (dynamic) Xcode location. */
|
||||
export const XCODE_SELECT_LINK_PATH = "/private/var/db/xcode_select_link";
|
||||
|
||||
/** The pattern we expect to find in the Xcode path. */
|
||||
export const XCODE_APP_FILENAME_PATTERN = new RegExp(
|
||||
/(?<filename>Xcode_(?<majorMinor>\d+.\d+).app)/,
|
||||
);
|
||||
|
||||
/**
|
||||
* Tries to determine the version of Xcode that is installed.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @returns The Xcode version or `undefined` if it couldn't be determined.
|
||||
*/
|
||||
export function xcodeVersion(logger: Logger): semver.SemVer | undefined {
|
||||
try {
|
||||
// Stat the expected symbolic link to check that it exists and is a symbolic link.
|
||||
// The `readlinkSync` call below returns an empty string in either case and so
|
||||
// this check allows us to distinguish between the two cases.
|
||||
const stats = fs.statSync(XCODE_SELECT_LINK_PATH);
|
||||
|
||||
if (!stats.isSymbolicLink()) {
|
||||
logger.warning(
|
||||
`${XCODE_SELECT_LINK_PATH} exists, but is not a symbolic link.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Read what the symbolic link points to.
|
||||
const xcodePath = fs.readlinkSync(XCODE_SELECT_LINK_PATH);
|
||||
|
||||
if (xcodePath === "") {
|
||||
logger.warning(
|
||||
`Resolving ${XCODE_SELECT_LINK_PATH} unexpectedly returned nothing.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try to extract the version from the path.
|
||||
const matchResult = xcodePath.match(XCODE_APP_FILENAME_PATTERN);
|
||||
|
||||
if (matchResult?.groups === undefined) {
|
||||
logger.warning(
|
||||
`Xcode path '${xcodePath}' does not contain expected pattern.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const majorMinor = matchResult.groups["majorMinor"];
|
||||
const version = semver.coerce(majorMinor);
|
||||
|
||||
if (version === null) {
|
||||
logger.warning(`Couldn't parse '${majorMinor}' as a semantic version.`);
|
||||
return undefined;
|
||||
}
|
||||
return version;
|
||||
} catch (err) {
|
||||
logger.warning(
|
||||
`Unable to determine Xcode version: ${getErrorMessage(err)}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user