Merge pull request #4221 from github/mbg/macos27/warn

Check for versions of macOS and Xcode that are unsupported for traced analysis
This commit is contained in:
Michael B. Gale
2026-10-30 13:18:49 +00:00
committed by GitHub
19 changed files with 2083 additions and 551 deletions

135
.github/workflows/__unsupported-macos.yml generated vendored Normal file
View File

@@ -0,0 +1,135 @@
# Warning: This file is generated automatically, and should not be modified.
# Instead, please modify the template in the pr-checks directory and run:
# pr-checks/sync.sh
# to regenerate this file.
name: PR Check - Unsupported macOS
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GO111MODULE: auto
on:
push:
branches:
- main
- releases/v*
pull_request: {}
merge_group:
types:
- checks_requested
schedule:
- cron: '0 5 * * *'
workflow_dispatch:
inputs: {}
workflow_call:
inputs: {}
defaults:
run:
shell: bash
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: unsupported-macos-${{github.ref}}
jobs:
unsupported-macos:
strategy:
fail-fast: false
matrix:
include:
- os: xcode-27
version: linked
- os: xcode-27
version: nightly-latest
name: Unsupported macOS
if: github.triggering_actor != 'dependabot[bot]'
permissions:
contents: read
security-events: read
timeout-minutes: 45
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
- name: Install newer npm
run: npm install -g npm@11.19.1
- name: Install dependencies
run: npm ci
- name: Prepare test
id: prepare-test
uses: ./.github/actions/prepare-test
with:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Check xcode_select_link
run: |
readlink /private/var/db/xcode_select_link
file -h /private/var/db/xcode_select_link
- uses: ./../action/init
id: init
continue-on-error: true
env:
CODEQL_ACTION_CHECK_UNSUPPORTED_TRACED_ANALYSIS: true
CODEQL_ACTION_ABORT_UNSUPPORTED_TRACED_ANALYSIS: true
with:
languages: swift
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Analyze
# We could run `analyze` here from `./../analyze`, but `getAnalyzeActionName` in `workflow.ts`
# can't cope with that unless we introduce a different testing environment. Since this step
# would just fail on `xcode-27` anyway, we just skip it instead.
if: false
uses: ./analyze
with:
category: '/test-codeql-version:${{ matrix.version }}'
env:
CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF: false
CODEQL_ACTION_SKIP_SARIF_UPLOAD: true
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
CODEQL_ACTION_TEST_MODE: true
unsupported-macos-sarif-checks:
if: github.triggering_actor != 'dependabot[bot]'
needs:
- unsupported-macos
permissions:
contents: read
security-events: read
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
- name: Install newer npm
run: npm install -g npm@11.19.1
- name: Install dependencies
run: npm ci
- name: Download workflow artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: test-mode-sarif-*
path: sarif
- name: List artifact contents
run: ls -laR ./sarif
- name: Check diagnostics appear in SARIF
env:
SARIF_PATH: "./sarif/"
DIAGNOSTIC_ID: "codeql-action/unsupported-traced-analysis-macos"
DIAGNOSTIC_NAME: "Traced analysis is not supported on this version of macOS"
EXPECTED_LOCATIONS: "0"
EXPECTED_MESSAGE: |
Traced analysis is not supported on macOS 27 or above.
run: npx tsx ./pr-checks/check-diagnostics.ts
env:
CODEQL_ACTION_TEST_MODE: true

1205
lib/entry-points.js generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
name: "Unsupported macOS"
description: "Tests that unsupported macOS versions are detected and result in appropriate diagnostics."
versions:
- linked
- nightly-latest
installNode: true
operatingSystems:
- os: macos
runner-image: xcode-27
env:
# Allow the failed SARIF file to be generated.
CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF: false
# But don't actually upload it.
CODEQL_ACTION_SKIP_SARIF_UPLOAD: true
# Mark telemetry for this workflow so it can be treated separately.
CODEQL_ACTION_TESTING_ENVIRONMENT: codeql-action-pr-checks
steps:
- name: Check xcode_select_link
run: |
readlink /private/var/db/xcode_select_link
file -h /private/var/db/xcode_select_link
- uses: ./../action/init
id: init
continue-on-error: true
env:
CODEQL_ACTION_CHECK_UNSUPPORTED_TRACED_ANALYSIS: true
CODEQL_ACTION_ABORT_UNSUPPORTED_TRACED_ANALYSIS: true
with:
languages: swift
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Analyze
# We could run `analyze` here from `./../analyze`, but `getAnalyzeActionName` in `workflow.ts`
# can't cope with that unless we introduce a different testing environment. Since this step
# would just fail on `xcode-27` anyway, we just skip it instead.
if: false
uses: ./analyze
with:
category: "/test-codeql-version:${{ matrix.version }}"
validationJobs:
unsupported-macos-sarif-checks:
checkout: true
installNode: true
steps:
- name: Download workflow artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: test-mode-sarif-*
path: sarif
- name: List artifact contents
run: ls -laR ./sarif
- name: Check diagnostics appear in SARIF
env:
SARIF_PATH: "./sarif/"
DIAGNOSTIC_ID: "codeql-action/unsupported-traced-analysis-macos"
DIAGNOSTIC_NAME: "Traced analysis is not supported on this version of macOS"
EXPECTED_LOCATIONS: "0"
EXPECTED_MESSAGE: |
Traced analysis is not supported on macOS 27 or above.
run: npx tsx ./pr-checks/check-diagnostics.ts

