Compare commits

...
5 changed files with 787 additions and 201 deletions

No files matched your search

+199 -78
View File
@@ -28347,7 +28347,7 @@ var require_io = __commonJS({
exports2.mv = mv;
exports2.rmRF = rmRF;
exports2.mkdirP = mkdirP;
exports2.which = which9;
exports2.which = which10;
exports2.findInPath = findInPath;
var assert_1 = require("assert");
var path30 = __importStar2(require("path"));
@@ -28423,13 +28423,13 @@ var require_io = __commonJS({
yield ioUtil.mkdir(fsPath, { recursive: true });
});
}
function which9(tool, check) {
function which10(tool, check) {
return __awaiter2(this, void 0, void 0, function* () {
if (!tool) {
throw new Error("parameter 'tool' is required");
}
if (check) {
const result = yield which9(tool, false);
const result = yield which10(tool, false);
if (!result) {
if (ioUtil.IS_WINDOWS) {
throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`);
@@ -28610,11 +28610,11 @@ var require_toolrunner = __commonJS({
var events = __importStar2(require("events"));
var child = __importStar2(require("child_process"));
var path30 = __importStar2(require("path"));
var io9 = __importStar2(require_io());
var io10 = __importStar2(require_io());
var ioUtil = __importStar2(require_io_util());
var timers_1 = require("timers");
var IS_WINDOWS = process.platform === "win32";
var ToolRunner7 = class extends events.EventEmitter {
var ToolRunner8 = class extends events.EventEmitter {
constructor(toolPath, args, options) {
super();
if (!toolPath) {
@@ -28826,7 +28826,7 @@ var require_toolrunner = __commonJS({
if (!ioUtil.isRooted(this.toolPath) && (this.toolPath.includes("/") || IS_WINDOWS && this.toolPath.includes("\\"))) {
this.toolPath = path30.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath);
}
this.toolPath = yield io9.which(this.toolPath, true);
this.toolPath = yield io10.which(this.toolPath, true);
return new Promise((resolve15, reject) => __awaiter2(this, void 0, void 0, function* () {
this._debug(`exec tool: ${this.toolPath}`);
this._debug("arguments:");
@@ -28923,7 +28923,7 @@ var require_toolrunner = __commonJS({
});
}
};
exports2.ToolRunner = ToolRunner7;
exports2.ToolRunner = ToolRunner8;
function argStringToArray(argString) {
const args = [];
let inQuotes = false;
@@ -42426,7 +42426,7 @@ var require_cacheUtils = __commonJS({
var core29 = __importStar2(require_core());
var exec3 = __importStar2(require_exec());
var glob2 = __importStar2(require_glob());
var io9 = __importStar2(require_io());
var io10 = __importStar2(require_io());
var crypto3 = __importStar2(require("crypto"));
var fs34 = __importStar2(require("fs"));
var path30 = __importStar2(require("path"));
@@ -42452,7 +42452,7 @@ var require_cacheUtils = __commonJS({
tempDirectory = path30.join(baseLocation, "actions", "temp");
}
const dest = path30.join(tempDirectory, crypto3.randomUUID());
yield io9.mkdirP(dest);
yield io10.mkdirP(dest);
return dest;
});
}
@@ -42541,7 +42541,7 @@ var require_cacheUtils = __commonJS({
return constants_1.GnuTarPathOnWindows;
}
const versionOutput = yield getVersion("tar");
return versionOutput.toLowerCase().includes("gnu tar") ? io9.which("tar") : "";
return versionOutput.toLowerCase().includes("gnu tar") ? io10.which("tar") : "";
});
}
function assertDefined(name, value) {
@@ -113305,7 +113305,7 @@ var require_tar = __commonJS({
exports2.createTar = createTar;
var core29 = __importStar2(require_core());
var exec_1 = require_exec();
var io9 = __importStar2(require_io());
var io10 = __importStar2(require_io());
var fs_1 = require("fs");
var path30 = __importStar2(require("path"));
var utils = __importStar2(require_cacheUtils());
@@ -113325,12 +113325,12 @@ var require_tar = __commonJS({
break;
}
case "darwin": {
const gnuTar = yield io9.which("gtar", false);
const gnuTar = yield io10.which("gtar", false);
if (gnuTar) {
return { path: gnuTar, type: constants_1.ArchiveToolType.GNU };
} else {
return {
path: yield io9.which("tar", true),
path: yield io10.which("tar", true),
type: constants_1.ArchiveToolType.BSD
};
}
@@ -113339,7 +113339,7 @@ var require_tar = __commonJS({
break;
}
return {
path: yield io9.which("tar", true),
path: yield io10.which("tar", true),
type: constants_1.ArchiveToolType.GNU
};
});
@@ -113468,7 +113468,7 @@ var require_tar = __commonJS({
} finally {
if (tempDirectory) {
try {
yield io9.rmRF(tempDirectory);
yield io10.rmRF(tempDirectory);
} catch (error3) {
core29.debug(`Failed to delete temporary tar directory: ${error3}`);
}
@@ -113484,7 +113484,7 @@ var require_tar = __commonJS({
function extractTar3(archivePath, compressionMethod) {
return __awaiter2(this, void 0, void 0, function* () {
const workingDirectory = getWorkingDirectory();
yield io9.mkdirP(workingDirectory);
yield io10.mkdirP(workingDirectory);
yield execReadCommands(archivePath, compressionMethod, "extract");
});
}
@@ -114348,7 +114348,7 @@ var require_tool_cache = __commonJS({
exports2.isExplicitVersion = isExplicitVersion;
exports2.evaluateVersions = evaluateVersions;
var core29 = __importStar2(require_core());
var io9 = __importStar2(require_io());
var io10 = __importStar2(require_io());
var crypto3 = __importStar2(require("crypto"));
var fs34 = __importStar2(require("fs"));
var mm = __importStar2(require_manifest());
@@ -114375,7 +114375,7 @@ var require_tool_cache = __commonJS({
function downloadTool3(url2, dest, auth2, headers) {
return __awaiter2(this, void 0, void 0, function* () {
dest = dest || path30.join(_getTempDirectory(), crypto3.randomUUID());
yield io9.mkdirP(path30.dirname(dest));
yield io10.mkdirP(path30.dirname(dest));
core29.debug(`Downloading ${url2}`);
core29.debug(`Destination ${dest}`);
const maxAttempts = 3;
@@ -114428,7 +114428,7 @@ var require_tool_cache = __commonJS({
if (!succeeded) {
core29.debug("download failed");
try {
yield io9.rmRF(dest);
yield io10.rmRF(dest);
} catch (err) {
core29.debug(`Failed to delete '${dest}'. ${err.message}`);
}
@@ -114483,7 +114483,7 @@ var require_tool_cache = __commonJS({
silent: true
};
try {
const powershellPath = yield io9.which("powershell", true);
const powershellPath = yield io10.which("powershell", true);
yield (0, exec_1.exec)(`"${powershellPath}"`, args, options);
} finally {
process.chdir(originalCwd);
@@ -114550,7 +114550,7 @@ var require_tool_cache = __commonJS({
if (core29.isDebug()) {
args.push("-v");
}
const xarPath = yield io9.which("xar", true);
const xarPath = yield io10.which("xar", true);
yield (0, exec_1.exec)(`"${xarPath}"`, _unique(args));
return dest;
});
@@ -114573,7 +114573,7 @@ var require_tool_cache = __commonJS({
return __awaiter2(this, void 0, void 0, function* () {
const escapedFile = file.replace(/'/g, "''").replace(/"|\n|\r/g, "");
const escapedDest = dest.replace(/'/g, "''").replace(/"|\n|\r/g, "");
const pwshPath = yield io9.which("pwsh", false);
const pwshPath = yield io10.which("pwsh", false);
if (pwshPath) {
const pwshCommand = [
`$ErrorActionPreference = 'Stop' ;`,
@@ -114609,7 +114609,7 @@ var require_tool_cache = __commonJS({
"-Command",
powershellCommand
];
const powershellPath = yield io9.which("powershell", true);
const powershellPath = yield io10.which("powershell", true);
core29.debug(`Using powershell at path: ${powershellPath}`);
yield (0, exec_1.exec)(`"${powershellPath}"`, args);
}
@@ -114617,7 +114617,7 @@ var require_tool_cache = __commonJS({
}
function extractZipNix(file, dest) {
return __awaiter2(this, void 0, void 0, function* () {
const unzipPath = yield io9.which("unzip", true);
const unzipPath = yield io10.which("unzip", true);
const args = [file];
if (!core29.isDebug()) {
args.unshift("-q");
@@ -114638,7 +114638,7 @@ var require_tool_cache = __commonJS({
const destPath = yield _createToolPath(tool, version, arch2);
for (const itemName of fs34.readdirSync(sourceDir)) {
const s = path30.join(sourceDir, itemName);
yield io9.cp(s, destPath, { recursive: true });
yield io10.cp(s, destPath, { recursive: true });
}
_completeToolPath(tool, version, arch2);
return destPath;
@@ -114656,7 +114656,7 @@ var require_tool_cache = __commonJS({
const destFolder = yield _createToolPath(tool, version, arch2);
const destPath = path30.join(destFolder, targetFile);
core29.debug(`destination file ${destPath}`);
yield io9.cp(sourceFile, destPath);
yield io10.cp(sourceFile, destPath);
_completeToolPath(tool, version, arch2);
return destFolder;
});
@@ -114750,7 +114750,7 @@ var require_tool_cache = __commonJS({
if (!dest) {
dest = path30.join(_getTempDirectory(), crypto3.randomUUID());
}
yield io9.mkdirP(dest);
yield io10.mkdirP(dest);
return dest;
});
}
@@ -114759,9 +114759,9 @@ var require_tool_cache = __commonJS({
const folderPath = path30.join(_getCacheDirectory(), tool, semver12.clean(version) || version, arch2 || "");
core29.debug(`destination ${folderPath}`);
const markerPath = `${folderPath}.complete`;
yield io9.rmRF(folderPath);
yield io9.rmRF(markerPath);
yield io9.mkdirP(folderPath);
yield io10.rmRF(folderPath);
yield io10.rmRF(markerPath);
yield io10.mkdirP(folderPath);
return folderPath;
});
}
@@ -152391,13 +152391,13 @@ var require_io2 = __commonJS({
});
}
exports2.mkdirP = mkdirP;
function which9(tool, check) {
function which10(tool, check) {
return __awaiter2(this, void 0, void 0, function* () {
if (!tool) {
throw new Error("parameter 'tool' is required");
}
if (check) {
const result = yield which9(tool, false);
const result = yield which10(tool, false);
if (!result) {
if (ioUtil.IS_WINDOWS) {
throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`);
@@ -152414,7 +152414,7 @@ var require_io2 = __commonJS({
return "";
});
}
exports2.which = which9;
exports2.which = which10;
function findInPath(tool) {
return __awaiter2(this, void 0, void 0, function* () {
if (!tool) {
@@ -152565,11 +152565,11 @@ var require_toolrunner2 = __commonJS({
var events = __importStar2(require("events"));
var child = __importStar2(require("child_process"));
var path30 = __importStar2(require("path"));
var io9 = __importStar2(require_io2());
var io10 = __importStar2(require_io2());
var ioUtil = __importStar2(require_io_util2());
var timers_1 = require("timers");
var IS_WINDOWS = process.platform === "win32";
var ToolRunner7 = class extends events.EventEmitter {
var ToolRunner8 = class extends events.EventEmitter {
constructor(toolPath, args, options) {
super();
if (!toolPath) {
@@ -152781,7 +152781,7 @@ var require_toolrunner2 = __commonJS({
if (!ioUtil.isRooted(this.toolPath) && (this.toolPath.includes("/") || IS_WINDOWS && this.toolPath.includes("\\"))) {
this.toolPath = path30.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath);
}
this.toolPath = yield io9.which(this.toolPath, true);
this.toolPath = yield io10.which(this.toolPath, true);
return new Promise((resolve15, reject) => __awaiter2(this, void 0, void 0, function* () {
this._debug(`exec tool: ${this.toolPath}`);
this._debug("arguments:");
@@ -152878,7 +152878,7 @@ var require_toolrunner2 = __commonJS({
});
}
};
exports2.ToolRunner = ToolRunner7;
exports2.ToolRunner = ToolRunner8;
function argStringToArray(argString) {
const args = [];
let inQuotes = false;
@@ -180446,13 +180446,13 @@ var SarifScanOrder = [
var fs19 = __toESM(require("fs"));
var path16 = __toESM(require("path"));
var import_perf_hooks5 = require("perf_hooks");
var io5 = __toESM(require_io());
var io6 = __toESM(require_io());
// src/codeql.ts
var fs18 = __toESM(require("fs"));
var path15 = __toESM(require("path"));
var core12 = __toESM(require_core());
var toolrunner3 = __toESM(require_toolrunner());
var toolrunner4 = __toESM(require_toolrunner());
// src/cli-errors.ts
var SUPPORTED_PLATFORMS = [
@@ -183421,6 +183421,8 @@ var semver7 = __toESM(require_semver2());
// src/platform.ts
var import_fs = __toESM(require("fs"));
var toolrunner3 = __toESM(require_toolrunner());
var io4 = __toESM(require_io());
var semver6 = __toESM(require_semver2());
function getBundlePlatform(platform2 = process.platform, arch2 = process.arch) {
switch (platform2) {
@@ -183448,7 +183450,7 @@ var XCODE_SELECT_LINK_PATH = "/private/var/db/xcode_select_link";
var XCODE_APP_FILENAME_PATTERN = new RegExp(
/(?<filename>Xcode_(?<majorMinor>\d+(\.\d+)?).*\.app)/
);
function xcodeVersion(logger2) {
function xcodeVersionFromLink(logger2) {
try {
const xcodePath = import_fs.default.readlinkSync(XCODE_SELECT_LINK_PATH);
if (xcodePath.trim() === "") {
@@ -183476,6 +183478,111 @@ function xcodeVersion(logger2) {
return void 0;
}
}
async function xcodeVersionFromXcodeBuild(logger2) {
try {
const xcrun = await io4.which("xcrun", true);
let stdout = "";
let stderr = "";
const exitCode = await new toolrunner3.ToolRunner(
xcrun,
["xcodebuild", "-version"],
{
silent: true,
ignoreReturnCode: true,
listeners: {
stdout: (data) => {
stdout += data.toString("utf-8");
},
stderr: (data) => {
stderr += data.toString("utf-8");
}
}
}
).exec();
if (exitCode !== 0) {
logger2.debug(`xcodebuild returned exit code ${exitCode}`);
logger2.debug(`stdout:
${stdout}`);
logger2.debug(`stderr:
${stderr}`);
return void 0;
}
const match2 = stdout.match(/^Xcode (\d+(\.\d+)?)/);
if (match2 === null) {
logger2.debug(
`Output doesn't appear to contain the Xcode product version.`
);
logger2.debug(`stdout:
${stdout}`);
logger2.debug(`stderr:
${stderr}`);
return void 0;
}
const version = semver6.coerce(match2[1].trim());
if (version === null) {
logger2.debug(
`xcodebuild's output couldn't be parsed as a version:
${stdout}`
);
logger2.debug(`stderr:
${stderr}`);
return void 0;
}
return version;
} catch (err) {
logger2.debug(
`Unable to determine Xcode version from xcodebuild: ${getErrorMessage(err)}`
);
return void 0;
}
}
async function xcodeSdkVersion(logger2) {
try {
const xcrun = await io4.which("xcrun", true);
let stdout = "";
let stderr = "";
const exitCode = await new toolrunner3.ToolRunner(
xcrun,
["--sdk", "macosx", "--show-sdk-version"],
{
silent: true,
ignoreReturnCode: true,
listeners: {
stdout: (data) => {
stdout += data.toString("utf-8");
},
stderr: (data) => {
stderr += data.toString("utf-8");
}
}
}
).exec();
if (exitCode !== 0) {
logger2.debug(`xcrun returned exit code ${exitCode}`);
logger2.debug(`stdout:
${stdout}`);
logger2.debug(`stderr:
${stderr}`);
return void 0;
}
const version = semver6.coerce(stdout.trim());
if (version === null) {
logger2.debug(
`xcrun's output couldn't be parsed as a version:
${stdout}`
);
logger2.debug(`stderr:
${stderr}`);
return void 0;
}
return version;
} catch (err) {
logger2.debug(
`Unable to determine Xcode SDK version: ${getErrorMessage(err)}`
);
return void 0;
}
}
// src/per-language-bundles.ts
var MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION = "2.27.1";
@@ -183659,13 +183766,13 @@ var import_child_process = require("child_process");
var fs14 = __toESM(require("fs"));
var stream = __toESM(require("stream"));
var import_toolrunner = __toESM(require_toolrunner());
var io4 = __toESM(require_io());
var io5 = __toESM(require_io());
var toolcache = __toESM(require_tool_cache());
var semver8 = __toESM(require_semver2());
var MIN_REQUIRED_BSD_TAR_VERSION = "3.4.3";
var MIN_REQUIRED_GNU_TAR_VERSION = "1.31";
async function getTarVersion() {
const tar = await io4.which("tar", true);
const tar = await io5.which("tar", true);
let stdout = "";
const exitCode = await new import_toolrunner.ToolRunner(tar, ["--version"], {
listeners: {
@@ -185154,7 +185261,7 @@ async function getCodeQLForCmd(logger2, cmd, checkVersion, env = getEnv()) {
])
);
}
await new toolrunner3.ToolRunner(cmd, args, {
await new toolrunner4.ToolRunner(cmd, args, {
env: env.getDefined()
}).exec();
},
@@ -185175,7 +185282,7 @@ async function getCodeQLForCmd(logger2, cmd, checkVersion, env = getEnv()) {
if (automationDetailsId !== void 0) {
args.push("--sarif-category", automationDetailsId);
}
await new toolrunner3.ToolRunner(cmd, args, {
await new toolrunner4.ToolRunner(cmd, args, {
env: env.getDefined()
}).exec();
},
@@ -185204,13 +185311,13 @@ async function getCodeQLForCmd(logger2, cmd, checkVersion, env = getEnv()) {
if (automationDetailsId !== void 0) {
args.push("--sarif-category", automationDetailsId);
}
await new toolrunner3.ToolRunner(cmd, args, {
await new toolrunner4.ToolRunner(cmd, args, {
env: env.getDefined()
}).exec();
},
async resolveExtractor(language) {
let extractorPath = "";
await new toolrunner3.ToolRunner(
await new toolrunner4.ToolRunner(
cmd,
[
"resolve",
@@ -186153,7 +186260,7 @@ async function runFinalize(features, outputDir, threadsFlag, memoryFlag, codeql,
async function warnIfGoInstalledAfterInit(config, logger2) {
const goInitPath = process.env["CODEQL_ACTION_GO_BINARY" /* GO_BINARY_LOCATION */];
if (process.env["CODEQL_ACTION_DID_AUTOBUILD_GOLANG" /* DID_AUTOBUILD_GOLANG */] !== "true" && goInitPath !== void 0) {
const goBinaryPath = await io5.which("go", true);
const goBinaryPath = await io6.which("go", true);
if (goInitPath !== goBinaryPath) {
logger2.warning(
`Expected \`which go\` to return ${goInitPath}, but got ${goBinaryPath}: please ensure that the correct version of Go is installed before the \`codeql-action/init\` Action is used.`
@@ -187502,9 +187609,9 @@ async function addFingerprints(sarifLog, sourceRoot, logger2) {
// src/init.ts
var fs22 = __toESM(require("fs"));
var path18 = __toESM(require("path"));
var toolrunner4 = __toESM(require_toolrunner());
var toolrunner5 = __toESM(require_toolrunner());
var github3 = __toESM(require_github());
var io6 = __toESM(require_io());
var io7 = __toESM(require_io());
async function initCodeQL(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, rawLanguages, otherLanguagePacksReason, useOverlayAwareDefaultCliVersion, features, logger2) {
logger2.startGroup("Setup CodeQL tools");
const { codeql, toolsDownloadStatusReport, toolsSource, toolsVersion } = await setupCodeQL(
@@ -187617,7 +187724,7 @@ async function checkInstallPython311(languages, codeql) {
"../python-setup",
"check_python12.ps1"
);
await new toolrunner4.ToolRunner(await io6.which("powershell", true), [
await new toolrunner5.ToolRunner(await io7.which("powershell", true), [
script
]).exec();
}
@@ -190345,10 +190452,10 @@ var Minimatch = class {
let ai = 0;
let bi = 0;
let result = [];
let which9 = "";
let which10 = "";
while (ai < a.length && bi < b.length) {
if (a[ai] === b[bi]) {
result.push(which9 === "b" ? b[bi] : a[ai]);
result.push(which10 === "b" ? b[bi] : a[ai]);
ai++;
bi++;
} else if (emptyGSMatch && a[ai] === "**" && b[bi] === a[ai + 1]) {
@@ -190358,16 +190465,16 @@ var Minimatch = class {
result.push(b[bi]);
bi++;
} else if (a[ai] === "*" && b[bi] && (this.options.dot || !b[bi].startsWith(".")) && b[bi] !== "**") {
if (which9 === "b")
if (which10 === "b")
return false;
which9 = "a";
which10 = "a";
result.push(a[ai]);
ai++;
bi++;
} else if (b[bi] === "*" && a[ai] && (this.options.dot || !a[ai].startsWith(".")) && a[ai] !== "**") {
if (which9 === "a")
if (which10 === "a")
return false;
which9 = "b";
which10 = "b";
result.push(b[bi]);
ai++;
bi++;
@@ -193856,7 +193963,7 @@ async function runWrapper3() {
var fs31 = __toESM(require("fs"));
var path25 = __toESM(require("path"));
var core19 = __toESM(require_core());
var io7 = __toESM(require_io());
var io8 = __toESM(require_io());
var semver11 = __toESM(require_semver2());
// src/config/inputs.ts
@@ -193911,20 +194018,35 @@ async function checkSwiftCompatibility(config, codeql, darwinVersion2) {
// src/languages/traced.ts
var UNSUPPORTED_DARWIN_FOR_TRACED = 27;
var UNSUPPORTED_XCODE_FOR_TRACED = 27;
function handleUnsupportedVersion(logger2, config, tracedLanguages, abortUnsupportedTracedAnalysis, product, version) {
const unsupportedVersion = product === "macOS" ? UNSUPPORTED_DARWIN_FOR_TRACED : UNSUPPORTED_XCODE_FOR_TRACED;
function diagnosticAttributeName(product) {
switch (product) {
case "macOS":
return "darwinVersion";
case "Xcode":
return "xcodeVersion";
case "Xcode SDK":
return "xcodeSdkVersion";
}
}
function handleUnsupportedVersion(logger2, config, tracedLanguages, abortUnsupportedTracedAnalysis, productVersion) {
const { product, version } = productVersion;
const unsupportedVersion = product === "macOS" ? UNSUPPORTED_DARWIN_FOR_TRACED : (
// We use the same constant for both Xcode and the Xcode SDK since we treat them
// as equivalent indicators for whether traced analysis is unsupported.
UNSUPPORTED_XCODE_FOR_TRACED
);
const baseMessage = [
`Traced analysis is not supported on ${product} ${unsupportedVersion} or above.`,
`Configure your analysis to run on macOS ${UNSUPPORTED_DARWIN_FOR_TRACED - 1} or below`,
`and Xcode ${UNSUPPORTED_XCODE_FOR_TRACED - 1} or below.`
].join(" ");
const attributeName = product === "macOS" ? "darwinVersion" : "xcodeVersion";
const attributeName = diagnosticAttributeName(product);
for (const language of tracedLanguages) {
addDiagnostic(
config,
language,
makeDiagnostic(
`codeql-action/unsupported-traced-analysis-${product.toLowerCase()}`,
`codeql-action/unsupported-traced-analysis-${product.toLowerCase().replaceAll(" ", "-")}`,
`Traced analysis is not supported on this version of ${product}`,
{
attributes: {
@@ -194005,28 +194127,27 @@ async function checkRunnerSupportsTracedAnalysis(action, config, codeql) {
config,
tracedLanguages,
abortUnsupportedTracedAnalysis,
"macOS",
version
{ product: "macOS", version }
);
return;
}
if (isSelfHostedRunner(action.env)) {
action.logger.debug(
"Skipping traced analysis compatibility check for Xcode because we are on a self-hosted runner."
);
return;
const xcodeVer = xcodeVersionFromLink(action.logger) ?? await xcodeVersionFromXcodeBuild(action.logger);
let xcodeProduct = void 0;
if (xcodeVer !== void 0) {
xcodeProduct = { product: "Xcode", version: xcodeVer };
} else {
const xcodeSdkVer = await xcodeSdkVersion(action.logger);
xcodeProduct = xcodeSdkVer !== void 0 ? { product: "Xcode SDK", version: xcodeSdkVer } : void 0;
}
const xcodeVer = xcodeVersion(action.logger);
if (xcodeVer !== void 0 && xcodeVer.major >= UNSUPPORTED_XCODE_FOR_TRACED) {
if (xcodeProduct !== void 0 && xcodeProduct.version.major >= UNSUPPORTED_XCODE_FOR_TRACED) {
handleUnsupportedVersion(
action.logger,
config,
tracedLanguages,
abortUnsupportedTracedAnalysis,
"Xcode",
xcodeVer
xcodeProduct
);
} else if (xcodeVer === void 0) {
} else if (xcodeProduct === void 0) {
action.logger.info(
"Unable to determine Xcode version. Skipping Xcode compatibility check."
);
@@ -194644,7 +194765,7 @@ async function run3(actionState) {
}
if (config.languages.includes("go" /* go */) && process.platform === "linux") {
try {
const goBinaryPath = await io7.which("go", true);
const goBinaryPath = await io8.which("go", true);
const fileOutput = await getFileType(goBinaryPath);
if (fileOutput.includes("statically linked") && !await codeql.supportsFeature(
"indirectTracingSupportsStaticBinaries" /* IndirectTracingSupportsStaticBinaries */
@@ -195999,8 +196120,8 @@ function generateCertificateAuthority() {
// src/start-proxy/environment.ts
var fs33 = __toESM(require("fs"));
var path28 = __toESM(require("path"));
var toolrunner5 = __toESM(require_toolrunner());
var io8 = __toESM(require_io());
var toolrunner6 = __toESM(require_toolrunner());
var io9 = __toESM(require_io());
function checkEnvVar(logger2, name) {
const value = process.env[name];
if (isDefined2(value)) {
@@ -196090,9 +196211,9 @@ function checkJdkSettings(logger2, jdkHome) {
}
async function showJavaSettings(logger2) {
try {
const java = await io8.which("java", true);
const java = await io9.which("java", true);
let output = "";
await new toolrunner5.ToolRunner(
await new toolrunner6.ToolRunner(
java,
["-XshowSettings:all", "-XshowSettings:security:all", "-version"],
{
+181 -32
View File
@@ -1,5 +1,8 @@
import fs from "fs";
import type { ExecOptions } from "@actions/exec";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import test from "ava";
import * as sinon from "sinon";
@@ -344,38 +347,6 @@ test.serial(
}),
);
test("checkRunnerSupportsTracedAnalysis doesn't perform Xcode check on self-hosted runners", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
env.set(ActionsEnvVars.RUNNER_ENVIRONMENT, "self-hosted");
const features = createFeatures([Feature.CheckUnsupportedTracedAnalysis]);
const codeql = await getCodeQLWithTracedStub(
"codeql-for-testing",
logger,
env,
);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
await checkRunnerSupportsTracedAnalysis(
initAllState({
logger,
platform: "darwin",
osRelease: "26.0.0",
env,
features,
}),
createTestConfig({ tempDir: tmpDir, languages: [BuiltInLanguage.go] }),
codeql,
);
checkExpectedLogMessages(t, logger.messages, [
"Skipping traced analysis compatibility check for Xcode because we are on a self-hosted runner.",
]);
}));
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis doesn't add a diagnostic if Xcode version is supported",
@@ -426,6 +397,86 @@ test.serial(
}),
);
test.serial(
"checkRunnerSupportsTracedAnalysis attempts fallbacks if needed",
async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const features = createFeatures([Feature.CheckUnsupportedTracedAnalysis]);
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.throws(new Error("Not found"));
const which = sinon.stub(io, "which").resolves("/path/to/xcrun");
const exec = sinon.stub<[], Promise<number>>().callsFake(async () => {
throw new Error("Error to force next fallback");
});
const runner = sinon.createStubInstance(toolrunner.ToolRunner, { exec });
const toolrunnerStub = sinon
.stub(toolrunner, "ToolRunner")
.callsFake(() => {
return runner;
});
const codeql = await getCodeQLWithTracedStub(
"codeql-for-testing",
logger,
env,
);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
const config = createTestConfig({
tempDir: tmpDir,
languages: [BuiltInLanguage.swift],
});
await checkRunnerSupportsTracedAnalysis(
initAllState({
logger,
platform: "darwin",
osRelease: "26.0.0",
env,
features,
}),
config,
codeql,
);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
t.is(which.callCount, 2);
t.deepEqual(which.args[0], ["xcrun", true]);
t.deepEqual(which.args[1], ["xcrun", true]);
t.is(toolrunnerStub.callCount, 2);
t.is(exec.callCount, 2);
t.is(toolrunnerStub.args[0][0], "/path/to/xcrun");
t.deepEqual(toolrunnerStub.args[0][1], ["xcodebuild", "-version"]);
t.is(toolrunnerStub.args[1][0], "/path/to/xcrun");
t.deepEqual(toolrunnerStub.args[1][1], [
"--sdk",
"macosx",
"--show-sdk-version",
]);
checkExpectedLogMessages(t, logger.messages, [
"Unable to determine Xcode version: Not found",
"Unable to determine Xcode version from xcodebuild: Error to force next fallback",
"Unable to determine Xcode SDK version: Error to force next fallback",
]);
checkUnexpectedLogMessages(t, logger.messages, [
"Traced analysis is not supported on Xcode 27",
]);
t.is(addDiagnostic.callCount, 0);
}),
);
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis logs and adds diagnostic if Xcode version is unsupported",
@@ -496,6 +547,104 @@ test.serial(
}),
);
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis logs and adds diagnostic if Xcode SDK version is unsupported",
async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const features = createFeatures([Feature.CheckUnsupportedTracedAnalysis]);
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.throws(new Error("Not found"));
const which = sinon.stub(io, "which").resolves("/path/to/xcrun");
let stdoutListener: ((data: Buffer) => void) | undefined = undefined;
const exec = sinon.stub<[], Promise<number>>().callsFake(async () => {
if (stdoutListener !== undefined) {
stdoutListener(Buffer.from("Xcode 27.5"));
stdoutListener(Buffer.from("Build version 27F9"));
return 0;
} else {
throw new Error("Error to force next fallback");
}
});
const runner = sinon.createStubInstance(toolrunner.ToolRunner, {
exec,
});
const toolrunnerStub = sinon
.stub(toolrunner, "ToolRunner")
.callsFake((_cmd, args: string[], opts: ExecOptions) => {
if (args[0] !== "xcodebuild") {
stdoutListener = opts.listeners?.stdout;
}
return runner;
});
const codeql = await getCodeQLWithTracedStub(
"codeql-for-testing",
logger,
env,
);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
const config = createTestConfig({
tempDir: tmpDir,
languages: [BuiltInLanguage.swift],
});
await checkRunnerSupportsTracedAnalysis(
initAllState({
logger,
platform: "darwin",
osRelease: "26.0.0",
env,
features,
}),
config,
codeql,
);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
t.is(which.callCount, 2);
t.is(toolrunnerStub.callCount, 2);
t.is(exec.callCount, 2);
checkExpectedLogMessages(t, logger.messages, [
"Unable to determine Xcode version from xcodebuild",
"Traced analysis is not supported on Xcode SDK 27",
]);
t.is(addDiagnostic.callCount, 1);
t.like(addDiagnostic.args[0], [
config,
BuiltInLanguage.swift,
{
attributes: {
languages: [BuiltInLanguage.swift],
xcodeSdkVersion: "27.5.0",
},
severity: "warning",
source: {
id: "codeql-action/unsupported-traced-analysis-xcode-sdk",
name: "Traced analysis is not supported on this version of Xcode SDK",
},
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
} satisfies Partial<diagnostics.DiagnosticMessage>,
]);
}),
);
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis throws if Xcode version is unsupported and FF is enabled",
+60 -27
View File
@@ -1,13 +1,17 @@
import * as semver from "semver";
import type { ActionState, Logger } from "../action-common";
import { isSelfHostedRunner } from "../actions-util";
import type { CodeQL } from "../codeql";
import type { ActionConfig } from "../config/action-config";
import { addDiagnostic, makeDiagnostic } from "../diagnostics";
import { DocUrl } from "../doc-url";
import { Feature } from "../feature-flags";
import { darwinVersion, xcodeVersion } from "../platform";
import {
darwinVersion,
xcodeVersionFromLink,
xcodeSdkVersion,
xcodeVersionFromXcodeBuild,
} from "../platform";
import { ConfigurationError } from "../util";
import { checkSwiftCompatibility } from "./swift";
@@ -18,6 +22,27 @@ export const UNSUPPORTED_DARWIN_FOR_TRACED = 27;
/** Xcode 27 and above do not support traced extraction. */
export const UNSUPPORTED_XCODE_FOR_TRACED = 27;
/** Names of products that we check the versions of here. */
type ProductName = "macOS" | "Xcode" | "Xcode SDK";
/** A version along with the product it relates to. */
type ProductVersion = {
product: ProductName;
version: semver.SemVer;
};
/** Gets the appropriate attribute name for the diagnostic based on {@link product}. */
function diagnosticAttributeName(product: ProductName): string {
switch (product) {
case "macOS":
return "darwinVersion";
case "Xcode":
return "xcodeVersion";
case "Xcode SDK":
return "xcodeSdkVersion";
}
}
/**
* Creates a diagnostic indicating that `version` of `product` is unsupported for traced analysis.
* Depending on `skipUnsupportedTracedAnalysis`, this function then either throws a {@link ConfigurationError}
@@ -27,28 +52,30 @@ export const UNSUPPORTED_XCODE_FOR_TRACED = 27;
* @param config The CodeQL Action configuration.
* @param tracedLanguages Traced languages that are configured for analysis.
* @param abortUnsupportedTracedAnalysis Whether this is a fatal error.
* @param product The product that the version is unsupported of.
* @param version The unsupported version.
* @param productVersion A description of the product and its version that is unsupported.
*/
function handleUnsupportedVersion(
logger: Logger,
config: ActionConfig<"dbLocation" | "tempDir">,
tracedLanguages: string[],
abortUnsupportedTracedAnalysis: boolean,
product: "macOS" | "Xcode",
version: semver.SemVer,
productVersion: ProductVersion,
) {
const { product, version } = productVersion;
const unsupportedVersion =
product === "macOS"
? UNSUPPORTED_DARWIN_FOR_TRACED
: UNSUPPORTED_XCODE_FOR_TRACED;
: // We use the same constant for both Xcode and the Xcode SDK since we treat them
// as equivalent indicators for whether traced analysis is unsupported.
UNSUPPORTED_XCODE_FOR_TRACED;
const baseMessage = [
`Traced analysis is not supported on ${product} ${unsupportedVersion} or above.`,
`Configure your analysis to run on macOS ${UNSUPPORTED_DARWIN_FOR_TRACED - 1} or below`,
`and Xcode ${UNSUPPORTED_XCODE_FOR_TRACED - 1} or below.`,
].join(" ");
const attributeName = product === "macOS" ? "darwinVersion" : "xcodeVersion";
const attributeName = diagnosticAttributeName(product);
// Create a diagnostic that will show up on the TSP.
for (const language of tracedLanguages) {
@@ -56,7 +83,7 @@ function handleUnsupportedVersion(
config,
language,
makeDiagnostic(
`codeql-action/unsupported-traced-analysis-${product.toLowerCase()}`,
`codeql-action/unsupported-traced-analysis-${product.toLowerCase().replaceAll(" ", "-")}`,
`Traced analysis is not supported on this version of ${product}`,
{
attributes: {
@@ -177,8 +204,7 @@ export async function checkRunnerSupportsTracedAnalysis(
config,
tracedLanguages,
abortUnsupportedTracedAnalysis,
"macOS",
version,
{ product: "macOS", version },
);
// Return early so that we don't end up producing more-or-less the same log message/diagnostic
@@ -186,32 +212,39 @@ export async function checkRunnerSupportsTracedAnalysis(
return;
}
// Skip if we are on a self-hosted runner since the current Xcode version detection
// is only validated on hosted runners.
if (isSelfHostedRunner(action.env)) {
action.logger.debug(
"Skipping traced analysis compatibility check for Xcode because we are on a self-hosted runner.",
);
return;
// Determining whether the Xcode version is supported only makes sense on macOS, so we only do it
// after determining that we are running on macOS. We first try to obtain the version of Xcode itself
// starting with the cheapest approach and then call out to an external program. If neither works, we try
// to obtain the version from the SDK instead. Since either Xcode or the SDK at version 27 or above is
// incompatible, either is fine as we only check the major version below.
const xcodeVer =
xcodeVersionFromLink(action.logger) ??
(await xcodeVersionFromXcodeBuild(action.logger));
let xcodeProduct: ProductVersion | undefined = undefined;
if (xcodeVer !== undefined) {
xcodeProduct = { product: "Xcode", version: xcodeVer };
} else {
// Fallback to checking the SDK version, which may be available even if a full Xcode installation is not.
const xcodeSdkVer = await xcodeSdkVersion(action.logger);
xcodeProduct =
xcodeSdkVer !== undefined
? { product: "Xcode SDK", version: xcodeSdkVer }
: undefined;
}
// Determining whether the Xcode version is supported only makes sense on macOS, so we only do it
// after determining that we are running on macOS.
const xcodeVer = xcodeVersion(action.logger);
if (
xcodeVer !== undefined &&
xcodeVer.major >= UNSUPPORTED_XCODE_FOR_TRACED
xcodeProduct !== undefined &&
xcodeProduct.version.major >= UNSUPPORTED_XCODE_FOR_TRACED
) {
handleUnsupportedVersion(
action.logger,
config,
tracedLanguages,
abortUnsupportedTracedAnalysis,
"Xcode",
xcodeVer,
xcodeProduct,
);
} else if (xcodeVer === undefined) {
} else if (xcodeProduct === undefined) {
action.logger.info(
"Unable to determine Xcode version. Skipping Xcode compatibility check.",
);
+211 -62
View File
@@ -1,5 +1,8 @@
import fs from "fs";
import type { ExecOptions } from "@actions/exec/lib/interfaces";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import test from "ava";
import * as sinon from "sinon";
@@ -8,7 +11,9 @@ import {
getBundlePlatform,
darwinVersion,
XCODE_SELECT_LINK_PATH,
xcodeVersion,
xcodeVersionFromLink,
xcodeSdkVersion,
xcodeVersionFromXcodeBuild,
} from "./platform";
import {
checkExpectedLogMessages,
@@ -71,46 +76,52 @@ test("darwinVersion returns semver if parsing succeeds", (t) => {
}
});
test.serial("xcodeVersion returns undefined if symlink doesn't exist", (t) => {
const logger = new RecordingLogger();
test.serial(
"xcodeVersionFromLink returns undefined if symlink doesn't exist",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.throws(new Error("ENOENT: no such file or directory"));
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.throws(new Error("ENOENT: no such file or directory"));
t.is(xcodeVersion(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
t.is(xcodeVersionFromLink(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
checkExpectedLogMessages(t, logger.messages, [
"Unable to determine Xcode version: ENOENT: no such file or directory",
]);
});
test.serial("xcodeVersion returns undefined if file is not a symlink", (t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.throws(new Error("EINVAL: invalid argument"));
t.is(xcodeVersion(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
checkExpectedLogMessages(t, logger.messages, [
"Unable to determine Xcode version: EINVAL: invalid argument",
]);
});
checkExpectedLogMessages(t, logger.messages, [
"Unable to determine Xcode version: ENOENT: no such file or directory",
]);
},
);
test.serial(
"xcodeVersion returns undefined if resolving the symlink returns nothing",
"xcodeVersionFromLink returns undefined if file is not a symlink",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.throws(new Error("EINVAL: invalid argument"));
t.is(xcodeVersionFromLink(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
checkExpectedLogMessages(t, logger.messages, [
"Unable to determine Xcode version: EINVAL: invalid argument",
]);
},
);
test.serial(
"xcodeVersionFromLink returns undefined if resolving the symlink returns nothing",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon.stub(fs, "readlinkSync").returns("");
t.is(xcodeVersion(logger), undefined);
t.is(xcodeVersionFromLink(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
@@ -121,7 +132,7 @@ test.serial(
);
test.serial(
"xcodeVersion returns undefined if resolved path doesn't include pattern",
"xcodeVersionFromLink returns undefined if resolved path doesn't include pattern",
(t) => {
const logger = new RecordingLogger();
@@ -129,7 +140,7 @@ test.serial(
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode.app/Contents/Developer");
t.is(xcodeVersion(logger), undefined);
t.is(xcodeVersionFromLink(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
@@ -139,30 +150,33 @@ test.serial(
},
);
test.serial("xcodeVersion returns undefined if match can't be parsed", (t) => {
const logger = new RecordingLogger();
test.serial(
"xcodeVersionFromLink returns undefined if match can't be parsed",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_00.0.app/Contents/Developer");
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_00.0.app/Contents/Developer");
t.is(xcodeVersion(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
t.is(xcodeVersionFromLink(logger), undefined);
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.",
]);
});
checkExpectedLogMessages(t, logger.messages, [
"Couldn't parse '00.0' as a semantic version.",
]);
},
);
test.serial("xcodeVersion returns version from resolved path", (t) => {
test.serial("xcodeVersionFromLink returns version from resolved path", (t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_16.4.app/Contents/Developer");
const result = xcodeVersion(logger);
const result = xcodeVersionFromLink(logger);
if (t.truthy(result)) {
t.is(result.major, 16);
@@ -173,26 +187,29 @@ test.serial("xcodeVersion returns version from resolved path", (t) => {
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
});
test.serial("xcodeVersion returns major version from resolved path", (t) => {
const logger = new RecordingLogger();
test.serial(
"xcodeVersionFromLink returns major version from resolved path",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_16.app/Contents/Developer");
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_16.app/Contents/Developer");
const result = xcodeVersion(logger);
const result = xcodeVersionFromLink(logger);
if (t.truthy(result)) {
t.is(result.major, 16);
t.is(result.minor, 0);
}
if (t.truthy(result)) {
t.is(result.major, 16);
t.is(result.minor, 0);
}
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
});
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
},
);
test.serial(
"xcodeVersion allows more version components in resolved path",
"xcodeVersionFromLink allows more version components in resolved path",
(t) => {
const logger = new RecordingLogger();
@@ -200,7 +217,7 @@ test.serial(
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_16.5.9.app/Contents/Developer");
const result = xcodeVersion(logger);
const result = xcodeVersionFromLink(logger);
if (t.truthy(result)) {
t.is(result.major, 16);
@@ -211,3 +228,135 @@ test.serial(
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
},
);
test.serial(
"xcodeVersionFromXcodeBuild returns undefined for non-zero exit code",
async (t) => {
const logger = new RecordingLogger();
const which = sinon.stub(io, "which").resolves("/path/to/xcrun");
const exec = sinon.stub<[], Promise<number>>().resolves(1);
const runner = sinon.createStubInstance(toolrunner.ToolRunner, { exec });
const toolrunnerStub = sinon.stub(toolrunner, "ToolRunner").returns(runner);
await t.notThrowsAsync(async () => {
const result = await xcodeVersionFromXcodeBuild(logger);
t.is(result, undefined);
t.is(which.callCount, 1);
t.deepEqual(which.args[0], ["xcrun", true]);
t.is(toolrunnerStub.callCount, 1);
t.is(exec.callCount, 1);
checkExpectedLogMessages(t, logger.messages, [
"xcodebuild returned exit code 1",
]);
});
},
);
test.serial(
"xcodeVersionFromXcodeBuild extracts version from xcrun stdout",
async (t) => {
const logger = new RecordingLogger();
const which = sinon.stub(io, "which").resolves("/path/to/xcrun");
let stdoutListener: ((data: Buffer) => void) | undefined = undefined;
const exec = sinon.stub<[], Promise<number>>().callsFake(async () => {
if (stdoutListener !== undefined) {
stdoutListener(Buffer.from("Xcode 26.5"));
stdoutListener(Buffer.from("Build version 26F9"));
}
return 0;
});
const runner = sinon.createStubInstance(toolrunner.ToolRunner, { exec });
const toolrunnerStub = sinon
.stub(toolrunner, "ToolRunner")
.callsFake((_cmd, _args, opts: ExecOptions) => {
stdoutListener = opts.listeners?.stdout;
return runner;
});
await t.notThrowsAsync(async () => {
const result = await xcodeVersionFromXcodeBuild(logger);
t.not(result, undefined);
t.is(result?.major, 26);
t.is(result?.minor, 5);
t.is(which.callCount, 1);
t.deepEqual(which.args[0], ["xcrun", true]);
t.is(toolrunnerStub.callCount, 1);
t.is(exec.callCount, 1);
});
},
);
test.serial(
"xcodeSdkVersion returns undefined for non-zero exit code",
async (t) => {
const logger = new RecordingLogger();
const which = sinon.stub(io, "which").resolves("/path/to/xcrun");
const exec = sinon.stub<[], Promise<number>>().resolves(1);
const runner = sinon.createStubInstance(toolrunner.ToolRunner, { exec });
const toolrunnerStub = sinon.stub(toolrunner, "ToolRunner").returns(runner);
await t.notThrowsAsync(async () => {
const result = await xcodeSdkVersion(logger);
t.is(result, undefined);
t.is(which.callCount, 1);
t.deepEqual(which.args[0], ["xcrun", true]);
t.is(toolrunnerStub.callCount, 1);
t.is(exec.callCount, 1);
checkExpectedLogMessages(t, logger.messages, [
"xcrun returned exit code 1",
]);
});
},
);
test.serial("xcodeSdkVersion extracts version from xcrun stdout", async (t) => {
const logger = new RecordingLogger();
const which = sinon.stub(io, "which").resolves("/path/to/xcrun");
let stdoutListener: ((data: Buffer) => void) | undefined = undefined;
const exec = sinon.stub<[], Promise<number>>().callsFake(async () => {
if (stdoutListener !== undefined) {
stdoutListener(Buffer.from("26.5"));
}
return 0;
});
const runner = sinon.createStubInstance(toolrunner.ToolRunner, { exec });
const toolrunnerStub = sinon
.stub(toolrunner, "ToolRunner")
.callsFake((_cmd, _args, opts: ExecOptions) => {
stdoutListener = opts.listeners?.stdout;
return runner;
});
await t.notThrowsAsync(async () => {
const result = await xcodeSdkVersion(logger);
t.not(result, undefined);
t.is(result?.major, 26);
t.is(result?.minor, 5);
t.is(which.callCount, 1);
t.deepEqual(which.args[0], ["xcrun", true]);
t.is(toolrunnerStub.callCount, 1);
t.is(exec.callCount, 1);
});
});
+136 -2
View File
@@ -1,5 +1,7 @@
import fs from "fs";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import * as semver from "semver";
import type { ActionState, Logger } from "./action-common";
@@ -68,12 +70,15 @@ export const XCODE_APP_FILENAME_PATTERN = new RegExp(
);
/**
* Tries to determine the version of Xcode that is installed.
* Tries to determine the version of Xcode that is installed from the resolved symbolic link
* at {@link XCODE_SELECT_LINK_PATH}.
*
* @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 {
export function xcodeVersionFromLink(
logger: Logger,
): semver.SemVer | undefined {
try {
// Read what the symbolic link points to. This will throw if `XCODE_SELECT_LINK_PATH` does not
// exist (`ENOENT`) or is not a symbolic link (`EINVAL`).
@@ -109,3 +114,132 @@ export function xcodeVersion(logger: Logger): semver.SemVer | undefined {
return undefined;
}
}
/**
* Tries to determine the version of Xcode that is installed by running `xcrun xcodebuild -version`.
*
* @param logger The logger to use.
* @returns The Xcode version or `undefined` if it couldn't be determined.
*/
export async function xcodeVersionFromXcodeBuild(
logger: Logger,
): Promise<semver.SemVer | undefined> {
try {
const xcrun = await io.which("xcrun", true);
let stdout = "";
let stderr = "";
const exitCode = await new toolrunner.ToolRunner(
xcrun,
["xcodebuild", "-version"],
{
silent: true,
ignoreReturnCode: true,
listeners: {
stdout: (data: Buffer) => {
stdout += data.toString("utf-8");
},
stderr: (data: Buffer) => {
stderr += data.toString("utf-8");
},
},
},
).exec();
if (exitCode !== 0) {
logger.debug(`xcodebuild returned exit code ${exitCode}`);
logger.debug(`stdout:\n${stdout}`);
logger.debug(`stderr:\n${stderr}`);
return undefined;
}
// The output contains a couple of lines, but we only care about the product version.
const match = stdout.match(/^Xcode (\d+(\.\d+)?)/);
if (match === null) {
logger.debug(
`Output doesn't appear to contain the Xcode product version.`,
);
logger.debug(`stdout:\n${stdout}`);
logger.debug(`stderr:\n${stderr}`);
return undefined;
}
// We expect the output to only be a version string. Try to parse it into a semver.
const version = semver.coerce(match[1].trim());
if (version === null) {
logger.debug(
`xcodebuild's output couldn't be parsed as a version:\n${stdout}`,
);
logger.debug(`stderr:\n${stderr}`);
return undefined;
}
return version;
} catch (err) {
logger.debug(
`Unable to determine Xcode version from xcodebuild: ${getErrorMessage(err)}`,
);
return undefined;
}
}
/**
* Tries to determine the version of the Xcode SDK that is installed by running
* `xcrun --sdk macosx --show-sdk-version`.
*
* @param logger The logger to use.
* @returns The Xcode SDK version or `undefined` if it couldn't be determined.
*/
export async function xcodeSdkVersion(
logger: Logger,
): Promise<semver.SemVer | undefined> {
try {
const xcrun = await io.which("xcrun", true);
let stdout = "";
let stderr = "";
const exitCode = await new toolrunner.ToolRunner(
xcrun,
["--sdk", "macosx", "--show-sdk-version"],
{
silent: true,
ignoreReturnCode: true,
listeners: {
stdout: (data: Buffer) => {
stdout += data.toString("utf-8");
},
stderr: (data: Buffer) => {
stderr += data.toString("utf-8");
},
},
},
).exec();
if (exitCode !== 0) {
logger.debug(`xcrun returned exit code ${exitCode}`);
logger.debug(`stdout:\n${stdout}`);
logger.debug(`stderr:\n${stderr}`);
return undefined;
}
// We expect the output to only be a version string. Try to parse it into a semver.
const version = semver.coerce(stdout.trim());
if (version === null) {
logger.debug(
`xcrun's output couldn't be parsed as a version:\n${stdout}`,
);
logger.debug(`stderr:\n${stderr}`);
return undefined;
}
return version;
} catch (err) {
logger.debug(
`Unable to determine Xcode SDK version: ${getErrorMessage(err)}`,
);
return undefined;
}
}