diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a96e0a9c..950e8ffbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] -No user facing changes. +- Update default CodeQL bundle version to [2.27.4](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.4). [#4284](https://github.com/github/codeql-action/pull/4284) ## 4.39.1 - 21 Oct 2026 diff --git a/lib/defaults.json b/lib/defaults.json index cde5136e2..cfa3f6c1c 100644 --- a/lib/defaults.json +++ b/lib/defaults.json @@ -1,6 +1,6 @@ { - "bundleVersion": "codeql-bundle-v2.27.3", - "cliVersion": "2.27.3", - "priorBundleVersion": "codeql-bundle-v2.27.2", - "priorCliVersion": "2.27.2" + "bundleVersion": "codeql-bundle-v2.27.4", + "cliVersion": "2.27.4", + "priorBundleVersion": "codeql-bundle-v2.27.3", + "priorCliVersion": "2.27.3" } diff --git a/lib/entry-points.js b/lib/entry-points.js index 49f7e9494..d8bba7e27 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -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,11 +42426,11 @@ 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")); - var semver12 = __importStar2(require_semver3()); + var semver13 = __importStar2(require_semver3()); var util4 = __importStar2(require("util")); var constants_1 = require_constants7(); var versionSalt = "1.0"; @@ -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; }); } @@ -42523,7 +42523,7 @@ var require_cacheUtils = __commonJS({ function getCompressionMethod() { return __awaiter2(this, void 0, void 0, function* () { const versionOutput = yield getVersion("zstd", ["--quiet"]); - const version = semver12.clean(versionOutput); + const version = semver13.clean(versionOutput); core29.debug(`zstd version: ${version}`); if (versionOutput === "") { return constants_1.CompressionMethod.Gzip; @@ -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) { @@ -50678,11 +50678,11 @@ var require_tracingPolicy = __commonJS({ var import_log = require_log3(); var import_core_util = require_commonjs4(); var import_restError = require_restError3(); - var import_util62 = require_internal3(); + var import_util61 = require_internal3(); var tracingPolicyName2 = "tracingPolicy"; function tracingPolicy2(options = {}) { const userAgentPromise = (0, import_userAgent.getUserAgentValue)(options.userAgentPrefix); - const sanitizer = new import_util62.Sanitizer({ + const sanitizer = new import_util61.Sanitizer({ additionalAllowedQueryParameters: options.additionalAllowedQueryParameters }); const tracingClient = tryCreateTracingClient(); @@ -54308,11 +54308,11 @@ var require_response2 = __commonJS({ }); module2.exports = __toCommonJS2(response_exports); var import_core_rest_pipeline = require_commonjs6(); - var import_util62 = require_util7(); + var import_util61 = require_util7(); var originalResponse = /* @__PURE__ */ Symbol("Original FullOperationResponse"); function toCompatResponse2(response, options) { - let request3 = (0, import_util62.toWebResourceLike)(response.request); - let headers = (0, import_util62.toHttpHeadersLike)(response.headers); + let request3 = (0, import_util61.toWebResourceLike)(response.request); + let headers = (0, import_util61.toHttpHeadersLike)(response.headers); if (options?.createProxy) { return new Proxy(response, { get(target, prop, receiver) { @@ -54353,7 +54353,7 @@ var require_response2 = __commonJS({ return { ...compatResponse, headers, - request: (0, import_util62.toPipelineRequest)(compatResponse.request) + request: (0, import_util61.toPipelineRequest)(compatResponse.request) }; } } @@ -54460,7 +54460,7 @@ var require_requestPolicyFactoryPolicy = __commonJS({ requestPolicyFactoryPolicyName: () => requestPolicyFactoryPolicyName2 }); module2.exports = __toCommonJS2(requestPolicyFactoryPolicy_exports); - var import_util62 = require_util7(); + var import_util61 = require_util7(); var import_response = require_response2(); var HttpPipelineLogLevel2 = /* @__PURE__ */ ((HttpPipelineLogLevel22) => { HttpPipelineLogLevel22[HttpPipelineLogLevel22["ERROR"] = 1] = "ERROR"; @@ -54484,14 +54484,14 @@ var require_requestPolicyFactoryPolicy = __commonJS({ async sendRequest(request3, next) { let httpPipeline = { async sendRequest(httpRequest) { - const response2 = await next((0, import_util62.toPipelineRequest)(httpRequest)); + const response2 = await next((0, import_util61.toPipelineRequest)(httpRequest)); return (0, import_response.toCompatResponse)(response2, { createProxy: true }); } }; for (const factory of orderedFactories) { httpPipeline = factory.create(httpPipeline, mockRequestPolicyOptions); } - const webResourceLike = (0, import_util62.toWebResourceLike)(request3, { createProxy: true }); + const webResourceLike = (0, import_util61.toWebResourceLike)(request3, { createProxy: true }); const response = await httpPipeline.sendRequest(webResourceLike); return (0, import_response.toPipelineResponse)(response); } @@ -54526,12 +54526,12 @@ var require_httpClientAdapter = __commonJS({ }); module2.exports = __toCommonJS2(httpClientAdapter_exports); var import_response = require_response2(); - var import_util62 = require_util7(); + var import_util61 = require_util7(); function convertHttpClient2(requestPolicyClient) { return { sendRequest: async (request3) => { const response = await requestPolicyClient.sendRequest( - (0, import_util62.toWebResourceLike)(request3, { createProxy: true }) + (0, import_util61.toWebResourceLike)(request3, { createProxy: true }) ); return (0, import_response.toPipelineResponse)(response); } @@ -54569,7 +54569,7 @@ var require_commonjs9 = __commonJS({ disableKeepAlivePolicyName: () => import_disableKeepAlivePolicy.disableKeepAlivePolicyName, requestPolicyFactoryPolicyName: () => import_requestPolicyFactoryPolicy.requestPolicyFactoryPolicyName, toCompatResponse: () => import_response.toCompatResponse, - toHttpHeadersLike: () => import_util62.toHttpHeadersLike + toHttpHeadersLike: () => import_util61.toHttpHeadersLike }); module2.exports = __toCommonJS2(src_exports); var import_extendedClient = require_extendedClient(); @@ -54577,7 +54577,7 @@ var require_commonjs9 = __commonJS({ var import_requestPolicyFactoryPolicy = require_requestPolicyFactoryPolicy(); var import_disableKeepAlivePolicy = require_disableKeepAlivePolicy(); var import_httpClientAdapter = require_httpClientAdapter(); - var import_util62 = require_util7(); + var import_util61 = require_util7(); } }); @@ -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"); }); } @@ -114072,7 +114072,7 @@ var require_manifest = __commonJS({ exports2._findMatch = _findMatch; exports2._getOsVersion = _getOsVersion; exports2._readLinuxVersionFile = _readLinuxVersionFile; - var semver12 = __importStar2(require_semver2()); + var semver13 = __importStar2(require_semver2()); var core_1 = require_core(); var os8 = require("os"); var cp = require("child_process"); @@ -114086,7 +114086,7 @@ var require_manifest = __commonJS({ for (const candidate of candidates) { const version = candidate.version; (0, core_1.debug)(`check ${version} satisfies ${versionSpec}`); - if (semver12.satisfies(version, versionSpec) && (!stable || candidate.stable === stable)) { + if (semver13.satisfies(version, versionSpec) && (!stable || candidate.stable === stable)) { file = candidate.files.find((item) => { (0, core_1.debug)(`${item.arch}===${archFilter} && ${item.platform}===${platFilter}`); let chk = item.arch === archFilter && item.platform === platFilter; @@ -114095,7 +114095,7 @@ var require_manifest = __commonJS({ if (osVersion === item.platform_version) { chk = true; } else { - chk = semver12.satisfies(osVersion, item.platform_version); + chk = semver13.satisfies(osVersion, item.platform_version); } } return chk; @@ -114348,14 +114348,14 @@ 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()); var os8 = __importStar2(require("os")); var path30 = __importStar2(require("path")); var httpm = __importStar2(require_lib()); - var semver12 = __importStar2(require_semver2()); + var semver13 = __importStar2(require_semver2()); var stream2 = __importStar2(require("stream")); var util4 = __importStar2(require("util")); var assert_1 = require("assert"); @@ -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"); @@ -114628,7 +114628,7 @@ var require_tool_cache = __commonJS({ } function cacheDir2(sourceDir, tool, version, arch2) { return __awaiter2(this, void 0, void 0, function* () { - version = semver12.clean(version) || version; + version = semver13.clean(version) || version; arch2 = arch2 || os8.arch(); core29.debug(`Caching tool ${tool} ${version} ${arch2}`); core29.debug(`source dir: ${sourceDir}`); @@ -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; @@ -114646,7 +114646,7 @@ var require_tool_cache = __commonJS({ } function cacheFile(sourceFile, targetFile, tool, version, arch2) { return __awaiter2(this, void 0, void 0, function* () { - version = semver12.clean(version) || version; + version = semver13.clean(version) || version; arch2 = arch2 || os8.arch(); core29.debug(`Caching tool ${tool} ${version} ${arch2}`); core29.debug(`source file: ${sourceFile}`); @@ -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; }); @@ -114676,7 +114676,7 @@ var require_tool_cache = __commonJS({ } let toolPath = ""; if (versionSpec) { - versionSpec = semver12.clean(versionSpec) || ""; + versionSpec = semver13.clean(versionSpec) || ""; const cachePath = path30.join(_getCacheDirectory(), toolName, versionSpec, arch2); core29.debug(`checking cache: ${cachePath}`); if (fs34.existsSync(cachePath) && fs34.existsSync(`${cachePath}.complete`)) { @@ -114750,31 +114750,31 @@ var require_tool_cache = __commonJS({ if (!dest) { dest = path30.join(_getTempDirectory(), crypto3.randomUUID()); } - yield io9.mkdirP(dest); + yield io10.mkdirP(dest); return dest; }); } function _createToolPath(tool, version, arch2) { return __awaiter2(this, void 0, void 0, function* () { - const folderPath = path30.join(_getCacheDirectory(), tool, semver12.clean(version) || version, arch2 || ""); + const folderPath = path30.join(_getCacheDirectory(), tool, semver13.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; }); } function _completeToolPath(tool, version, arch2) { - const folderPath = path30.join(_getCacheDirectory(), tool, semver12.clean(version) || version, arch2 || ""); + const folderPath = path30.join(_getCacheDirectory(), tool, semver13.clean(version) || version, arch2 || ""); const markerPath = `${folderPath}.complete`; fs34.writeFileSync(markerPath, ""); core29.debug("finished caching tool"); } function isExplicitVersion(versionSpec) { - const c = semver12.clean(versionSpec) || ""; + const c = semver13.clean(versionSpec) || ""; core29.debug(`isExplicit: ${c}`); - const valid4 = semver12.valid(c) != null; + const valid4 = semver13.valid(c) != null; core29.debug(`explicit? ${valid4}`); return valid4; } @@ -114782,14 +114782,14 @@ var require_tool_cache = __commonJS({ let version = ""; core29.debug(`evaluating ${versions.length} versions`); versions = versions.sort((a, b) => { - if (semver12.gt(a, b)) { + if (semver13.gt(a, b)) { return 1; } return -1; }); for (let i = versions.length - 1; i >= 0; i--) { const potential = versions[i]; - const satisfied = semver12.satisfies(potential, versionSpec); + const satisfied = semver13.satisfies(potential, versionSpec); if (satisfied) { version = potential; break; @@ -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; @@ -173905,6 +173905,17 @@ var Env = class extends ReadOnlyEnv { core.exportVariable(name, val); } } + /** + * Set some initial environment variables that we can set even without + * knowing what version of CodeQL we're running. + */ + initialise(version) { + this.export("CODEQL_ACTION_FEATURE_MULTI_LANGUAGE" /* FEATURE_MULTI_LANGUAGE */, "false"); + this.export("CODEQL_ACTION_FEATURE_SANDWICH" /* FEATURE_SANDWICH */, "false"); + this.export("CODEQL_ACTION_FEATURE_SARIF_COMBINE" /* FEATURE_SARIF_COMBINE */, "true"); + this.export("CODEQL_ACTION_FEATURE_WILL_UPLOAD" /* FEATURE_WILL_UPLOAD */, "true"); + this.export("CODEQL_ACTION_VERSION" /* VERSION */, version); + } /** Gets a value indicating whether `set` was called at least once. */ hasChanged() { return this.changed; @@ -177334,6 +177345,85 @@ function checkSchema(schema, obj, options = {}, path30 = "") { return result; } +// src/errors/index.ts +function wrapError(error3) { + return error3 instanceof Error ? error3 : new Error(String(error3)); +} +function getErrorMessage(error3) { + return error3 instanceof Error ? error3.message : String(error3); +} +function isErrnoException(error3) { + return typeof error3 === "object" && error3 !== null && "code" in error3 && typeof error3.code === "string"; +} +function isDiskConfigurationError(e) { + if (!(e instanceof Error)) { + return false; + } + return ( + // out of disk space + e.message.includes("ENOSPC") || // access denied + e.message.includes("EACCES") + ); +} +var ConfigurationError = class extends Error { +}; +var HTTPError = class extends Error { + status; + constructor(message, status) { + super(message); + this.status = status; + } +}; +function asHTTPError(arg) { + if (!isObject2(arg) || !isString(arg.message)) { + return void 0; + } + if (Number.isInteger(arg.status)) { + return new HTTPError(arg.message, arg.status); + } + if (Number.isInteger(arg.httpStatusCode)) { + return new HTTPError(arg.message, arg.httpStatusCode); + } + return void 0; +} + +// src/github.ts +var GITHUB_DOTCOM_URL = "https://github.com"; +function parseGitHubUrl(inputUrl) { + const originalUrl = inputUrl; + if (inputUrl.indexOf("://") === -1) { + inputUrl = `https://${inputUrl}`; + } + if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) { + throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`); + } + let url2; + try { + url2 = new URL(inputUrl); + } catch { + throw new ConfigurationError(`"${originalUrl}" is not a valid URL`); + } + if (url2.hostname === "github.com" || url2.hostname === "api.github.com") { + return GITHUB_DOTCOM_URL; + } + if (url2.pathname.indexOf("/api/v3") !== -1) { + url2.pathname = url2.pathname.substring(0, url2.pathname.indexOf("/api/v3")); + } + if (url2.hostname.startsWith("api.")) { + url2.hostname = url2.hostname.substring(4); + } + if (!url2.pathname.endsWith("/")) { + url2.pathname = `${url2.pathname}/`; + } + return url2.toString(); +} +function getCurrentInstance(env) { + return { + url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) + }; +} + // src/util.ts var BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; var BROKEN_VERSIONS = ["0.0.0-20211207"]; @@ -177595,37 +177685,6 @@ var ExhaustivityCheckingError = class extends Error { function assertNever(value) { throw new ExhaustivityCheckingError(value); } -function initializeEnvironment(version, env = getEnv()) { - env.export("CODEQL_ACTION_FEATURE_MULTI_LANGUAGE" /* FEATURE_MULTI_LANGUAGE */, "false"); - env.export("CODEQL_ACTION_FEATURE_SANDWICH" /* FEATURE_SANDWICH */, "false"); - env.export("CODEQL_ACTION_FEATURE_SARIF_COMBINE" /* FEATURE_SARIF_COMBINE */, "true"); - env.export("CODEQL_ACTION_FEATURE_WILL_UPLOAD" /* FEATURE_WILL_UPLOAD */, "true"); - env.export("CODEQL_ACTION_VERSION" /* VERSION */, version); -} -var HTTPError = class extends Error { - status; - constructor(message, status) { - super(message); - this.status = status; - } -}; -var ConfigurationError = class extends Error { -}; -function asHTTPError(arg) { - if (!isObject2(arg) || !isString(arg.message)) { - return void 0; - } - if (Number.isInteger(arg.status)) { - return new HTTPError(arg.message, arg.status); - } - if (Number.isInteger(arg.httpStatusCode)) { - return new HTTPError(arg.message, arg.httpStatusCode); - } - return void 0; -} -async function codeQlVersionAtLeast(codeql, requiredVersion) { - return semver.gte((await codeql.getVersion()).version, requiredVersion); -} function getBaseDatabaseOidsFilePath(config) { return path.join(config.dbLocation, BASE_DATABASE_OIDS_FILE_NAME); } @@ -177767,15 +177826,6 @@ function parseMatrixInput(matrixInput) { ); } } -function wrapError(error3) { - return error3 instanceof Error ? error3 : new Error(String(error3)); -} -function getErrorMessage(error3) { - return error3 instanceof Error ? error3.message : String(error3); -} -function isErrnoException(error3) { - return typeof error3 === "object" && error3 !== null && "code" in error3 && typeof error3.code === "string"; -} function prettyPrintPack(pack) { return `${pack.name}${pack.version ? `@${pack.version}` : ""}${pack.path ? `:${pack.path}` : ""}`; } @@ -178432,43 +178482,6 @@ retry.VERSION = VERSION7; // src/api-client.ts var import_undici = __toESM(require_undici()); -// src/github.ts -var GITHUB_DOTCOM_URL = "https://github.com"; -function parseGitHubUrl(inputUrl) { - const originalUrl = inputUrl; - if (inputUrl.indexOf("://") === -1) { - inputUrl = `https://${inputUrl}`; - } - if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) { - throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`); - } - let url2; - try { - url2 = new URL(inputUrl); - } catch { - throw new ConfigurationError(`"${originalUrl}" is not a valid URL`); - } - if (url2.hostname === "github.com" || url2.hostname === "api.github.com") { - return GITHUB_DOTCOM_URL; - } - if (url2.pathname.indexOf("/api/v3") !== -1) { - url2.pathname = url2.pathname.substring(0, url2.pathname.indexOf("/api/v3")); - } - if (url2.hostname.startsWith("api.")) { - url2.hostname = url2.hostname.substring(4); - } - if (!url2.pathname.endsWith("/")) { - url2.pathname = `${url2.pathname}/`; - } - return url2.toString(); -} -function getCurrentInstance(env) { - return { - url: env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), - apiURL: env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) - }; -} - // src/repository.ts function isRepositoryNwoStr(val) { return val.includes("/"); @@ -179587,8 +179600,8 @@ var path6 = __toESM(require("path")); var semver3 = __toESM(require_semver2()); // src/defaults.json -var bundleVersion = "codeql-bundle-v2.27.3"; -var cliVersion = "2.27.3"; +var bundleVersion = "codeql-bundle-v2.27.4"; +var cliVersion = "2.27.4"; // src/overlay/index.ts var fs5 = __toESM(require("fs")); @@ -180030,7 +180043,7 @@ var OfflineFeatures = class { } const minimumVersion2 = config.minimumVersion; if (codeql && minimumVersion2) { - if (!await codeQlVersionAtLeast(codeql, minimumVersion2)) { + if (!await codeql.isVersionAtLeast(minimumVersion2)) { this.logger.debug( `Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum version ${minimumVersion2}.` ); @@ -180446,13 +180459,14 @@ 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()); +var semver11 = __toESM(require_semver2()); // src/cli-errors.ts var SUPPORTED_PLATFORMS = [ @@ -182447,10 +182461,7 @@ function runnerHasSufficientDiskSpace(diskUsage, logger2, minimumDiskSpaceMb) { return true; } async function runnerHasSufficientMemory(codeql, ramInput, logger2) { - if (await codeQlVersionAtLeast( - codeql, - CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE - )) { + if (await codeql.isVersionAtLeast(CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE)) { logger2.debug( `Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.` ); @@ -182588,7 +182599,7 @@ async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDataba ); return new Failure("incompatible-build-mode" /* IncompatibleBuildMode */); } - if (!await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION)) { + if (!await codeql.isVersionAtLeast(CODEQL_OVERLAY_MINIMUM_VERSION)) { logger2.warning( `Cannot build an ${overlayDatabaseMode} database because the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. Falling back to creating a normal full database instead.` ); @@ -183421,6 +183432,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 +183461,7 @@ var XCODE_SELECT_LINK_PATH = "/private/var/db/xcode_select_link"; var XCODE_APP_FILENAME_PATTERN = new RegExp( /(?Xcode_(?\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 +183489,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 +183777,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: { @@ -184787,16 +184905,6 @@ var CODEQL_NEXT_MINIMUM_VERSION = "2.22.4"; var GHES_VERSION_MOST_RECENTLY_DEPRECATED = "3.18"; var GHES_MOST_RECENT_DEPRECATION_DATE = "2026-10-16"; var EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++"; -function isDiskConfigurationError(e) { - if (!(e instanceof Error)) { - return false; - } - return ( - // out of disk space - e.message.includes("ENOSPC") || // access denied - e.message.includes("EACCES") - ); -} async function setupCodeQL(toolsInput, apiDetails, tempDir, variant, defaultCliVersion, rawLanguages, otherLanguagePacksReason, useOverlayAwareDefaultCliVersion, features, logger2, checkVersion) { try { const { @@ -184872,6 +184980,9 @@ async function getCodeQLForCmd(logger2, cmd, checkVersion, env = getEnv()) { } return result; }, + async isVersionAtLeast(requiredVersion) { + return semver11.gte((await this.getVersion()).version, requiredVersion); + }, async printVersion() { core12.info(JSON.stringify(await this.getVersion(), null, 2)); }, @@ -185154,7 +185265,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 +185286,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 +185315,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", @@ -185286,11 +185397,11 @@ Output was: ${extractorPath}` await runCli(env, cmd, args); } }; - if (checkVersion && !await codeQlVersionAtLeast(codeql, CODEQL_MINIMUM_VERSION)) { + if (checkVersion && !await codeql.isVersionAtLeast(CODEQL_MINIMUM_VERSION)) { throw new ConfigurationError( `Expected a CodeQL CLI with version at least ${CODEQL_MINIMUM_VERSION} but got version ${(await codeql.getVersion()).version}` ); - } else if (checkVersion && env.getOptional("CODEQL_ACTION_SUPPRESS_DEPRECATED_SOON_WARNING" /* SUPPRESS_DEPRECATED_SOON_WARNING */) !== "true" && !await codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION)) { + } else if (checkVersion && env.getOptional("CODEQL_ACTION_SUPPRESS_DEPRECATED_SOON_WARNING" /* SUPPRESS_DEPRECATED_SOON_WARNING */) !== "true" && !await codeql.isVersionAtLeast(CODEQL_NEXT_MINIMUM_VERSION)) { const result = await codeql.getVersion(); core12.warning( `CodeQL CLI version ${result.version} was discontinued on ${GHES_MOST_RECENT_DEPRECATION_DATE} alongside GitHub Enterprise Server ${GHES_VERSION_MOST_RECENTLY_DEPRECATED} and will not be supported by the next minor release of the CodeQL Action. Please update to CodeQL CLI version ${CODEQL_NEXT_MINIMUM_VERSION} or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version. @@ -186152,7 +186263,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.` @@ -187501,9 +187612,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( @@ -187616,7 +187727,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(); } @@ -188584,7 +188695,7 @@ async function run(action) { let dependencyCacheResults; let databaseUploadResults = []; try { - initializeEnvironment(getActionVersion()); + action.env.initialise(getActionVersion()); persistInputs(); const statusReportBase = await createStatusReportBase( "finish" /* Analyze */, @@ -190344,10 +190455,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]) { @@ -190357,16 +190468,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++; @@ -190910,7 +191021,7 @@ var import_async = __toESM(require_async(), 1); var import_path7 = require("path"); // node_modules/archiver/lib/error.js -var import_util38 = __toESM(require("util"), 1); +var import_util37 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -190935,7 +191046,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util38.default.inherits(ArchiverError, Error); +import_util37.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); @@ -193758,8 +193869,8 @@ async function runWrapper2() { // src/autobuild-action.ts var core17 = __toESM(require_core()); -async function sendCompletedStatusReport(config, logger2, startedAt, allLanguages, failingLanguage, cause) { - initializeEnvironment(getActionVersion()); +async function sendCompletedStatusReport(env, config, logger2, startedAt, allLanguages, failingLanguage, cause) { + env.initialise(getActionVersion()); const status = getActionsStatus(cause, failingLanguage); const statusReportBase = await createStatusReportBase( "autobuild" /* Autobuild */, @@ -193831,6 +193942,7 @@ async function run2({ `We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. ${error3.message}` ); await sendCompletedStatusReport( + env, config, logger2, startedAt, @@ -193841,7 +193953,13 @@ async function run2({ return; } env.export("CODEQL_ACTION_AUTOBUILD_DID_COMPLETE_SUCCESSFULLY" /* AUTOBUILD_DID_COMPLETE_SUCCESSFULLY */, "true"); - await sendCompletedStatusReport(config, logger2, startedAt, languages ?? []); + await sendCompletedStatusReport( + env, + config, + logger2, + startedAt, + languages ?? [] + ); } var autobuild = { name: "autobuild" /* Autobuild */, @@ -193855,8 +193973,8 @@ async function runWrapper3() { var fs31 = __toESM(require("fs")); var path25 = __toESM(require("path")); var core19 = __toESM(require_core()); -var io7 = __toESM(require_io()); -var semver11 = __toESM(require_semver2()); +var io8 = __toESM(require_io()); +var semver12 = __toESM(require_semver2()); // src/config/inputs.ts async function getToolsInput(action, repositoryProperties) { @@ -193910,20 +194028,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: { @@ -194004,28 +194137,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." ); @@ -194409,7 +194541,7 @@ async function run3(actionState) { let toolsSource; let toolsVersion; try { - initializeEnvironment(getActionVersion()); + actionState.env.initialise(getActionVersion()); persistInputs(); apiDetails = { auth: getRequiredInput("token"), @@ -194506,7 +194638,7 @@ async function run3(actionState) { ) { const publicPreview = "2.22.1"; const actualVer = (await codeql.getVersion()).version; - if (semver11.lt(actualVer, publicPreview)) { + if (semver12.lt(actualVer, publicPreview)) { actionState.env.export("CODEQL_ENABLE_EXPERIMENTAL_FEATURES" /* EXPERIMENTAL_FEATURES */, "true"); logger2.info("Experimental Rust analysis enabled"); } @@ -194643,7 +194775,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 */ @@ -194740,7 +194872,7 @@ exec ${goBinaryPath} "$@"` logger2.debug( `${"CODEQL_EXTRACTOR_JAVA_OPTION_MINIMIZE_DEPENDENCY_JARS" /* JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS */} is already set to '${process.env["CODEQL_EXTRACTOR_JAVA_OPTION_MINIMIZE_DEPENDENCY_JARS" /* JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS */]}', so the Action will not override it.` ); - } else if (await codeQlVersionAtLeast(codeql, CODEQL_VERSION_JAR_MINIMIZATION) && config.dependencyCachingEnabled && config.buildMode === "none" /* None */ && config.languages.includes("java" /* java */)) { + } else if (await codeql.isVersionAtLeast(CODEQL_VERSION_JAR_MINIMIZATION) && config.dependencyCachingEnabled && config.buildMode === "none" /* None */ && config.languages.includes("java" /* java */)) { actionState.env.export( "CODEQL_EXTRACTOR_JAVA_OPTION_MINIMIZE_DEPENDENCY_JARS" /* JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS */, "true" @@ -195477,7 +195609,7 @@ async function run6(actionState) { let toolsSource; let toolsVersion; try { - initializeEnvironment(getActionVersion()); + actionState.env.initialise(getActionVersion()); const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), @@ -195998,8 +196130,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)) { @@ -196089,9 +196221,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"], { @@ -196429,9 +196561,10 @@ async function sendSuccessStatusReport2(startedAt, uploadStats, logger2) { await sendStatusReport(statusReport); } } -async function run8({ startedAt, logger: logger2 }) { +async function run8(action) { + const logger2 = action.logger; try { - initializeEnvironment(getActionVersion()); + action.env.initialise(getActionVersion()); const gitHubVersion = await getGitHubVersion(); checkActionVersion(getActionVersion(), gitHubVersion); persistInputs(); @@ -196446,7 +196579,7 @@ async function run8({ startedAt, logger: logger2 }) { const startingStatusReportBase = await createStatusReportBase( "upload-sarif" /* UploadSarif */, "starting", - startedAt, + action.startedAt, void 0, await checkDiskUsage(logger2), logger2 @@ -196491,7 +196624,7 @@ async function run8({ startedAt, logger: logger2 }) { } } await sendSuccessStatusReport2( - startedAt, + action.startedAt, codeScanningResult?.statusReport || {}, logger2 ); @@ -196502,7 +196635,7 @@ async function run8({ startedAt, logger: logger2 }) { const errorStatusReportBase = await createStatusReportBase( "upload-sarif" /* UploadSarif */, getActionsStatus(error3), - startedAt, + action.startedAt, void 0, await checkDiskUsage(logger2), logger2, diff --git a/src/actions-util.test.ts b/src/actions-util.test.ts index 175fdea96..1c10b06bf 100644 --- a/src/actions-util.test.ts +++ b/src/actions-util.test.ts @@ -11,8 +11,8 @@ import { } from "./actions-util"; import { computeAutomationID } from "./api-client"; import { EnvVar } from "./environment"; -import { RecordingLogger, setupTests } from "./testing-utils"; -import { initializeEnvironment, withTmpDir } from "./util"; +import { getTestEnv, RecordingLogger, setupTests } from "./testing-utils"; +import { withTmpDir } from "./util"; setupTests(test); @@ -227,8 +227,9 @@ test.serial( ); test.serial("initializeEnvironment", (t) => { - initializeEnvironment("1.2.3"); - t.deepEqual(process.env[EnvVar.VERSION], "1.2.3"); + const env = getTestEnv(); + env.initialise("1.2.3"); + t.deepEqual(env.getOptional(EnvVar.VERSION), "1.2.3"); }); test.serial("fixCodeQualityCategory", (t) => { diff --git a/src/analyze-action.ts b/src/analyze-action.ts index ff7deb489..3c038e62d 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -230,7 +230,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) { let databaseUploadResults: DatabaseUploadResult[] = []; try { - util.initializeEnvironment(actionsUtil.getActionVersion()); + action.env.initialise(actionsUtil.getActionVersion()); // Make inputs accessible in the `post` step, details at // https://github.com/github/codeql-action/issues/2553 diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 2bb878f3b..34ebd52e9 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -7,7 +7,7 @@ import { ProxyAgent } from "undici"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; -import { ActionsEnvVars, RegistryProxyVars } from "./environment"; +import { ActionsEnvVars, RegistryProxyVars, getEnv } from "./environment"; import { GITHUB_DOTCOM_API_URL, GITHUB_DOTCOM_URL } from "./github"; import { callee, @@ -21,7 +21,7 @@ import * as util from "./util"; setupTests(test); test.beforeEach(() => { - util.initializeEnvironment(actionsUtil.getActionVersion()); + getEnv().initialise(actionsUtil.getActionVersion()); }); test.serial("getApiClient", async (t) => { diff --git a/src/autobuild-action.ts b/src/autobuild-action.ts index 18aad1185..3e2eb1a3f 100644 --- a/src/autobuild-action.ts +++ b/src/autobuild-action.ts @@ -10,7 +10,7 @@ import { getGitHubVersion } from "./api-client"; import { determineAutobuildLanguages, runAutobuild } from "./autobuild"; import { getCodeQL } from "./codeql"; import { Config, getConfig } from "./config-utils"; -import { EnvVar } from "./environment"; +import { EnvVar, type Env } from "./environment"; import { Language } from "./languages"; import { Logger } from "./logging"; import { @@ -26,7 +26,6 @@ import { checkDiskUsage, checkGitHubVersionInRange, ConfigurationError, - initializeEnvironment, wrapError, } from "./util"; @@ -38,6 +37,7 @@ interface AutobuildStatusReport extends StatusReportBase { } async function sendCompletedStatusReport( + env: Env, config: Config | undefined, logger: Logger, startedAt: Date, @@ -45,7 +45,7 @@ async function sendCompletedStatusReport( failingLanguage?: string, cause?: Error, ) { - initializeEnvironment(getActionVersion()); + env.initialise(getActionVersion()); const status = getActionsStatus(cause, failingLanguage); const statusReportBase = await createStatusReportBase( @@ -129,6 +129,7 @@ async function run({ `We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. ${error.message}`, ); await sendCompletedStatusReport( + env, config, logger, startedAt, @@ -141,7 +142,13 @@ async function run({ env.export(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true"); - await sendCompletedStatusReport(config, logger, startedAt, languages ?? []); + await sendCompletedStatusReport( + env, + config, + logger, + startedAt, + languages ?? [], + ); } /** Defines the `autobuild` Action. */ diff --git a/src/codeql.test.ts b/src/codeql.test.ts index d702fe438..d39b9a60d 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -21,6 +21,7 @@ import { import type { Config } from "./config-utils"; import * as defaults from "./defaults.json"; import { DocUrl } from "./doc-url"; +import { getEnv } from "./environment"; import { BuiltInLanguage } from "./languages"; import { ToolsSource } from "./setup-codeql"; import { @@ -38,14 +39,13 @@ import { } from "./testing-utils"; import { ToolsDownloadStatusReport } from "./tools-download"; import * as util from "./util"; -import { initializeEnvironment } from "./util"; setupTests(test); let stubConfig: Config; test.beforeEach(() => { - initializeEnvironment("1.2.3"); + getEnv().initialise("1.2.3"); stubConfig = createTestConfig({ languages: [BuiltInLanguage.cpp], diff --git a/src/codeql.ts b/src/codeql.ts index ca93cedf1..c142c537a 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -4,6 +4,7 @@ import * as path from "path"; import * as core from "@actions/core"; import * as toolrunner from "@actions/exec/lib/toolrunner"; import * as yaml from "js-yaml"; +import * as semver from "semver"; import { CommandInvocationError, @@ -19,6 +20,7 @@ import type { ActionConfig } from "./config/action-config"; import { appendExtraQueryExclusions, type Config } from "./config-utils"; import { DocUrl } from "./doc-url"; import { EnvVar, getEnv, Env, ReadOnlyEnv } from "./environment"; +import { getErrorMessage, isDiskConfigurationError } from "./errors"; import { CodeQLDefaultVersionInfo, Feature, @@ -34,7 +36,9 @@ import { ToolsDownloadStatusReport } from "./tools-download"; import { ToolsFeature, isSupportedToolsFeature } from "./tools-features"; import { shouldEnableIndirectTracing } from "./tracer-config"; import * as util from "./util"; -import { BuildMode, CleanupLevel, getErrorMessage } from "./util"; +import { BuildMode, CleanupLevel } from "./util"; + +export { isDiskConfigurationError } from "./errors"; type Options = Array; @@ -70,6 +74,11 @@ export interface CodeQL { * Get a string containing the semver version of the CodeQL executable. */ getVersion(): Promise; + /** + * Determines whether the CLI installation represented by this {@link CodeQL} instance + * has at least the {@link requiredVersion}. + */ + isVersionAtLeast(requiredVersion: string): Promise; /** * Print version information about CodeQL. */ @@ -275,26 +284,6 @@ const GHES_MOST_RECENT_DEPRECATION_DATE = "2026-10-16"; /** The CLI verbosity level to use for extraction in debug mode. */ const EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++"; -/** - * Decides whether `e` is a disk-related error outside of our control - * that should be classified as a `ConfigurationError`. - * - * @param e The error to check. - * @returns True if the error should be treated as a `ConfigurationError` or false if not. - */ -export function isDiskConfigurationError(e: unknown): boolean { - if (!(e instanceof Error)) { - return false; - } - - return ( - // out of disk space - e.message.includes("ENOSPC") || - // access denied - e.message.includes("EACCES") - ); -} - /** * Set up CodeQL CLI access. * @@ -403,9 +392,9 @@ export function setCodeQL(codeql: Partial): void { cachedCodeQL = createStubCodeQL(codeql); } -function resolveFunction( +function resolveFunction( partialCodeql: Partial, - methodName: string, + methodName: K, defaultImplementation?: T, ): T { if (typeof partialCodeql[methodName] !== "function") { @@ -415,7 +404,7 @@ function resolveFunction( const dummyMethod = () => { throw new Error(`CodeQL ${methodName} method not correctly defined`); }; - return dummyMethod as T; + return dummyMethod as unknown as T; } return partialCodeql[methodName] as T; } @@ -432,6 +421,19 @@ export function createStubCodeQL(partialCodeql: Partial): CodeQL { getVersion: resolveFunction(partialCodeql, "getVersion", async () => ({ version: "1.0.0", })), + isVersionAtLeast: resolveFunction( + partialCodeql, + "isVersionAtLeast", + async (requiredVersion) => { + return ( + !!partialCodeql.getVersion && + semver.gte( + (await partialCodeql.getVersion()).version, + requiredVersion, + ) + ); + }, + ), printVersion: resolveFunction(partialCodeql, "printVersion"), supportsFeature: resolveFunction( partialCodeql, @@ -525,6 +527,9 @@ export async function getCodeQLForCmd( } return result; }, + async isVersionAtLeast(requiredVersion: string) { + return semver.gte((await this.getVersion()).version, requiredVersion); + }, async printVersion() { // Reuse the cached version information rather than invoking the CLI again. core.info(JSON.stringify(await this.getVersion(), null, 2)); @@ -1062,7 +1067,7 @@ export async function getCodeQLForCmd( // CodeQL object is created. if ( checkVersion && - !(await util.codeQlVersionAtLeast(codeql, CODEQL_MINIMUM_VERSION)) + !(await codeql.isVersionAtLeast(CODEQL_MINIMUM_VERSION)) ) { throw new util.ConfigurationError( `Expected a CodeQL CLI with version at least ${CODEQL_MINIMUM_VERSION} but got version ${ @@ -1072,7 +1077,7 @@ export async function getCodeQLForCmd( } else if ( checkVersion && env.getOptional(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING) !== "true" && - !(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION)) + !(await codeql.isVersionAtLeast(CODEQL_NEXT_MINIMUM_VERSION)) ) { const result = await codeql.getVersion(); core.warning( diff --git a/src/config-utils.ts b/src/config-utils.ts index 12c1024f8..b59149e47 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -82,7 +82,6 @@ import { GitHubVersion, ConfigurationError, BuildMode, - codeQlVersionAtLeast, cloneObject, isDefined, checkDiskUsage, @@ -654,10 +653,7 @@ async function runnerHasSufficientMemory( logger: Logger, ): Promise { if ( - await codeQlVersionAtLeast( - codeql, - CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE, - ) + await codeql.isVersionAtLeast(CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE) ) { logger.debug( `Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.`, @@ -901,7 +897,7 @@ async function validateOverlayDatabaseMode( ); return new Failure(OverlayDisabledReason.IncompatibleBuildMode); } - if (!(await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION))) { + if (!(await codeql.isVersionAtLeast(CODEQL_OVERLAY_MINIMUM_VERSION))) { logger.warning( `Cannot build an ${overlayDatabaseMode} database because ` + `the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. ` + diff --git a/src/database-upload.test.ts b/src/database-upload.test.ts index 908994112..7d7145dd3 100644 --- a/src/database-upload.test.ts +++ b/src/database-upload.test.ts @@ -15,6 +15,7 @@ import { cleanupAndUploadDatabases, uploadBundledDatabase, } from "./database-upload"; +import { getEnv } from "./environment"; import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; import { BuiltInLanguage } from "./languages"; @@ -31,18 +32,12 @@ import { setupActionsVars, setupTests, } from "./testing-utils"; -import { - CleanupLevel, - GitHubVariant, - HTTPError, - initializeEnvironment, - withTmpDir, -} from "./util"; +import { CleanupLevel, GitHubVariant, HTTPError, withTmpDir } from "./util"; setupTests(test); test.beforeEach(() => { - initializeEnvironment("1.2.3"); + getEnv().initialise("1.2.3"); }); const testRepoName: RepositoryNwo = { owner: "github", repo: "example" }; diff --git a/src/defaults.json b/src/defaults.json index cde5136e2..cfa3f6c1c 100644 --- a/src/defaults.json +++ b/src/defaults.json @@ -1,6 +1,6 @@ { - "bundleVersion": "codeql-bundle-v2.27.3", - "cliVersion": "2.27.3", - "priorBundleVersion": "codeql-bundle-v2.27.2", - "priorCliVersion": "2.27.2" + "bundleVersion": "codeql-bundle-v2.27.4", + "cliVersion": "2.27.4", + "priorBundleVersion": "codeql-bundle-v2.27.3", + "priorCliVersion": "2.27.3" } diff --git a/src/environment.ts b/src/environment.ts index c6c07f3e6..f3d38075e 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -394,6 +394,18 @@ export class Env extends ReadOnlyEnv { } } + /** + * Set some initial environment variables that we can set even without + * knowing what version of CodeQL we're running. + */ + public initialise(version: string) { + this.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false"); + this.export(EnvVar.FEATURE_SANDWICH, "false"); + this.export(EnvVar.FEATURE_SARIF_COMBINE, "true"); + this.export(EnvVar.FEATURE_WILL_UPLOAD, "true"); + this.export(EnvVar.VERSION, version); + } + /** Gets a value indicating whether `set` was called at least once. */ public hasChanged(): boolean { return this.changed; diff --git a/src/errors/index.ts b/src/errors/index.ts new file mode 100644 index 000000000..c2b947d3f --- /dev/null +++ b/src/errors/index.ts @@ -0,0 +1,88 @@ +import * as json from "../json"; + +/** + * If {@link error} is not already an instance of {@link Error}, then this function constructs + * an {@link Error} with a string representation of {@link error} as the message. + */ +export function wrapError(error: unknown): Error { + return error instanceof Error ? error : new Error(String(error)); +} + +/** + * Returns an appropriate message for the error. + * + * If the error is an {@link Error} instance, this returns the error message without + * an `Error: ` prefix. + */ +export function getErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +/** + * Approximates whether {@link error} is like a {@link NodeJS.ErrnoException}. + * We only typically care about the `code` property, so that's what we check for here. + */ +export function isErrnoException( + error: unknown, +): error is NodeJS.ErrnoException { + return ( + typeof error === "object" && + error !== null && + "code" in error && + typeof error.code === "string" + ); +} + +/** + * Decides whether `e` is a disk-related error outside of our control + * that should be classified as a `ConfigurationError`. + * + * @param e The error to check. + * @returns True if the error should be treated as a `ConfigurationError` or false if not. + */ +export function isDiskConfigurationError(e: unknown): boolean { + if (!(e instanceof Error)) { + return false; + } + + return ( + // out of disk space + e.message.includes("ENOSPC") || + // access denied + e.message.includes("EACCES") + ); +} + +/** + * An Error class that indicates an error that occurred due to + * a misconfiguration of the action or the CodeQL CLI. + */ +export class ConfigurationError extends Error {} + +/** Minimal representation of errors resulting from HTTP requests. */ +export class HTTPError extends Error { + public status: number; + + constructor(message: string, status: number) { + super(message); + this.status = status; + } +} + +/** + * Tries to convert {@link arg} to a {@link HTTPError}, but returns `undefined` if + * {@link arg} is not like a {@link HTTPError}. + */ +export function asHTTPError(arg: any): HTTPError | undefined { + if (!json.isObject(arg) || !json.isString(arg.message)) { + return undefined; + } + if (Number.isInteger(arg.status)) { + return new HTTPError(arg.message, arg.status as number); + } + // See https://github.com/actions/toolkit/blob/acb230b99a46ed33a3f04a758cd68b47b9a82908/packages/tool-cache/src/tool-cache.ts#L19 + if (Number.isInteger(arg.httpStatusCode)) { + return new HTTPError(arg.message, arg.httpStatusCode as number); + } + return undefined; +} diff --git a/src/feature-flags.test.ts b/src/feature-flags.test.ts index d8b5eea04..7b1c1ef3e 100644 --- a/src/feature-flags.test.ts +++ b/src/feature-flags.test.ts @@ -4,6 +4,7 @@ import * as path from "path"; import test from "ava"; import * as defaults from "./defaults.json"; +import { getEnv } from "./environment"; import { Feature, featureConfig, @@ -26,12 +27,12 @@ import { setupTests, stubFeatureFlagApiEndpoint, } from "./testing-utils"; -import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util"; +import { GitHubVariant, withTmpDir } from "./util"; setupTests(test); test.beforeEach(() => { - initializeEnvironment("1.2.3"); + getEnv().initialise("1.2.3"); }); test.serial( diff --git a/src/feature-flags.ts b/src/feature-flags.ts index a321b941f..20b7a0263 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -560,7 +560,7 @@ class OfflineFeatures implements FeatureEnablement { // Never use this feature if the CLI version explicitly can't support it. const minimumVersion = config.minimumVersion; if (codeql && minimumVersion) { - if (!(await util.codeQlVersionAtLeast(codeql, minimumVersion))) { + if (!(await codeql.isVersionAtLeast(minimumVersion))) { this.logger.debug( `Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum ` + `version ${minimumVersion}.`, diff --git a/src/feature-flags/offline-features.test.ts b/src/feature-flags/offline-features.test.ts index 2870ef3b8..1c3d2a8c1 100644 --- a/src/feature-flags/offline-features.test.ts +++ b/src/feature-flags/offline-features.test.ts @@ -8,7 +8,7 @@ import { LoggedMessage, setupTests, } from "../testing-utils"; -import { GitHubVariant, initializeEnvironment, withTmpDir } from "../util"; +import { getEnv, GitHubVariant, withTmpDir } from "../util"; import { assertAllFeaturesHaveDefaultValues, @@ -18,7 +18,7 @@ import { setupTests(test); test.beforeEach(() => { - initializeEnvironment("1.2.3"); + getEnv().initialise("1.2.3"); }); test("OfflineFeatures makes no API requests", async (t) => { diff --git a/src/github.ts b/src/github.ts index 6350fb3f3..e39cddfb0 100644 --- a/src/github.ts +++ b/src/github.ts @@ -1,5 +1,19 @@ import { ActionsEnvVars, type ReadOnlyEnv } from "./environment"; -import { ConfigurationError } from "./util"; +import { ConfigurationError } from "./errors"; + +export enum GitHubVariant { + /** [GitHub.com](https://github.com) */ + DOTCOM = "GitHub.com", + /** [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) */ + GHES = "GitHub Enterprise Server", + /** [GitHub Enterprise Cloud with data residency](https://docs.github.com/en/enterprise-cloud@latest/admin/data-residency/about-github-enterprise-cloud-with-data-residency) */ + GHEC_DR = "GitHub Enterprise Cloud with data residency", +} + +export type GitHubVersion = + | { type: GitHubVariant.DOTCOM } + | { type: GitHubVariant.GHEC_DR } + | { type: GitHubVariant.GHES; version: string }; /** A string representing a GitHub instance address. */ export type InstanceURL = string; diff --git a/src/init-action.ts b/src/init-action.ts index af95e8ffa..0998c2e71 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -89,12 +89,10 @@ import { checkDiskUsage, checkForTimeout, checkGitHubVersionInRange, - codeQlVersionAtLeast, DEFAULT_DEBUG_ARTIFACT_NAME, DEFAULT_DEBUG_DATABASE_NAME, getCodeQLMemoryLimit, getThreadsFlagValue, - initializeEnvironment, ConfigurationError, wrapError, checkActionVersion, @@ -227,7 +225,7 @@ async function run( let toolsVersion: string; try { - initializeEnvironment(getActionVersion()); + actionState.env.initialise(getActionVersion()); // Make inputs accessible in the `post` step. persistInputs(); @@ -685,7 +683,7 @@ async function run( `${EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS} is already set to '${process.env[EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS]}', so the Action will not override it.`, ); } else if ( - (await codeQlVersionAtLeast(codeql, CODEQL_VERSION_JAR_MINIMIZATION)) && + (await codeql.isVersionAtLeast(CODEQL_VERSION_JAR_MINIMIZATION)) && config.dependencyCachingEnabled && config.buildMode === BuildMode.None && config.languages.includes(BuiltInLanguage.java) diff --git a/src/languages/traced.test.ts b/src/languages/traced.test.ts index 8de9512a5..631a647ae 100644 --- a/src/languages/traced.test.ts +++ b/src/languages/traced.test.ts @@ -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>().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,103 @@ 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>().callsFake(async () => { + if (stdoutListener !== undefined) { + stdoutListener(Buffer.from("27.5")); + 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, + ]); + }), +); + // `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", diff --git a/src/languages/traced.ts b/src/languages/traced.ts index 1e6442dd6..406afc9b3 100644 --- a/src/languages/traced.ts +++ b/src/languages/traced.ts @@ -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.", ); diff --git a/src/platform.test.ts b/src/platform.test.ts index 58c41408e..e69614202 100644 --- a/src/platform.test.ts +++ b/src/platform.test.ts @@ -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>().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>().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>().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>().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); + }); +}); diff --git a/src/platform.ts b/src/platform.ts index e5c0e6ac6..283a789c0 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -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 { + 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 { + 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; + } +} diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index 757b26fac..1bbfdacec 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -38,7 +38,6 @@ import { checkDiskUsage, checkForTimeout, checkGitHubVersionInRange, - initializeEnvironment, ConfigurationError, wrapError, checkActionVersion, @@ -108,7 +107,7 @@ async function run( let toolsVersion: string; try { - initializeEnvironment(getActionVersion()); + actionState.env.initialise(getActionVersion()); const apiDetails = { auth: getRequiredInput("token"), diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index b67146a79..a244501c3 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -11,7 +11,13 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import * as diagnostics from "./diagnostics"; -import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment"; +import { + ActionsEnvVars, + Env, + EnvVar, + getEnv, + ReadOnlyEnv, +} from "./environment"; import { Feature } from "./feature-flags"; import { BuiltInLanguage } from "./languages"; import { getCacheRestoreKeyPrefix } from "./overlay/caching"; @@ -37,18 +43,12 @@ import { setupTests, } from "./testing-utils"; import * as toolsDownload from "./tools-download"; -import { - getErrorMessage, - GitHubVariant, - HTTPError, - initializeEnvironment, - withTmpDir, -} from "./util"; +import { getErrorMessage, GitHubVariant, HTTPError, withTmpDir } from "./util"; setupTests(test); test.beforeEach(() => { - initializeEnvironment("1.2.3"); + getEnv().initialise("1.2.3"); }); function stubDownloadAndExtract() { diff --git a/src/setup/repository.test.ts b/src/setup/repository.test.ts index d1c6d034c..9fabd8488 100644 --- a/src/setup/repository.test.ts +++ b/src/setup/repository.test.ts @@ -6,7 +6,6 @@ import * as sinon from "sinon"; import * as actionsUtil from "../actions-util"; import { ActionsEnvVars } from "../environment"; import { callee, getTestEnv, setupTests } from "../testing-utils"; -import { initializeEnvironment } from "../util"; import { getCodeQLActionRepository } from "./repository"; @@ -16,7 +15,7 @@ test.serial("getCodeQLActionRepository", async (t) => { const initialEnv = getTestEnv({ [ActionsEnvVars.RUNNER_TEMP]: path.dirname(__dirname), }); - initializeEnvironment("1.2.3", initialEnv); + initialEnv.initialise("1.2.3"); const target = callee(getCodeQLActionRepository) .withArgs() diff --git a/src/setup/repository.ts b/src/setup/repository.ts index 341a27875..ff02a1560 100644 --- a/src/setup/repository.ts +++ b/src/setup/repository.ts @@ -2,13 +2,12 @@ import { ActionState } from "../action-common"; import { isRunningLocalAction } from "../actions-util"; import * as api from "../api-client"; import { ActionsEnvVars } from "../environment"; -import { GITHUB_DOTCOM_URL } from "../github"; +import { GITHUB_DOTCOM_URL, type InstanceURL } from "../github"; import { isRepositoryNwoStr, parseRepositoryNwo, type RepositoryNwoStr, } from "../repository"; -import { type InstanceURL } from "../util"; /** The NWO of the standard CodeQL Action repository. */ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action"; diff --git a/src/tools-download.test.ts b/src/tools-download.test.ts index 928255d3c..9ba5bfd16 100644 --- a/src/tools-download.test.ts +++ b/src/tools-download.test.ts @@ -10,6 +10,7 @@ import { https } from "follow-redirects"; import nock from "nock"; import * as sinon from "sinon"; +import * as errors from "./errors"; import * as tar from "./tar"; import { RecordingLogger, setupTests } from "./testing-utils"; import { downloadAndExtract } from "./tools-download"; @@ -163,7 +164,7 @@ test.serial( test.serial( "downloadAndExtract handles an unknown status as a non-HTTP error", async (t) => { - const asHTTPError = sinon.spy(util, "asHTTPError"); + const asHTTPError = sinon.spy(errors, "asHTTPError"); await util.withTmpDir(async (tmpDir) => { sinon.stub(process, "platform").value("linux"); const archivePath = path.join(tmpDir, "codeql-bundle.tar.zst"); diff --git a/src/upload-lib.test.ts b/src/upload-lib.test.ts index ebfc5cd40..4738c07c6 100644 --- a/src/upload-lib.test.ts +++ b/src/upload-lib.test.ts @@ -10,17 +10,18 @@ import * as analyses from "./analyses"; import { AnalysisKind, CodeQuality, CodeScanning } from "./analyses"; import * as api from "./api-client"; import * as diffUtils from "./diff-informed-analysis-utils"; +import { getEnv } from "./environment"; import { Logger } from "./logging"; import * as sarif from "./sarif"; import { RecordingLogger, setupTests } from "./testing-utils"; import * as uploadLib from "./upload-lib"; import { UploadPayload } from "./upload-lib/types"; -import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util"; +import { GitHubVariant, withTmpDir } from "./util"; setupTests(test); test.beforeEach(() => { - initializeEnvironment("1.2.3"); + getEnv().initialise("1.2.3"); }); test.serial("validateSarifFileSchema - valid", (t) => { diff --git a/src/upload-sarif-action.ts b/src/upload-sarif-action.ts index 2ac5cd48e..033663fb7 100644 --- a/src/upload-sarif-action.ts +++ b/src/upload-sarif-action.ts @@ -24,7 +24,6 @@ import { ConfigurationError, checkActionVersion, checkDiskUsage, - initializeEnvironment, shouldSkipSarifUpload, wrapError, } from "./util"; @@ -54,11 +53,13 @@ async function sendSuccessStatusReport( } } -async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { +async function run(action: ActionState<["Base", "Env", "Logger"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. + const logger = action.logger; + try { - initializeEnvironment(getActionVersion()); + action.env.initialise(getActionVersion()); const gitHubVersion = await getGitHubVersion(); checkActionVersion(getActionVersion(), gitHubVersion); @@ -78,7 +79,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { const startingStatusReportBase = await createStatusReportBase( ActionName.UploadSarif, "starting", - startedAt, + action.startedAt, undefined, await checkDiskUsage(logger), logger, @@ -136,7 +137,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { // so we can't wait for that here. } await sendSuccessStatusReport( - startedAt, + action.startedAt, codeScanningResult?.statusReport || {}, logger, ); @@ -152,7 +153,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { const errorStatusReportBase = await createStatusReportBase( ActionName.UploadSarif, getActionsStatus(error), - startedAt, + action.startedAt, undefined, await checkDiskUsage(logger), logger, diff --git a/src/util.ts b/src/util.ts index b68824d6e..533f40ed7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -20,20 +20,22 @@ import { getRequiredEnvParam, exportEnvVar, getEnv, - type Env, type ReadOnlyEnv, } from "./environment"; -import * as json from "./json"; -import { Language } from "./languages"; -import { Logger } from "./logging"; +import { ConfigurationError, getErrorMessage, wrapError } from "./errors"; +import { GitHubVariant, type GitHubVersion } from "./github"; +import type { Language } from "./languages"; +import type { Logger } from "./logging"; // Re-export for backwards compatibility to avoid updating a lot of imports elsewhere. +export * from "./errors"; export { getRequiredEnvParam, getOptionalEnvVar, getEnv, isInTestMode, } from "./environment"; +export { GitHubVariant, type GitHubVersion } from "./github"; /** * The name of the file containing the base database OIDs, as stored in the @@ -47,9 +49,6 @@ const BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json"; */ const BROKEN_VERSIONS = ["0.0.0-20211207"]; -/** A string representing a GitHub instance. */ -export type InstanceURL = string; - /** * Default name of the debugging artifact. */ @@ -470,20 +469,6 @@ const CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR = let hasBeenWarnedAboutVersion = false; -export enum GitHubVariant { - /** [GitHub.com](https://github.com) */ - DOTCOM = "GitHub.com", - /** [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) */ - GHES = "GitHub Enterprise Server", - /** [GitHub Enterprise Cloud with data residency](https://docs.github.com/en/enterprise-cloud@latest/admin/data-residency/about-github-enterprise-cloud-with-data-residency) */ - GHEC_DR = "GitHub Enterprise Cloud with data residency", -} - -export type GitHubVersion = - | { type: GitHubVariant.DOTCOM } - | { type: GitHubVariant.GHEC_DR } - | { type: GitHubVariant.GHES; version: string }; - export function checkGitHubVersionInRange( version: GitHubVersion, logger: Logger, @@ -552,54 +537,6 @@ export function assertNever(value: never): never { throw new ExhaustivityCheckingError(value); } -/** - * Set some initial environment variables that we can set even without - * knowing what version of CodeQL we're running. - */ -export function initializeEnvironment(version: string, env: Env = getEnv()) { - env.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false"); - env.export(EnvVar.FEATURE_SANDWICH, "false"); - env.export(EnvVar.FEATURE_SARIF_COMBINE, "true"); - env.export(EnvVar.FEATURE_WILL_UPLOAD, "true"); - env.export(EnvVar.VERSION, version); -} - -export class HTTPError extends Error { - public status: number; - - constructor(message: string, status: number) { - super(message); - this.status = status; - } -} - -/** - * An Error class that indicates an error that occurred due to - * a misconfiguration of the action or the CodeQL CLI. - */ -export class ConfigurationError extends Error {} - -export function asHTTPError(arg: any): HTTPError | undefined { - if (!json.isObject(arg) || !json.isString(arg.message)) { - return undefined; - } - if (Number.isInteger(arg.status)) { - return new HTTPError(arg.message, arg.status as number); - } - // See https://github.com/actions/toolkit/blob/acb230b99a46ed33a3f04a758cd68b47b9a82908/packages/tool-cache/src/tool-cache.ts#L19 - if (Number.isInteger(arg.httpStatusCode)) { - return new HTTPError(arg.message, arg.httpStatusCode as number); - } - return undefined; -} - -export async function codeQlVersionAtLeast( - codeql: CodeQL, - requiredVersion: string, -): Promise { - return semver.gte((await codeql.getVersion()).version, requiredVersion); -} - export function getBaseDatabaseOidsFilePath( config: ActionConfig<"dbLocation">, ): string { @@ -886,35 +823,6 @@ export function parseMatrixInput( } } -export function wrapError(error: unknown): Error { - return error instanceof Error ? error : new Error(String(error)); -} - -/** - * Returns an appropriate message for the error. - * - * If the error is an `Error` instance, this returns the error message without - * an `Error: ` prefix. - */ -export function getErrorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); -} - -/** - * Approximates whether {@link error} is like a {@link NodeJS.ErrnoException}. - * We only typically care about the `code` property, so that's what we check for here. - */ -export function isErrnoException( - error: unknown, -): error is NodeJS.ErrnoException { - return ( - typeof error === "object" && - error !== null && - "code" in error && - typeof error.code === "string" - ); -} - export function prettyPrintPack(pack: Pack) { return `${pack.name}${pack.version ? `@${pack.version}` : ""}${ pack.path ? `:${pack.path}` : ""