View File

@@ -666,9 +666,11 @@ function generateValidationJob(
// Add the generated steps in front of the ones from the specification.
specSteps.items.unshift(...checkoutStep, ...steps);
const baseCondition = "github.triggering_actor != 'dependabot[bot]'";
const validationJob: Record<string, any> = {
name: jobSpecification.name,
if: "github.triggering_actor != 'dependabot[bot]'",
if: baseCondition,
needs: [checkName],
permissions: {
contents: "read",

View File

@@ -1,3 +1,5 @@
import * as os from "os";
import * as core from "@actions/core";
import { ActionsEnv, getActionsEnv } from "./actions-util";
@@ -13,6 +15,8 @@ import {
} from "./status-report";
import { getEnv, getErrorMessage, wrapError } from "./util";
export type { Logger } from "./logging";
/** Base state that is available to an Action on startup. */
export interface BaseState {
/** The name of the Action. */
@@ -23,6 +27,8 @@ export interface BaseState {
platform: NodeJS.Platform;
/** The architecture of the host. */
arch: NodeJS.Architecture;
/** The version of the operating system. */
osRelease: string;
}
/** Describes different state features that an Action may have. */
@@ -173,6 +179,7 @@ export async function runInActions(action: Action) {
startedAt,
platform: process.platform,
arch: process.arch,
osRelease: os.release(),
logger,
env,
actions: actionsEnv,

View File

@@ -364,7 +364,7 @@ const MAX_STDERR_BUFFER_SIZE = 20000;
* @throws A {@link CommandInvocationError} if the tool exits with a non-zero status code.
*/
export async function runTool(
env: Env,
env: ReadOnlyEnv,
cmd: string,
args: string[] = [],
opts: { stdin?: string; noStreamStdout?: boolean } = {},

View File

@@ -1166,7 +1166,7 @@ export function getExtraOptions(
}
async function runCli(
env: Env,
env: ReadOnlyEnv,
cmd: string,
args: string[] = [],
opts: { stdin?: string; noStreamStdout?: boolean } = {},
@@ -1193,7 +1193,7 @@ async function runCli(
* @returns The parsed JSON output from the command.
*/
async function runCliJson<T>(
env: Env,
env: ReadOnlyEnv,
cmd: string,
args: string[] = [],
opts: { stdin?: string; noStreamStdout?: boolean } = {},

View File

@@ -12,4 +12,6 @@ export enum DocUrl {
SPECIFY_BUILD_STEPS_MANUALLY = "https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages#about-specifying-build-steps-manually",
SYSTEM_REQUIREMENTS = "https://codeql.github.com/docs/codeql-overview/system-requirements/",
TRACK_CODE_SCANNING_ALERTS_ACROSS_RUNS = "https://docs.github.com/en/code-security/reference/code-scanning/sarif-support-for-code-scanning#data-for-preventing-duplicated-alerts",
/** Blog post about traced analysis being unsupported on macOS 27 and with Xcode 27. */
UNSUPPORTED_TRACED_ANALYSIS_MACOS27 = "https://github.blog/changelog/2026-10-19-codeql-changes-for-macos-27-xcode-27-and-swift-6-4/",
}

View File

@@ -74,6 +74,11 @@ export enum Feature {
AllowMergeConfigFiles = "allow_merge_config_files",
/** Controls whether we allow multiple values for the `analysis-kinds` input. */
AllowMultipleAnalysisKinds = "allow_multiple_analysis_kinds",
/**
* Whether to check for unsupported traced analysis. Primarily intended as an override to disable
* the check if it causes problems somehow.
*/
CheckUnsupportedTracedAnalysis = "check_unsupported_traced_analysis",
/** Whether to allow the `config-file` input to be specified via a repository property. */
ConfigFileRepositoryProperty = "config_file_repository_property",
CppDependencyInstallation = "cpp_dependency_installation_enabled",
@@ -144,6 +149,8 @@ export enum Feature {
/** Controls whether overlay build failures on the default branch are stored in the Actions cache. */
OverlayAnalysisStatusSave = "overlay_analysis_status_save",
QaTelemetryEnabled = "qa_telemetry_enabled",
/** Whether we should fail early if we detect that traced analysis is unsupported. */
AbortUnsupportedTracedAnalysis = "abort_unsupported_traced_analysis",
/** Routes (some) API requests through the registry proxy. */
ProxyApiRequests = "proxy_api_requests",
/** Note that this currently only disables baseline file coverage information. */
@@ -195,6 +202,11 @@ export const featureConfig = {
envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS",
minimumVersion: undefined,
},
[Feature.CheckUnsupportedTracedAnalysis]: {
defaultValue: false,
envVar: "CODEQL_ACTION_CHECK_UNSUPPORTED_TRACED_ANALYSIS",
minimumVersion: undefined,
},
[Feature.ConfigFileRepositoryProperty]: {
defaultValue: false,
envVar: "CODEQL_ACTION_CONFIG_FILE_REPOSITORY_PROPERTY",
@@ -410,6 +422,11 @@ export const featureConfig = {
envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE",
minimumVersion: undefined,
},
[Feature.AbortUnsupportedTracedAnalysis]: {
defaultValue: false,
envVar: "CODEQL_ACTION_ABORT_UNSUPPORTED_TRACED_ANALYSIS",
minimumVersion: undefined,
},
[Feature.ToolsRepositoryProperty]: {
defaultValue: false,
envVar: "CODEQL_ACTION_TOOLS_REPOSITORY_PROPERTY",

View File

@@ -62,7 +62,7 @@ import {
runDatabaseInitCluster,
} from "./init";
import { JavaEnvVars, BuiltInLanguage } from "./languages";
import { checkSwiftCompatibility } from "./languages/swift";
import { checkRunnerSupportsTracedAnalysis } from "./languages/traced";
import { Logger, withGroupAsync } from "./logging";
import {
downloadOverlayBaseDatabaseFromCache,
@@ -419,7 +419,11 @@ async function run(
logger,
});
checkSwiftCompatibility(actionState, config);
await checkRunnerSupportsTracedAnalysis(
actionStateWithFeatures,
config,
codeql,
);
if (repositoryPropertiesResult.isFailure()) {
addNoLanguageDiagnostic(

View File

@@ -6,31 +6,66 @@ import {
createTestConfig,
getCodeQLForTesting,
getTestEnv,
initAllState,
makeVersionInfo,
RecordingLogger,
setupTests,
} from "../testing-utils";
import { ConfigurationError, withTmpDir } from "../util";
import { ToolsFeature } from "../tools-features";
import { withTmpDir } from "../util";
import { checkSwiftCompatibility } from "./swift";
import { BuiltInLanguage } from ".";
test("checkSwiftCompatibility doesn't throw for non-Swift languages", (t) => {
setupTests(test);
test("checkSwiftCompatibility doesn't throw for non-Swift languages", async (t) => {
for (const language of Object.values(BuiltInLanguage)) {
if (language === BuiltInLanguage.swift) {
continue;
}
t.notThrows(() =>
const codeql = await getCodeQLForTesting();
await t.notThrowsAsync(
checkSwiftCompatibility(
initAllState(),
createTestConfig({ languages: [language] }),
codeql,
// A value of `undefined` should make `checkSwiftCompatibility` throw for Swift by default
// since it means we are on a platform other than macOS
undefined,
),
);
}
});
test("checkSwiftCompatibility doesn't throw for Swift if CLI supports swiftSupportsAllPlatforms", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const codeql = await getCodeQLForTesting("codeql-for-testing", logger, env);
const supportsFeature = sinon
.stub(codeql, "supportsFeature")
.withArgs(ToolsFeature.SwiftSupportsAllPlatforms)
.resolves(true);
await t.notThrowsAsync(
checkSwiftCompatibility(
createTestConfig({ languages: [BuiltInLanguage.swift] }),
codeql,
// A value of `undefined` should make `checkSwiftCompatibility` throw for Swift by default
// since it means we are on a platform other than macOS
undefined,
),
);
t.is(supportsFeature.callCount, 1);
t.deepEqual(supportsFeature.args[0], [
ToolsFeature.SwiftSupportsAllPlatforms,
]);
}));
test("checkSwiftCompatibility doesn't throw for Swift on darwin", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
@@ -40,31 +75,15 @@ test("checkSwiftCompatibility doesn't throw for Swift on darwin", async (t) =>
const codeql = await getCodeQLForTesting("codeql-for-testing", logger, env);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
t.notThrows(() =>
await t.notThrowsAsync(
checkSwiftCompatibility(
initAllState({ platform: "darwin", env, logger }),
createTestConfig({
tempDir: tmpDir,
languages: [BuiltInLanguage.swift],
}),
codeql,
// A value other than `undefined` means we are on macOS.
"some-macos-version",
),
);
}));
const nonDarwinPlatforms: NodeJS.Platform[] = ["linux", "win32"];
for (const nonDarwinPlatform of nonDarwinPlatforms) {
test(`checkSwiftCompatibility throws for Swift on ${nonDarwinPlatform}`, (t) => {
t.throws(
() => {
checkSwiftCompatibility(
initAllState({ platform: nonDarwinPlatform }),
createTestConfig({ languages: [BuiltInLanguage.swift] }),
);
},
{
instanceOf: ConfigurationError,
message: /^Swift analysis is only supported on macOS runner images/,
},
);
});
}

View File

@@ -1,5 +1,8 @@
import { ActionState } from "../action-common";
import { Config } from "../config-utils";
import type { SemVer } from "semver";
import { CodeQL } from "../codeql";
import type { ActionConfig } from "../config/action-config";
import { ToolsFeature } from "../tools-features";
import { ConfigurationError } from "../util";
import { BuiltInLanguage } from ".";
@@ -7,19 +10,31 @@ import { BuiltInLanguage } from ".";
/**
* Determines whether we can run a Swift analysis on the current runner.
*
* @param action The Action state.
* @param config The Action configuration.
* @param codeql The {@link CodeQL} CLI instance.
* @param darwinVersion The version of the Darwin kernel on macOS, if any.
*
* @throws {ConfigurationError} If Swift analysis is not possible on the current runner.
*/
export function checkSwiftCompatibility(
action: ActionState<["Base"]>,
config: Config,
export async function checkSwiftCompatibility(
config: ActionConfig<"languages">,
codeql: CodeQL,
darwinVersion: SemVer | string | undefined,
) {
if (
config.languages.includes(BuiltInLanguage.swift) &&
action.platform !== "darwin"
) {
// The checks are not relevant if we are not trying to analyse Swift.
if (!config.languages.includes(BuiltInLanguage.swift)) {
return;
}
// Skip the checks if the `swiftSupportsAllPlatforms` feature is supported by the CLI.
// This is a forward-looking measure that allows a future CLI update to disable these
// platform checks in the Action when they shouldn't be enforced anymore.
if (await codeql.supportsFeature(ToolsFeature.SwiftSupportsAllPlatforms)) {
return;
}
// If `darwinVersion` is undefined, then we are not on macOS and Swift analysis is unsupported.
if (darwinVersion === undefined) {
throw new ConfigurationError(
`Swift analysis is only supported on macOS runner images. Please migrate to a macOS runner.`,
);

View File

@@ -0,0 +1,569 @@
import fs from "fs";
import test from "ava";
import * as sinon from "sinon";
import { type CodeQL } from "../codeql";
import * as diagnostics from "../diagnostics";
import { ActionsEnvVars, type Env } from "../environment";
import { Feature } from "../feature-flags";
import type { Logger } from "../logging";
import { XCODE_SELECT_LINK_PATH } from "../platform";
import {
checkExpectedLogMessages,
checkUnexpectedLogMessages,
createFeatures,
createTestConfig,
getCodeQLForTesting,
getTestEnv,
initAllState,
makeVersionInfo,
RecordingLogger,
setupTests,
} from "../testing-utils";
import { BuildMode, ConfigurationError, withTmpDir } from "../util";
import { checkRunnerSupportsTracedAnalysis } from "./traced";
import { BuiltInLanguage } from ".";
setupTests(test);
const TEST_DEFAULT_TRACED_LANGUAGES = {
[BuiltInLanguage.swift]: true,
[BuiltInLanguage.go]: true,
};
async function getCodeQLWithTracedStub(
cmd: string,
logger: Logger,
env: Env,
tracedLanguages: Partial<
Record<BuiltInLanguage, boolean>
> = TEST_DEFAULT_TRACED_LANGUAGES,
): Promise<CodeQL> {
const codeql = await getCodeQLForTesting(cmd, logger, env);
sinon.stub(codeql, "isTracedLanguage").callsFake(async (language) => {
return tracedLanguages[language] === true;
});
return codeql;
}
const nonDarwinPlatforms: NodeJS.Platform[] = ["linux", "win32"];
for (const nonDarwinPlatform of nonDarwinPlatforms) {
test(`checkRunnerSupportsTracedAnalysis throws for Swift on ${nonDarwinPlatform}`, async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const codeql = await getCodeQLWithTracedStub(
"codeql-for-testing",
logger,
env,
);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
await t.throwsAsync(
checkRunnerSupportsTracedAnalysis(
initAllState({ platform: nonDarwinPlatform, env, logger }),
createTestConfig({
tempDir: tmpDir,
languages: [BuiltInLanguage.swift],
}),
codeql,
),
{
instanceOf: ConfigurationError,
message: /^Swift analysis is only supported on macOS runner images/,
},
);
}));
}
test(`checkRunnerSupportsTracedAnalysis doesn't throw for BMN`, async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const features = createFeatures([Feature.CheckUnsupportedTracedAnalysis]);
const codeql = await getCodeQLWithTracedStub(
"codeql-for-testing",
logger,
env,
);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
await t.notThrowsAsync(
checkRunnerSupportsTracedAnalysis(
initAllState({
platform: "darwin",
osRelease: "27.0.0",
env,
logger,
features,
}),
createTestConfig({ tempDir: tmpDir, buildMode: BuildMode.None }),
codeql,
),
);
checkExpectedLogMessages(t, logger.messages, [
"Skipping traced analysis compatibility checks because the build mode is 'none'.",
]);
}));
// Some languages not in `TEST_DEFAULT_TRACED_LANGUAGES`.
const nonTracedLanguages: BuiltInLanguage[] = [
BuiltInLanguage.javascript,
BuiltInLanguage.actions,
];
for (const nonTracedLanguage of nonTracedLanguages) {
test(`checkRunnerSupportsTracedAnalysis doesn't throw for ${nonTracedLanguage} on unsupported macOS`, async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const features = createFeatures([Feature.CheckUnsupportedTracedAnalysis]);
const codeql = await getCodeQLWithTracedStub(
"codeql-for-testing",
logger,
env,
);
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
await t.notThrowsAsync(
checkRunnerSupportsTracedAnalysis(
initAllState({
platform: "darwin",
osRelease: "27.0.0",
env,
logger,
features,
}),
createTestConfig({ tempDir: tmpDir, languages: [nonTracedLanguage] }),
codeql,
),
);
checkExpectedLogMessages(t, logger.messages, [
"Skipping traced analysis compatibility checks because no traced languages are being analyzed.",
]);
}));
}
test("checkRunnerSupportsTracedAnalysis doesn't perform checks if FF is not enabled", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const features = createFeatures([]);
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: "unexpected",
env,
features,
}),
createTestConfig({ tempDir: tmpDir, languages: [BuiltInLanguage.go] }),
codeql,
);
checkExpectedLogMessages(t, logger.messages, [
"Skipping traced analysis compatibility checks because the feature is not enabled.",
]);
}));
test("checkRunnerSupportsTracedAnalysis warns if version string is not a semver", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
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: "unexpected",
env,
features,
}),
createTestConfig({ tempDir: tmpDir, languages: [BuiltInLanguage.go] }),
codeql,
);
checkExpectedLogMessages(t, logger.messages, [
"Unable to parse Darwin kernel version, got: unexpected",
]);
}));
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis logs and adds diagnostic if macOS version is unsupported",
async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const features = createFeatures([Feature.CheckUnsupportedTracedAnalysis]);
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
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: "27.0.0",
env,
features,
}),
config,
codeql,
);
checkExpectedLogMessages(t, logger.messages, [
"Traced analysis is not supported on macOS 27",
]);
t.is(addDiagnostic.callCount, 1);
t.like(addDiagnostic.args[0], [
config,
BuiltInLanguage.swift,
{
attributes: {
languages: [BuiltInLanguage.swift],
darwinVersion: "27.0.0",
},
severity: "warning",
source: {
id: "codeql-action/unsupported-traced-analysis-macos",
name: "Traced analysis is not supported on this version of macOS",
},
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
} satisfies Partial<diagnostics.DiagnosticMessage>,
]);
}),
);
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis throws if macOS version is unsupported and FF is enabled",
async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const features = createFeatures([
Feature.CheckUnsupportedTracedAnalysis,
Feature.AbortUnsupportedTracedAnalysis,
]);
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 t.throwsAsync(
checkRunnerSupportsTracedAnalysis(
initAllState({
logger,
platform: "darwin",
osRelease: "27.0.0",
env,
features,
}),
config,
codeql,
),
);
t.is(addDiagnostic.callCount, 1);
t.like(addDiagnostic.args[0], [
config,
BuiltInLanguage.swift,
{
attributes: {
languages: [BuiltInLanguage.swift],
darwinVersion: "27.0.0",
},
severity: "error",
source: {
id: "codeql-action/unsupported-traced-analysis-macos",
name: "Traced analysis is not supported on this version of macOS",
},
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
} satisfies Partial<diagnostics.DiagnosticMessage>,
]);
}),
);
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",
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")
.returns("/Applications/Xcode_26.0.app/Contents/Developer");
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]);
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",
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")
.returns("/Applications/Xcode_27.0.app/Contents/Developer");
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]);
checkExpectedLogMessages(t, logger.messages, [
"Traced analysis is not supported on Xcode 27",
]);
t.is(addDiagnostic.callCount, 1);
t.like(addDiagnostic.args[0], [
config,
BuiltInLanguage.swift,
{
attributes: {
languages: [BuiltInLanguage.swift],
xcodeVersion: "27.0.0",
},
severity: "warning",
source: {
id: "codeql-action/unsupported-traced-analysis-xcode",
name: "Traced analysis is not supported on this version of Xcode",
},
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
} satisfies Partial<diagnostics.DiagnosticMessage>,
]);
}),
);
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
test.serial(
"checkRunnerSupportsTracedAnalysis throws if Xcode version is unsupported and FF is enabled",
async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
const env = getTestEnv();
env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir);
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_27.0.app/Contents/Developer");
const features = createFeatures([
Feature.CheckUnsupportedTracedAnalysis,
Feature.AbortUnsupportedTracedAnalysis,
]);
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 t.throwsAsync(
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(addDiagnostic.callCount, 1);
t.like(addDiagnostic.args[0], [
config,
BuiltInLanguage.swift,
{
attributes: {
languages: [BuiltInLanguage.swift],
xcodeVersion: "27.0.0",
},
severity: "error",
source: {
id: "codeql-action/unsupported-traced-analysis-xcode",
name: "Traced analysis is not supported on this version of Xcode",
},
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
} satisfies Partial<diagnostics.DiagnosticMessage>,
]);
}),
);

219
src/languages/traced.ts Normal file
View File

@@ -0,0 +1,219 @@
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 { ConfigurationError } from "../util";
import { checkSwiftCompatibility } from "./swift";
/** macOS 27 and above do not support traced extraction. */
export const UNSUPPORTED_DARWIN_FOR_TRACED = 27;
/** Xcode 27 and above do not support traced extraction. */
export const UNSUPPORTED_XCODE_FOR_TRACED = 27;
/**
* Creates a diagnostic indicating that `version` of `product` is unsupported for traced analysis.
* Depending on `skipUnsupportedTracedAnalysis`, this function then either throws a {@link ConfigurationError}
* or logs the problem as a warning.
*
* @param logger The logger to use.
* @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.
*/
function handleUnsupportedVersion(
logger: Logger,
config: ActionConfig<"dbLocation" | "tempDir">,
tracedLanguages: string[],
abortUnsupportedTracedAnalysis: boolean,
product: "macOS" | "Xcode",
version: semver.SemVer,
) {
const unsupportedVersion =
product === "macOS"
? UNSUPPORTED_DARWIN_FOR_TRACED
: 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";
// Create a diagnostic that will show up on the TSP.
for (const language of tracedLanguages) {
addDiagnostic(
config,
language,
makeDiagnostic(
`codeql-action/unsupported-traced-analysis-${product.toLowerCase()}`,
`Traced analysis is not supported on this version of ${product}`,
{
attributes: {
languages: config.languages,
[attributeName]: version.toString(),
},
markdownMessage: [
baseMessage,
`For more information, see [this announcement](${DocUrl.UNSUPPORTED_TRACED_ANALYSIS_MACOS27}).`,
].join(" "),
severity: abortUnsupportedTracedAnalysis ? "error" : "warning",
visibility: {
cliSummaryTable: true,
statusPage: true,
telemetry: true,
},
},
),
);
}
// Log messages generally don't render as markdown, so we include the URL verbatim there.
const logMessage = [
baseMessage,
`For more information, see ${DocUrl.UNSUPPORTED_TRACED_ANALYSIS_MACOS27}`,
].join(" ");
// Throw an error to abort the analysis if the FF is enabled or log the message.
if (abortUnsupportedTracedAnalysis) {
throw new ConfigurationError(logMessage);
} else {
// This will also show up as a workflow annotation.
logger.warning(logMessage);
}
}
/**
* Returns traced languages from {@link config} by looking for tracer
* configurations in the {@link codeql} CLI installation.
*/
export async function hasTracedLanguages(
config: ActionConfig<"languages">,
codeql: CodeQL,
): Promise<string[]> {
const tracedLanguages: string[] = [];
for (const language of config.languages) {
if (await codeql.isTracedLanguage(language)) {
tracedLanguages.push(language);
}
}
return tracedLanguages;
}
/**
* Determines whether we can run a traced analysis on the current runner.
*
* @throws {ConfigurationError} If traced analysis is not possible on the current runner.
*/
export async function checkRunnerSupportsTracedAnalysis(
action: ActionState<["Base", "Env", "Logger", "FeatureFlags"]>,
config: ActionConfig<"languages" | "dbLocation" | "tempDir">,
codeql: CodeQL,
) {
// Try to get the Darwin version on macOS.
const version = darwinVersion(action);
// Perform some initial, Swift-specific checks.
await checkSwiftCompatibility(config, codeql, version);
// Skip the checks for unsupported traced analysis if the FF is not enabled.
if (
!(await action.features.getValue(Feature.CheckUnsupportedTracedAnalysis))
) {
action.logger.debug(
"Skipping traced analysis compatibility checks because the feature is not enabled.",
);
return;
}
// `build-mode: none` doesn't use the tracer, so it is always supported.
if (config.buildMode !== undefined && config.buildMode === "none") {
action.logger.debug(
"Skipping traced analysis compatibility checks because the build mode is 'none'.",
);
return;
}
// These checks don't apply if we are not on macOS.
if (version === undefined) {
action.logger.debug(
"Skipping traced analysis compatibility checks because we are not on macOS.",
);
return;
}
// If none of the languages are traced, then we can skip the compatibility checks below as well.
const tracedLanguages = await hasTracedLanguages(config, codeql);
if (tracedLanguages.length === 0) {
action.logger.debug(
"Skipping traced analysis compatibility checks because no traced languages are being analyzed.",
);
return;
}
const abortUnsupportedTracedAnalysis = await action.features.getValue(
Feature.AbortUnsupportedTracedAnalysis,
);
if (typeof version === "string") {
// If we got a string, we are on macOS but couldn't parse the version string.
action.logger.warning(
`Unable to parse Darwin kernel version, got: ${version}. Skipping macOS compatibility check.`,
);
} else if (version.major >= UNSUPPORTED_DARWIN_FOR_TRACED) {
handleUnsupportedVersion(
action.logger,
config,
tracedLanguages,
abortUnsupportedTracedAnalysis,
"macOS",
version,
);
// Return early so that we don't end up producing more-or-less the same log message/diagnostic
// twice. The ones for macOS already comment on the Xcode requirement.
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.
const xcodeVer = xcodeVersion(action.logger);
if (
xcodeVer !== undefined &&
xcodeVer.major >= UNSUPPORTED_XCODE_FOR_TRACED
) {
handleUnsupportedVersion(
action.logger,
config,
tracedLanguages,
abortUnsupportedTracedAnalysis,
"Xcode",
xcodeVer,
);
} else if (xcodeVer === undefined) {
action.logger.info(
"Unable to determine Xcode version. Skipping Xcode compatibility check.",
);
}
}

View File

@@ -1,6 +1,23 @@
import test from "ava";
import fs from "fs";
import { BundlePlatform, getBundlePlatform } from "./platform";
import test from "ava";
import * as sinon from "sinon";
import {
BundlePlatform,
getBundlePlatform,
darwinVersion,
XCODE_SELECT_LINK_PATH,
xcodeVersion,
} from "./platform";
import {
checkExpectedLogMessages,
initAllState,
RecordingLogger,
setupTests,
} from "./testing-utils";
setupTests(test);
for (const [platform, arch, expected] of [
["linux", "x64", BundlePlatform.Linux64],
@@ -16,3 +33,181 @@ for (const [platform, arch, expected] of [
t.is(getBundlePlatform(platform, arch), expected);
});
}
const platforms: NodeJS.Platform[] = ["linux", "win32", "freebsd"];
for (const platform of platforms) {
test(`darwinVersion returns undefined on ${platform}`, (t) => {
t.is(darwinVersion(initAllState({ platform })), undefined);
});
}
test("darwinVersion returns raw string if semver parsing fails", (t) => {
const invalidSemVer = "sealOS-2026";
t.is(
darwinVersion(
initAllState({ platform: "darwin", osRelease: invalidSemVer }),
),
invalidSemVer,
);
});
test("darwinVersion returns semver if parsing succeeds", (t) => {
const validSemVer = "27.0.1";
const version = darwinVersion(
initAllState({ platform: "darwin", osRelease: validSemVer }),
);
// Check that `version` is not undefined and narrow the type; throws if undefined.
if (t.truthy(version)) {
// Check that it's also not just a string.
t.not(typeof version, "string");
// Should be an object with the expected properties.
t.is(typeof version, "object");
t.is(version["major"], 27);
t.is(version["minor"], 0);
t.is(version["patch"], 1);
}
});
test.serial("xcodeVersion 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"));
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: 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",
]);
});
test.serial(
"xcodeVersion 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(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
checkExpectedLogMessages(t, logger.messages, [
"unexpectedly returned nothing",
]);
},
);
test.serial(
"xcodeVersion returns undefined if resolved path doesn't include pattern",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode.app/Contents/Developer");
t.is(xcodeVersion(logger), undefined);
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
checkExpectedLogMessages(t, logger.messages, [
"does not contain expected pattern",
]);
},
);
test.serial("xcodeVersion returns undefined if match can't be parsed", (t) => {
const logger = new RecordingLogger();
const 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]);
checkExpectedLogMessages(t, logger.messages, [
"Couldn't parse '00.0' as a semantic version.",
]);
});
test.serial("xcodeVersion returns version from resolved path", (t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_16.4.app/Contents/Developer");
const result = xcodeVersion(logger);
if (t.truthy(result)) {
t.is(result.major, 16);
t.is(result.minor, 4);
}
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
});
test.serial("xcodeVersion 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 result = xcodeVersion(logger);
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]);
});
test.serial(
"xcodeVersion allows more version components in resolved path",
(t) => {
const logger = new RecordingLogger();
const readlinkSync = sinon
.stub(fs, "readlinkSync")
.returns("/Applications/Xcode_16.5.9.app/Contents/Developer");
const result = xcodeVersion(logger);
if (t.truthy(result)) {
t.is(result.major, 16);
t.is(result.minor, 5);
}
t.is(readlinkSync.callCount, 1);
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
},
);

View File

@@ -1,3 +1,10 @@
import fs from "fs";
import * as semver from "semver";
import type { ActionState, Logger } from "./action-common";
import { getErrorMessage } from "./util";
/** Platform identifiers used in CodeQL bundle asset names. */
export enum BundlePlatform {
Linux64 = "linux64",
@@ -24,3 +31,81 @@ export function getBundlePlatform(
return undefined;
}
}
/**
* Tries to determine the version of the Darwin kernel. We use this to approximate the macOS version based
* on the Darwin kernel version so that we don't have to launch an external process. Note that the Darwin
* kernel major version does not correspond perfectly to the macOS major version.
*
* @returns
* The version as either a semantic version object, the raw version string
* if it is not a semantic version, or `undefined` if we are not on macOS.
*/
export function darwinVersion(
action: ActionState<["Base"]>,
): semver.SemVer | string | undefined {
// Skip if we are not running on macOS.
if (action.platform !== "darwin") {
return undefined;
}
// Try to parse the kernel version string.
const version = semver.parse(action.osRelease);
if (version === null) {
return action.osRelease;
}
return version;
}
/** The static path we check for a symbolic link to the (dynamic) Xcode location. */
export const XCODE_SELECT_LINK_PATH = "/private/var/db/xcode_select_link";
/** The pattern we expect to find in the Xcode path. */
export const XCODE_APP_FILENAME_PATTERN = new RegExp(
/(?<filename>Xcode_(?<majorMinor>\d+(\.\d+)?).*\.app)/,
);
/**
* Tries to determine the version of Xcode that is installed.
*
* @param logger The logger to use.
* @returns The Xcode version or `undefined` if it couldn't be determined.
*/
export function xcodeVersion(logger: Logger): semver.SemVer | undefined {
try {
// 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`).
const xcodePath = fs.readlinkSync(XCODE_SELECT_LINK_PATH);
if (xcodePath.trim() === "") {
logger.debug(
`Resolving ${XCODE_SELECT_LINK_PATH} unexpectedly returned nothing.`,
);
return undefined;
}
// Try to extract the version from the path.
const matchResult = xcodePath.match(XCODE_APP_FILENAME_PATTERN);
if (matchResult?.groups === undefined) {
logger.debug(
`Xcode path '${xcodePath}' does not contain expected pattern.`,
);
return undefined;
}
const majorMinor = matchResult.groups["majorMinor"];
const version = semver.coerce(majorMinor);
if (version === null) {
logger.debug(`Couldn't parse '${majorMinor}' as a semantic version.`);
return undefined;
}
return version;
} catch (err) {
logger.debug(`Unable to determine Xcode version: ${getErrorMessage(err)}`);
return undefined;
}
}

View File

@@ -1,4 +1,5 @@
import { TextDecoder } from "node:util";
import * as os from "os";
import path from "path";
import * as github from "@actions/github";
@@ -34,6 +35,7 @@ import { joinMessageStrings, Loggable, LoggableError, Logger } from "./logging";
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
import { getBundlePlatform } from "./platform";
import { ActionName } from "./status-report";
import { ToolsFeature } from "./tools-features";
import {
DEFAULT_DEBUG_ARTIFACT_NAME,
DEFAULT_DEBUG_DATABASE_NAME,
@@ -272,6 +274,7 @@ export function initAllState(
startedAt: new Date(),
platform: process.platform,
arch: process.arch,
osRelease: os.release(),
logger: new RecordingLogger(),
env,
actions: getTestActionsEnv(env),
@@ -911,7 +914,7 @@ export function mockLanguagesInRepo(languages: string[]) {
*/
export const makeVersionInfo = (
version: string,
features?: { [name: string]: boolean },
features?: { [key in ToolsFeature]?: boolean },
overlayVersion?: number,
): VersionInfo => ({
version,

View File

@@ -6,6 +6,7 @@ export enum ToolsFeature {
BundleSupportsOverlay = "bundleSupportsOverlay",
IndirectTracingSupportsStaticBinaries = "indirectTracingSupportsStaticBinaries",
SuppressesMissingFileBaselineWarning = "suppressesMissingFileBaselineWarning",
SwiftSupportsAllPlatforms = "swiftSupportsAllPlatforms",
}
/**

View File

@@ -0,0 +1 @@
- Starting with macOS 27 and Xcode 27, traced analyses are unsupported. See [this blog post for more information](https://github.blog/changelog/2026-10-19-codeql-changes-for-macos-27-xcode-27-and-swift-6-4/). The CodeQL Action will now try to identify if a workflow is about to perform a traced analysis on version 27 or above of macOS or Xcode. If so, the CodeQL Action will emit a corresponding warning that will also be visible on the [Tool Status Page](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/use-the-tools-status-page-for-code-scanning). This check will be rolled out following this release of the CodeQL Action. [#4221](https://github.com/github/codeql-action/pull/4221)