mirror of
https://github.com/github/codeql-action.git
synced 2026-10-09 20:25:26 +00:00
Compare commits
14 Commits
default-se
...
mbg/macos2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6de93bcd7 | ||
|
|
bf94034f3c | ||
|
|
436656b463 | ||
|
|
130da64af0 | ||
|
|
6745303b3d | ||
|
|
d09e9c0eb9 | ||
|
|
ff832f1444 | ||
|
|
047473c122 | ||
|
|
08bb1f303d | ||
|
|
b904de6d18 | ||
|
|
807d4743d0 | ||
|
|
788a22022b | ||
|
|
ed72893659 | ||
|
|
acf4e5b3ac |
@@ -1,4 +1,3 @@
|
||||
import pkg from "./package.json" with { type: "json" };
|
||||
|
||||
globalThis.__CODEQL_ACTION_VERSION__ = pkg.version;
|
||||
globalThis.__CODEQL_ACTION_TEST_ENV__ = "unit-test";
|
||||
|
||||
@@ -78,7 +78,7 @@ const UPLOAD_LIB_SRC = "./src/upload-lib";
|
||||
*
|
||||
* The virtual module additionally re-exports `upload-lib` under the `uploadLib` namespace so that
|
||||
* external consumers can access it via the small `lib/upload-lib.js` stub emitted below.
|
||||
*
|
||||
*
|
||||
* A tiny stub file is emitted for each Action entrypoint, and one for `upload-lib`. Each stub
|
||||
* imports the shared bundle and calls/re-exports from the respective entry point.
|
||||
*
|
||||
@@ -212,7 +212,6 @@ const context = await esbuild.context({
|
||||
target: ["node20"],
|
||||
define: {
|
||||
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),
|
||||
__CODEQL_ACTION_TEST_ENV__: JSON.stringify(""),
|
||||
},
|
||||
metafile: true,
|
||||
});
|
||||
|
||||
@@ -140,17 +140,6 @@ export default [
|
||||
"no-async-foreach/no-async-foreach": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "off",
|
||||
|
||||
// A basic check that we don't use `exportVariable` from `@actions/core`.
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "MemberExpression[property.name='exportVariable']",
|
||||
message:
|
||||
"Use the `export` method of an `Env` instance or `exportEnvVar` from `environment.ts` instead.",
|
||||
},
|
||||
],
|
||||
|
||||
// This is overly restrictive with unsetting `EnvVar`s
|
||||
"@typescript-eslint/no-dynamic-delete": "off",
|
||||
"@typescript-eslint/no-shadow": "error",
|
||||
@@ -168,15 +157,6 @@ export default [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/environment.ts"],
|
||||
|
||||
// We allow `exportVariable` from `@actions/core` to be used in this file
|
||||
// since it defines the wrapper around it that other modules use.
|
||||
rules: {
|
||||
"no-restricted-syntax": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.ts", "**/*.js"],
|
||||
|
||||
|
||||
2184
lib/entry-points.js
generated
2184
lib/entry-points.js
generated
File diff suppressed because it is too large
Load Diff
10
package-lock.json
generated
10
package-lock.json
generated
@@ -58,7 +58,7 @@
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^65.0.1",
|
||||
"eslint-plugin-jsdoc": "^64.5.4",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.12.0",
|
||||
@@ -5381,15 +5381,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc": {
|
||||
"version": "65.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.0.1.tgz",
|
||||
"integrity": "sha512-7IvB+ZS71WCw5tGKh6f6D5k55qtXKdrGTnIpQTBKWdpyZ5IfhGHfh0i6ArWlCJ4JEtz22YRk5Bqu1MvbOlpMKQ==",
|
||||
"version": "64.5.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.4.tgz",
|
||||
"integrity": "sha512-xfRhXPSSWT612muJ6XTvxuVJ8zYHv99qNgBqPVqF28aSqoy4s4XbSkrWXnYA2hl5ejC02yEpk6fqkj3BmJ6Xbg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@es-joy/jsdoccomment": "~0.98.0",
|
||||
"@es-joy/resolve.exports": "1.2.0",
|
||||
"@typescript-eslint/utils": "^8.70.1",
|
||||
"@typescript-eslint/utils": "^8.70.0",
|
||||
"are-docs-informative": "^0.1.1",
|
||||
"comment-parser": "1.4.9",
|
||||
"debug": "^4.4.3",
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^65.0.1",
|
||||
"eslint-plugin-jsdoc": "^64.5.4",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.12.0",
|
||||
|
||||
@@ -154,6 +154,26 @@ await describe("isValidChangenoteFile", async () => {
|
||||
assert.equal(isValidChangenoteFile("non-existent-file.md"), false);
|
||||
});
|
||||
|
||||
await it("rejects invalid filename", async () => {
|
||||
await withTmpFile(
|
||||
"fix-bug.md",
|
||||
"---\ncategory: fix\n---\n- Fixed a bug\n",
|
||||
(filePath) => {
|
||||
assert.equal(isValidChangenoteFile(filePath), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await it("rejects missing frontmatter", async () => {
|
||||
await withTmpFile(
|
||||
"2026-01-01-fix-bug.md",
|
||||
"- Fixed a bug\n",
|
||||
(filePath) => {
|
||||
assert.equal(isValidChangenoteFile(filePath), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await it("rejects invalid Markdown", async () => {
|
||||
await withTmpFile(
|
||||
"2026-01-01-fix-bug.md",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { matter } from "lite-matter";
|
||||
import type { List, ListItem } from "mdast";
|
||||
@@ -94,7 +95,21 @@ export function isValidChangenoteFile(filename: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { content } = matter(fileData);
|
||||
const { data: frontmatter, content } = matter(fileData);
|
||||
|
||||
if (!isValidChangenoteFilename(path.basename(filename))) {
|
||||
isValid = false;
|
||||
console.error(
|
||||
`${filename}: invalid filename; must match pattern YYYY-MM-DD-id.md`,
|
||||
);
|
||||
}
|
||||
if (!hasValidChangenoteCategory(frontmatter)) {
|
||||
isValid = false;
|
||||
const categories = Object.keys(VALID_CHANGE_NOTE_CATEGORIES).join(", ");
|
||||
console.error(
|
||||
`${filename}: invalid category; must be one of: ${categories}`,
|
||||
);
|
||||
}
|
||||
if (!isValidChangenoteContent(content)) {
|
||||
isValid = false;
|
||||
console.error(
|
||||
|
||||
@@ -23,8 +23,7 @@ predicate isSafeForDefaultSetup(string envVar) {
|
||||
"GITHUB_BASE_REF", "GITHUB_EVENT_NAME", "GITHUB_JOB", "GITHUB_RUN_ATTEMPT", "GITHUB_RUN_ID",
|
||||
"GITHUB_SHA", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKFLOW",
|
||||
"GITHUB_WORKSPACE", "GOFLAGS", "ImageVersion", "JAVA_TOOL_OPTIONS", "RUNNER_ARCH",
|
||||
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE",
|
||||
"NODE_ENV"
|
||||
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { ActionsEnv, getActionsEnv } from "./actions-util";
|
||||
import type { ApiClient } from "./api-client";
|
||||
import { Env, ReadOnlyEnv } from "./environment";
|
||||
import type { FeatureEnablement } from "./feature-flags";
|
||||
import type { FileSystem } from "./fs";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import {
|
||||
ActionName,
|
||||
@@ -13,6 +17,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 +29,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. */
|
||||
@@ -51,6 +59,10 @@ export interface FeatureState {
|
||||
/** Information about enabled feature flags. */
|
||||
features: FeatureEnablement;
|
||||
};
|
||||
FS: {
|
||||
/** The file system operations to use. */
|
||||
fs: FileSystem;
|
||||
};
|
||||
}
|
||||
|
||||
/** Identifies a type of state an Action may have. */
|
||||
@@ -74,7 +86,7 @@ export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
|
||||
* Each Action can then augment the `state` further if additional features are required.
|
||||
*/
|
||||
export type ActionMain = (
|
||||
state: ActionState<["Base", "Logger", "Env", "Actions"]>,
|
||||
state: ActionState<["Base", "FS", "Logger", "Env", "Actions"]>,
|
||||
) => Promise<void>;
|
||||
|
||||
/** A specification for a CodeQL Action step. */
|
||||
@@ -104,6 +116,8 @@ export async function runInActions(action: Action) {
|
||||
startedAt,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
osRelease: os.release(),
|
||||
fs,
|
||||
logger,
|
||||
env,
|
||||
actions: actionsEnv,
|
||||
|
||||
@@ -30,6 +30,7 @@ declare const __CODEQL_ACTION_VERSION__: string;
|
||||
export interface ActionsEnv {
|
||||
getRequiredInput: (name: string) => string;
|
||||
getOptionalInput: (name: string) => string | undefined;
|
||||
exportVariable: (name: string, value: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,7 @@ export function getActionsEnv(): ActionsEnv {
|
||||
return {
|
||||
getRequiredInput,
|
||||
getOptionalInput,
|
||||
exportVariable: core.exportVariable,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ export const getOptionalInput = function (name: string): string | undefined {
|
||||
* directory that has been set in `CODEQL_ACTION_TEMP` by e.g. a previous step, or the
|
||||
* value of `RUNNER_TEMP` otherwise.
|
||||
*/
|
||||
export function getTemporaryDirectory(env: Env = getEnv()): string {
|
||||
export function getTemporaryDirectory(env: ReadOnlyEnv = getEnv()): string {
|
||||
return (
|
||||
env.getOptional(EnvVar.TEMP) ?? env.getRequired(ActionsEnvVars.RUNNER_TEMP)
|
||||
);
|
||||
|
||||
@@ -214,10 +214,9 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
|
||||
|
||||
async function run({
|
||||
startedAt,
|
||||
env,
|
||||
logger,
|
||||
actions,
|
||||
}: ActionState<["Base", "Env", "Logger", "Actions"]>) {
|
||||
}: ActionState<["Base", "Logger", "Actions"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
@@ -288,7 +287,7 @@ async function run({
|
||||
|
||||
const apiDetails = getApiDetails();
|
||||
const outputDir = actionsUtil.getRequiredInput("output");
|
||||
env.export(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
|
||||
core.exportVariable(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
|
||||
const threads = util.getThreadsFlag(
|
||||
actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"],
|
||||
logger,
|
||||
@@ -457,7 +456,7 @@ async function run({
|
||||
`expect-error input was set to true but no error was thrown.`,
|
||||
);
|
||||
}
|
||||
env.export(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
core.exportVariable(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
} catch (unwrappedError) {
|
||||
const error = util.wrapError(unwrappedError);
|
||||
if (
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
ReadOnlyEnv,
|
||||
RegistryProxyVars,
|
||||
getEnv,
|
||||
exportEnvVar,
|
||||
} from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
@@ -317,7 +316,7 @@ export async function getAnalysisKey(): Promise<string> {
|
||||
const jobName = getRequiredEnvParam("GITHUB_JOB");
|
||||
|
||||
analysisKey = `${workflowPath}:${jobName}`;
|
||||
exportEnvVar(EnvVar.ANALYSIS_KEY, analysisKey);
|
||||
core.exportVariable(EnvVar.ANALYSIS_KEY, analysisKey);
|
||||
return analysisKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,11 +68,7 @@ async function sendCompletedStatusReport(
|
||||
}
|
||||
}
|
||||
|
||||
async function run({
|
||||
startedAt,
|
||||
env,
|
||||
logger,
|
||||
}: ActionState<["Base", "Env", "Logger"]>) {
|
||||
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
@@ -139,7 +135,7 @@ async function run({
|
||||
return;
|
||||
}
|
||||
|
||||
env.export(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
core.exportVariable(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
|
||||
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { getTemporaryDirectory, getWorkflowEventName } from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { ActionsEnvVars, EnvVar, exportEnvVar } from "./environment";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import { Feature, featureConfig, initFeatures } from "./feature-flags";
|
||||
import { BuiltInLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
@@ -134,16 +136,16 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
|
||||
: ""
|
||||
}`,
|
||||
);
|
||||
exportEnvVar(envVar, "false");
|
||||
core.exportVariable(envVar, "false");
|
||||
} else {
|
||||
logger.info(
|
||||
`Enabling ${featureName}. This can be disabled by setting the ${envVar} environment variable to 'false'. See ${DocUrl.DEFINE_ENV_VARIABLES} for more information.`,
|
||||
);
|
||||
exportEnvVar(envVar, "true");
|
||||
core.exportVariable(envVar, "true");
|
||||
}
|
||||
} else {
|
||||
logger.info(`Disabling ${featureName}.`);
|
||||
exportEnvVar(envVar, "false");
|
||||
core.exportVariable(envVar, "false");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +165,7 @@ export async function runAutobuild(
|
||||
await codeQL.runAutobuild(config, language);
|
||||
}
|
||||
if (language === BuiltInLanguage.go) {
|
||||
exportEnvVar(EnvVar.DID_AUTOBUILD_GOLANG, "true");
|
||||
core.exportVariable(EnvVar.DID_AUTOBUILD_GOLANG, "true");
|
||||
}
|
||||
logger.endGroup();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { getTemporaryDirectory } from "../actions-util";
|
||||
import { Env } from "../environment";
|
||||
import { ReadOnlyEnv } from "../environment";
|
||||
import * as json from "../json";
|
||||
import { Logger } from "../logging";
|
||||
|
||||
@@ -51,7 +51,7 @@ export function resetCachedCodeQlVersion(): void {
|
||||
* Returns the path to the temporary file that backs the
|
||||
* on-disk cache of CLI responses between workflow steps.
|
||||
*/
|
||||
export function getCommandCacheFilePath(env: Env): string {
|
||||
export function getCommandCacheFilePath(env: ReadOnlyEnv): string {
|
||||
return path.join(getTemporaryDirectory(env), COMMAND_CACHE_FILENAME);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { VersionInfo } from "./cli/types";
|
||||
import { CliError, wrapCliConfigurationError } from "./cli-errors";
|
||||
import { appendExtraQueryExclusions, type Config } from "./config-utils";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { EnvVar, getEnv, exportEnvVar } from "./environment";
|
||||
import { Env, EnvVar, getEnv, ReadOnlyEnv } from "./environment";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
Feature,
|
||||
@@ -493,8 +493,9 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
|
||||
export async function getCodeQLForTesting(
|
||||
cmd = "codeql-for-testing",
|
||||
logger: Logger = getRunnerLogger(true),
|
||||
env: Env = getEnv(),
|
||||
): Promise<CodeQL> {
|
||||
return getCodeQLForCmd(logger, cmd, false);
|
||||
return getCodeQLForCmd(logger, cmd, false, env);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -509,13 +510,14 @@ async function getCodeQLForCmd(
|
||||
logger: Logger,
|
||||
cmd: string,
|
||||
checkVersion: boolean,
|
||||
env: Env = getEnv(),
|
||||
): Promise<CodeQL> {
|
||||
const codeql: CodeQL = {
|
||||
getPath() {
|
||||
return cmd;
|
||||
},
|
||||
async getVersion() {
|
||||
const cacheFilePath = outputCache.getCommandCacheFilePath(getEnv());
|
||||
const cacheFilePath = outputCache.getCommandCacheFilePath(env);
|
||||
let result = outputCache.getCachedCodeQlVersion(
|
||||
logger,
|
||||
cacheFilePath,
|
||||
@@ -641,7 +643,7 @@ async function getCodeQLForCmd(
|
||||
}
|
||||
},
|
||||
async runAutobuild(config: Config, language: Language) {
|
||||
applyAutobuildAzurePipelinesTimeoutFix();
|
||||
applyAutobuildAzurePipelinesTimeoutFix(env);
|
||||
|
||||
const autobuildCmd = path.join(
|
||||
await this.resolveExtractor(language),
|
||||
@@ -651,8 +653,11 @@ async function getCodeQLForCmd(
|
||||
|
||||
// Bump the verbosity of the autobuild command if we're in debug mode
|
||||
if (config.debugMode) {
|
||||
process.env[EnvVar.CLI_VERBOSITY] =
|
||||
process.env[EnvVar.CLI_VERBOSITY] || EXTRACTION_DEBUG_MODE_VERBOSITY;
|
||||
env.set(
|
||||
EnvVar.CLI_VERBOSITY,
|
||||
env.getOptional(EnvVar.CLI_VERBOSITY) ??
|
||||
EXTRACTION_DEBUG_MODE_VERBOSITY,
|
||||
);
|
||||
}
|
||||
|
||||
// On macOS, System Integrity Protection (SIP) typically interferes with
|
||||
@@ -684,7 +689,7 @@ async function getCodeQLForCmd(
|
||||
},
|
||||
async extractUsingBuildMode(config: Config, language: Language) {
|
||||
if (config.buildMode === BuildMode.Autobuild) {
|
||||
applyAutobuildAzurePipelinesTimeoutFix();
|
||||
applyAutobuildAzurePipelinesTimeoutFix(env);
|
||||
}
|
||||
try {
|
||||
await runCli(cmd, [
|
||||
@@ -816,7 +821,7 @@ async function getCodeQLForCmd(
|
||||
"--sarif-group-rules-by-pack",
|
||||
"--sarif-include-query-help=always",
|
||||
"--sublanguage-file-coverage",
|
||||
...(await getJobRunUuidSarifOptions()),
|
||||
...(await getJobRunUuidSarifOptions(env)),
|
||||
...getExtraOptionsFromEnv(["database", "interpret-results"]),
|
||||
];
|
||||
if (sarifRunPropertyFlag !== undefined) {
|
||||
@@ -1036,7 +1041,7 @@ async function getCodeQLForCmd(
|
||||
);
|
||||
} else if (
|
||||
checkVersion &&
|
||||
process.env[EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING] !== "true" &&
|
||||
env.getOptional(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING) !== "true" &&
|
||||
!(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION))
|
||||
) {
|
||||
const result = await codeql.getVersion();
|
||||
@@ -1054,7 +1059,7 @@ async function getCodeQLForCmd(
|
||||
}' by 'github/codeql-action/*@v${getActionVersion()}' in your code scanning workflow to ` +
|
||||
"continue using this version of the CodeQL Action.",
|
||||
);
|
||||
exportEnvVar(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
|
||||
core.exportVariable(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
|
||||
}
|
||||
return codeql;
|
||||
}
|
||||
@@ -1256,17 +1261,20 @@ function getExtractionVerbosityArguments(
|
||||
* Without the fix, long build processes will timeout when pulling down Java packages
|
||||
* https://developercommunity.visualstudio.com/content/problem/292284/maven-hosted-agent-connection-timeout.html
|
||||
*/
|
||||
function applyAutobuildAzurePipelinesTimeoutFix() {
|
||||
const javaToolOptions = process.env["JAVA_TOOL_OPTIONS"] || "";
|
||||
process.env["JAVA_TOOL_OPTIONS"] = [
|
||||
...javaToolOptions.split(/\s+/),
|
||||
"-Dhttp.keepAlive=false",
|
||||
"-Dmaven.wagon.http.pool=false",
|
||||
].join(" ");
|
||||
function applyAutobuildAzurePipelinesTimeoutFix(env: Env) {
|
||||
const javaToolOptions = env.getOptional("JAVA_TOOL_OPTIONS") ?? "";
|
||||
env.set(
|
||||
"JAVA_TOOL_OPTIONS",
|
||||
[
|
||||
...javaToolOptions.split(/\s+/),
|
||||
"-Dhttp.keepAlive=false",
|
||||
"-Dmaven.wagon.http.pool=false",
|
||||
].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
async function getJobRunUuidSarifOptions() {
|
||||
const jobRunUuid = process.env[EnvVar.JOB_RUN_UUID];
|
||||
async function getJobRunUuidSarifOptions(env: ReadOnlyEnv) {
|
||||
const jobRunUuid = env.getOptional(EnvVar.JOB_RUN_UUID);
|
||||
|
||||
return jobRunUuid ? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`] : [];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as yaml from "js-yaml";
|
||||
|
||||
import { ActionState } from "./action-common";
|
||||
@@ -45,7 +46,7 @@ import {
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar, exportEnvVar } from "./environment";
|
||||
import { EnvVar } from "./environment";
|
||||
import * as errorMessages from "./error-messages";
|
||||
import { Feature, FeatureEnablement, FeatureWithoutCLI } from "./feature-flags";
|
||||
import {
|
||||
@@ -964,10 +965,10 @@ async function setCppTrapCachingEnvironmentVariables(
|
||||
);
|
||||
} else if (config.trapCaches[BuiltInLanguage.cpp]) {
|
||||
logger.info("Enabling TRAP caching for C/C++.");
|
||||
exportEnvVar(envVar, "true");
|
||||
core.exportVariable(envVar, "true");
|
||||
} else {
|
||||
logger.debug(`Disabling TRAP caching for C/C++.`);
|
||||
exportEnvVar(envVar, "false");
|
||||
core.exportVariable(envVar, "false");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { dbIsFinalized } from "./analyze";
|
||||
import { scanArtifactsForTokens } from "./artifact-scanner";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { EnvVar, exportEnvVar } from "./environment";
|
||||
import { EnvVar } from "./environment";
|
||||
import * as json from "./json";
|
||||
import { Language } from "./languages";
|
||||
import { Logger, withGroup } from "./logging";
|
||||
@@ -330,7 +330,7 @@ export async function uploadArtifacts(
|
||||
// some issues early.
|
||||
if (isInTestMode()) {
|
||||
await scanArtifactsForTokens(toUpload, logger);
|
||||
exportEnvVar("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
|
||||
core.exportVariable("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
|
||||
}
|
||||
|
||||
const suffix = getArtifactSuffix(getOptionalInput("matrix"));
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import test from "ava";
|
||||
|
||||
import { Env, ReadOnlyEnv } from "./environment";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("isTestingEnv() is true", (t) => {
|
||||
t.true(new ReadOnlyEnv(process.env).isTestingEnv());
|
||||
t.true(new Env(process.env).isTestingEnv());
|
||||
t.true(getTestEnv().isTestingEnv());
|
||||
});
|
||||
@@ -1,10 +1,3 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
/**
|
||||
* This constant is set in `ava.setup.mjs` for tests.
|
||||
*/
|
||||
declare const __CODEQL_ACTION_TEST_ENV__: string | undefined;
|
||||
|
||||
/**
|
||||
* Environment variables used by Default Setup to communicate the private registry proxy configuration.
|
||||
*/
|
||||
@@ -218,20 +211,8 @@ export enum ActionsEnvVars {
|
||||
RUNNER_TOOL_CACHE = "RUNNER_TOOL_CACHE",
|
||||
}
|
||||
|
||||
/** Environment variables which are not specific to CodeQL. */
|
||||
export enum SystemEnvVar {
|
||||
/**
|
||||
* Used by Node and related tools to indicate what kind of environment we are running in.
|
||||
*/
|
||||
NODE_ENV = "NODE_ENV",
|
||||
}
|
||||
|
||||
/** A type representing all known environment variables. */
|
||||
export type KnownEnvVar =
|
||||
| EnvVar
|
||||
| ActionsEnvVars
|
||||
| RegistryProxyVars
|
||||
| SystemEnvVar;
|
||||
export type KnownEnvVar = EnvVar | ActionsEnvVars | RegistryProxyVars;
|
||||
|
||||
/**
|
||||
* Gets an environment variable, but throws an error if it is not set.
|
||||
@@ -308,26 +289,6 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
public entries(): Array<[string, T]> {
|
||||
return Object.entries(this.vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether we should skip uploads of
|
||||
* all kinds (SARIF results, status reports, DBs, ...).
|
||||
*
|
||||
* This is not guaranteed to be set in all test environments.
|
||||
*/
|
||||
public isSkippingUploadsInTests(): boolean {
|
||||
return this.getOptional(EnvVar.TEST_MODE) === "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicative of whether we are in a testing environment
|
||||
* by testing whether the value of the `NODE_ENV` variable is "test".
|
||||
* This is expected to be the case if e.g. `ava` is running the tests
|
||||
* or if this instance was constructed by `getTestEnv`.
|
||||
*/
|
||||
public isTestingEnv(): boolean {
|
||||
return __CODEQL_ACTION_TEST_ENV__ === "unit-test";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,26 +307,6 @@ export class Env<
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around `core.exportVariable` which does not call `core.exportVariable`
|
||||
* when running unit tests. This is important, because otherwise `core.exportVariable`
|
||||
* sets environment variables for other steps in a workflow when we run unit tests in CI.
|
||||
*
|
||||
* @param name The name of the environment variable to set and export.
|
||||
* @param val The value to set and export for the environment variable.
|
||||
*/
|
||||
public export(name: string, val: T): void {
|
||||
// Setting the environment variable for this instance is always OK, including
|
||||
// in tests, since we use fresh `Env` instances whenever needed. This allows
|
||||
// tests to pass that rely on that part of the `core.exportVariable` behaviour.
|
||||
this.set(name, val);
|
||||
|
||||
// Call `core.exportVariable` whenever we are not in a test environment.
|
||||
if (!this.isTestingEnv()) {
|
||||
core.exportVariable(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a value indicating whether `set` was called at least once. */
|
||||
public hasChanged(): boolean {
|
||||
return this.changed;
|
||||
@@ -376,34 +317,3 @@ export class Env<
|
||||
export function getEnv(env: NodeJS.ProcessEnv = process.env): Env {
|
||||
return new Env(env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are in test mode. This is used by CodeQL Action PR checks.
|
||||
*
|
||||
* In test mode, we skip several uploads (SARIF results, status reports, DBs, ...).
|
||||
*
|
||||
* @deprecated
|
||||
* The purpose of this function is ambiguous. Use `isSkippingUploadsInTests` on
|
||||
* a `ReadOnlyEnv` instance instead for equivalent behaviour. Use `isTestingEnv`
|
||||
* to determine if we are running in a unit test.
|
||||
*/
|
||||
export function isInTestMode(): boolean {
|
||||
return getEnv().isSkippingUploadsInTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around `core.exportVariable` which does not call `core.exportVariable`
|
||||
* when running unit tests. This is important, because otherwise `core.exportVariable`
|
||||
* sets environment variables for other steps in a workflow when we run unit tests in CI.
|
||||
*
|
||||
* @deprecated Use `export` on an `Env` instance instead.
|
||||
*/
|
||||
export function exportEnvVar(name: string, val: any): void {
|
||||
const env = getEnv();
|
||||
|
||||
if (typeof val === "string") {
|
||||
env.export(name, val);
|
||||
} else {
|
||||
env.export(name, JSON.stringify(val));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,8 @@ export enum Feature {
|
||||
*/
|
||||
PerLanguageBundles = "per_language_bundles_v2",
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
/** Whether we should fail early if we detect that traced Swift analysis is unsupported. */
|
||||
SwiftSkipUnsupportedTracedAnalysis = "swift_skip_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. */
|
||||
@@ -466,6 +468,11 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.SwiftSkipUnsupportedTracedAnalysis]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SWIFT_SKIP_UNSUPPORTED_TRACED_ANALYSIS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ToolsRepositoryProperty]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_TOOLS_REPOSITORY_PROPERTY",
|
||||
|
||||
35
src/fs.ts
Normal file
35
src/fs.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* This module exports a `FileSystem` type which corresponds to the interface of the "fs" module.
|
||||
*
|
||||
* Functions which are parameterised over this type can then be passed a different implementation in tests:
|
||||
*
|
||||
* ```typescript
|
||||
* import * as nodefs from "fs";
|
||||
*
|
||||
* function foo(fs: FileSystem = nodefs) {
|
||||
* // Uses the real "fs" module by default, but can be given a different implementation.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The type can also be constrained to a subset of available operations. For example, in the following
|
||||
* case we have a function that only needs `statSync`:
|
||||
*
|
||||
* ```
|
||||
* function bar(fs: FileSystem<"statSync"> = nodefs) {
|
||||
* // This function can only use `statSync`.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This is useful to define a clearer interface for what the function does and also only requires stubbing
|
||||
* of the relevant functions.
|
||||
*/
|
||||
|
||||
import * as fs from "fs";
|
||||
|
||||
/** Represents the names of operations exported from "fs". */
|
||||
export type FileOperation = keyof typeof fs;
|
||||
|
||||
/** Represents the type of "fs", optionally filtered down to just `Ops`. */
|
||||
export type FileSystem<Ops extends FileOperation = keyof typeof fs> = {
|
||||
[Key in Ops]: (typeof fs)[Key];
|
||||
};
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
DependencyCachingUsageReport,
|
||||
getDependencyCacheUsage,
|
||||
} from "./dependency-caching";
|
||||
import { EnvVar, getEnv, exportEnvVar } from "./environment";
|
||||
import { EnvVar, getEnv } from "./environment";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import * as initActionPostHelper from "./init-action-post-helper";
|
||||
@@ -166,7 +166,7 @@ function getFinalJobStatus(config: Config | undefined): JobStatus {
|
||||
let jobStatus: JobStatus;
|
||||
|
||||
if (process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] === "true") {
|
||||
exportEnvVar(EnvVar.JOB_STATUS, JobStatus.SuccessStatus);
|
||||
core.exportVariable(EnvVar.JOB_STATUS, JobStatus.SuccessStatus);
|
||||
jobStatus = JobStatus.SuccessStatus;
|
||||
} else if (config !== undefined) {
|
||||
// - We have computed a CodeQL config
|
||||
@@ -191,7 +191,7 @@ function getFinalJobStatus(config: Config | undefined): JobStatus {
|
||||
|
||||
// This shouldn't be necessary, but in the odd case that we run more than one
|
||||
// `init` post step, ensure the job status is consistent between them.
|
||||
exportEnvVar(EnvVar.JOB_STATUS, jobStatus);
|
||||
core.exportVariable(EnvVar.JOB_STATUS, jobStatus);
|
||||
return jobStatus;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
runDatabaseInitCluster,
|
||||
} from "./init";
|
||||
import { JavaEnvVars, BuiltInLanguage } from "./languages";
|
||||
import { isSwiftCompatible } from "./languages/swift";
|
||||
import { Logger, withGroupAsync } from "./logging";
|
||||
import {
|
||||
downloadOverlayBaseDatabaseFromCache,
|
||||
@@ -93,6 +94,7 @@ import {
|
||||
checkActionVersion,
|
||||
getErrorMessage,
|
||||
BuildMode,
|
||||
getOptionalEnvVar,
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
@@ -199,7 +201,7 @@ async function sendCompletedStatusReport(
|
||||
}
|
||||
|
||||
async function run(
|
||||
actionState: ActionState<["Base", "Logger", "Env", "Actions"]>,
|
||||
actionState: ActionState<["Base", "Logger", "Env", "Actions", "FS"]>,
|
||||
) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
@@ -252,7 +254,7 @@ async function run(
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
|
||||
actionState.env.export(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||
|
||||
// path.resolve() respects the intended semantics of source-root. If
|
||||
// source-root is relative, it is relative to the GITHUB_WORKSPACE. If
|
||||
@@ -368,7 +370,7 @@ async function run(
|
||||
);
|
||||
}
|
||||
if (semver.lt(actualVer, publicPreview)) {
|
||||
actionState.env.export(EnvVar.EXPERIMENTAL_FEATURES, "true");
|
||||
core.exportVariable(EnvVar.EXPERIMENTAL_FEATURES, "true");
|
||||
logger.info("Experimental Rust analysis enabled");
|
||||
}
|
||||
}
|
||||
@@ -417,14 +419,7 @@ async function run(
|
||||
logger,
|
||||
});
|
||||
|
||||
if (
|
||||
config.languages.includes(BuiltInLanguage.swift) &&
|
||||
process.platform !== "darwin"
|
||||
) {
|
||||
throw new ConfigurationError(
|
||||
`Swift analysis is only supported on macOS runner images. Please migrate to a macOS runner.`,
|
||||
);
|
||||
}
|
||||
await isSwiftCompatible(actionStateWithFeatures, config, codeql);
|
||||
|
||||
if (repositoryPropertiesResult.isFailure()) {
|
||||
addNoLanguageDiagnostic(
|
||||
@@ -516,9 +511,9 @@ async function run(
|
||||
}
|
||||
|
||||
// Forward Go flags
|
||||
const goFlags = actionState.env.getOptional("GOFLAGS");
|
||||
const goFlags = process.env["GOFLAGS"];
|
||||
if (goFlags) {
|
||||
actionState.env.export("GOFLAGS", goFlags);
|
||||
core.exportVariable("GOFLAGS", goFlags);
|
||||
core.warning(
|
||||
"Passing the GOFLAGS env parameter to the init action is deprecated. Please move this to the analyze action.",
|
||||
);
|
||||
@@ -564,7 +559,7 @@ async function run(
|
||||
|
||||
// Store the original location of our wrapper script somewhere where we can
|
||||
// later retrieve it from and cross-check that it hasn't been changed.
|
||||
actionState.env.export(EnvVar.GO_BINARY_LOCATION, goWrapperPath);
|
||||
core.exportVariable(EnvVar.GO_BINARY_LOCATION, goWrapperPath);
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
`Analyzing Go on Linux, but failed to install wrapper script. Tracing custom builds may fail: ${e}`,
|
||||
@@ -573,7 +568,7 @@ async function run(
|
||||
} else {
|
||||
// Store the location of the original Go binary, so we can check that no setup tasks were performed after the
|
||||
// `init` Action ran.
|
||||
actionState.env.export(EnvVar.GO_BINARY_LOCATION, goBinaryPath);
|
||||
core.exportVariable(EnvVar.GO_BINARY_LOCATION, goBinaryPath);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
@@ -608,12 +603,12 @@ async function run(
|
||||
// threads it would ask extractors to use. See help text for the "--ram" and "--threads"
|
||||
// options at https://codeql.github.com/docs/codeql-cli/manual/database-trace-command/
|
||||
// for details.
|
||||
actionState.env.export(
|
||||
core.exportVariable(
|
||||
"CODEQL_RAM",
|
||||
process.env["CODEQL_RAM"] ||
|
||||
getCodeQLMemoryLimit(getOptionalInput("ram"), logger).toString(),
|
||||
);
|
||||
actionState.env.export(
|
||||
core.exportVariable(
|
||||
"CODEQL_THREADS",
|
||||
process.env["CODEQL_THREADS"] ||
|
||||
getThreadsFlagValue(getOptionalInput("threads"), logger).toString(),
|
||||
@@ -621,15 +616,12 @@ async function run(
|
||||
|
||||
// Disable Kotlin extractor if feature flag set
|
||||
if (await features.getValue(Feature.DisableKotlinAnalysisEnabled)) {
|
||||
actionState.env.export(
|
||||
"CODEQL_EXTRACTOR_JAVA_AGENT_DISABLE_KOTLIN",
|
||||
"true",
|
||||
);
|
||||
core.exportVariable("CODEQL_EXTRACTOR_JAVA_AGENT_DISABLE_KOTLIN", "true");
|
||||
}
|
||||
|
||||
// Emergency override to force the CodeQL CLI back to the JGit-based Git backend.
|
||||
if (await features.getValue(Feature.ForceJGit)) {
|
||||
actionState.env.export("CODEQL_GIT_BACKEND", "jgit");
|
||||
core.exportVariable("CODEQL_GIT_BACKEND", "jgit");
|
||||
}
|
||||
|
||||
const kotlinLimitVar =
|
||||
@@ -638,7 +630,7 @@ async function run(
|
||||
(await codeQlVersionAtLeast(codeql, "2.20.3")) &&
|
||||
!(await codeQlVersionAtLeast(codeql, "2.20.4"))
|
||||
) {
|
||||
actionState.env.export(kotlinLimitVar, "2.1.20");
|
||||
core.exportVariable(kotlinLimitVar, "2.1.20");
|
||||
}
|
||||
|
||||
// Restore dependency cache(s), if they exist.
|
||||
@@ -687,7 +679,7 @@ async function run(
|
||||
config.buildMode === BuildMode.None &&
|
||||
config.languages.includes(BuiltInLanguage.java)
|
||||
) {
|
||||
actionState.env.export(
|
||||
core.exportVariable(
|
||||
EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS,
|
||||
"true",
|
||||
);
|
||||
@@ -745,7 +737,7 @@ async function run(
|
||||
const tracerConfig = await getCombinedTracerConfig(logger, codeql, config);
|
||||
if (tracerConfig !== undefined) {
|
||||
for (const [key, value] of Object.entries(tracerConfig.env)) {
|
||||
actionState.env.export(key, value);
|
||||
core.exportVariable(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,10 +745,10 @@ async function run(
|
||||
if (await features.getValue(Feature.JavaNetworkDebugging)) {
|
||||
// Get the existing value of `JAVA_TOOL_OPTIONS`, if any.
|
||||
const existingJavaToolOptions =
|
||||
actionState.env.getOptional(JavaEnvVars.JAVA_TOOL_OPTIONS) ?? "";
|
||||
getOptionalEnvVar(JavaEnvVars.JAVA_TOOL_OPTIONS) || "";
|
||||
|
||||
// Add the network debugging options.
|
||||
actionState.env.export(
|
||||
core.exportVariable(
|
||||
JavaEnvVars.JAVA_TOOL_OPTIONS,
|
||||
`${existingJavaToolOptions} -Djavax.net.debug=all`,
|
||||
);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as github from "@actions/github";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import * as environment from "./environment";
|
||||
import { ActionsEnvVars } from "./environment";
|
||||
import { Feature } from "./feature-flags";
|
||||
import {
|
||||
checkPacksForOverlayCompatibility,
|
||||
@@ -84,7 +85,7 @@ for (const { runnerEnv, ErrorConstructor, message } of [
|
||||
`cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
process.env[environment.ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv;
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv;
|
||||
|
||||
const dbLocation = path.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
@@ -545,7 +546,7 @@ test.serial(
|
||||
test.serial(
|
||||
"file coverage deprecation warning for org-owned repo with default setup recommends repo property",
|
||||
(t) => {
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -565,14 +566,14 @@ test.serial(
|
||||
'with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to ' +
|
||||
"`true` in the repository's settings.",
|
||||
);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for org-owned repo with advanced setup recommends env var and repo property",
|
||||
(t) => {
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -593,14 +594,14 @@ test.serial(
|
||||
'with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to ' +
|
||||
"`true` in the repository's settings.",
|
||||
);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for user-owned repo with default setup recommends advanced setup",
|
||||
(t) => {
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -619,14 +620,14 @@ test.serial(
|
||||
"To opt out of this change, switch to an advanced setup workflow and " +
|
||||
"set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for user-owned repo with advanced setup recommends env var",
|
||||
(t) => {
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -644,14 +645,14 @@ test.serial(
|
||||
"to improve analysis performance. File coverage information will still be computed on non-PR analyses.\n\n" +
|
||||
"To opt out of this change, set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for unknown owner type with default setup recommends advanced setup",
|
||||
(t) => {
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
|
||||
github.context.payload = { repository: undefined };
|
||||
const messages: LoggedMessage[] = [];
|
||||
@@ -665,14 +666,14 @@ test.serial(
|
||||
"To opt out of this change, switch to an advanced setup workflow and " +
|
||||
"set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for unknown owner type with advanced setup recommends env var",
|
||||
(t) => {
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
|
||||
github.context.payload = { repository: undefined };
|
||||
const messages: LoggedMessage[] = [];
|
||||
@@ -685,7 +686,7 @@ test.serial(
|
||||
"to improve analysis performance. File coverage information will still be computed on non-PR analyses.\n\n" +
|
||||
"To opt out of this change, set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -694,10 +695,10 @@ test.serial(
|
||||
(t) => {
|
||||
process.env["CODEQL_ACTION_DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION"] =
|
||||
"true";
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const messages: LoggedMessage[] = [];
|
||||
logFileCoverageOnPrsDeprecationWarning(getRecordingLogger(messages));
|
||||
t.is(messages.length, 0);
|
||||
t.true(exportEnvVarStub.notCalled);
|
||||
t.true(exportVariableStub.notCalled);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as github from "@actions/github";
|
||||
import * as io from "@actions/io";
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
import { GitHubApiDetails } from "./api-client";
|
||||
import { CodeQL, setupCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { EnvVar, exportEnvVar } from "./environment";
|
||||
import { EnvVar } from "./environment";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
Feature,
|
||||
@@ -410,5 +411,5 @@ export function logFileCoverageOnPrsDeprecationWarning(logger: Logger): void {
|
||||
}
|
||||
|
||||
logger.warning(message);
|
||||
exportEnvVar(EnvVar.DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION, "true");
|
||||
core.exportVariable(EnvVar.DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION, "true");
|
||||
}
|
||||
|
||||
589
src/languages/swift.test.ts
Normal file
589
src/languages/swift.test.ts
Normal file
@@ -0,0 +1,589 @@
|
||||
import * as fs from "fs";
|
||||
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { getCodeQLForTesting } from "../codeql";
|
||||
import * as diagnostics from "../diagnostics";
|
||||
import { ActionsEnvVars } from "../environment";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { FileSystem } from "../fs";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
checkUnexpectedLogMessages,
|
||||
createFeatures,
|
||||
createTestConfig,
|
||||
getTestEnv,
|
||||
initAllState,
|
||||
makeVersionInfo,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
import { ToolsFeature } from "../tools-features";
|
||||
import { withTmpDir } from "../util";
|
||||
|
||||
import {
|
||||
isSwiftCompatible,
|
||||
XCODE_SELECT_LINK_PATH,
|
||||
xcodeVersion,
|
||||
} from "./swift";
|
||||
|
||||
import { BuiltInLanguage } from ".";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
type RequiredFS = FileSystem<"statSync" | "readlinkSync">;
|
||||
|
||||
/**
|
||||
* Sets up a suitable mock `FileSystem` for use with `xcodeVersion`.
|
||||
*
|
||||
* @param statSyncResult The result of `statSync`.
|
||||
* @param readlinkSyncResult The result of `readlinkSync`.
|
||||
*
|
||||
* @returns The mocked `FileSystem` and stubs.
|
||||
*/
|
||||
function mockFs(
|
||||
statSyncResult: boolean | Error,
|
||||
readlinkSyncResult: string = "",
|
||||
) {
|
||||
const stubbedFs: RequiredFS = {
|
||||
statSync: fs.statSync,
|
||||
readlinkSync: fs.readlinkSync,
|
||||
};
|
||||
const statSync = sinon.stub(stubbedFs, "statSync");
|
||||
|
||||
if (typeof statSyncResult === "boolean") {
|
||||
statSync.returns({ isSymbolicLink: () => statSyncResult } as fs.Stats);
|
||||
} else {
|
||||
statSync.throws(new Error("ENOENT"));
|
||||
}
|
||||
|
||||
const readlinkSync = sinon
|
||||
.stub(stubbedFs, "readlinkSync")
|
||||
.returns(readlinkSyncResult);
|
||||
|
||||
return { stubbedFs, statSync, readlinkSync };
|
||||
}
|
||||
|
||||
test("xcodeVersion returns undefined if symlink doesn't exist", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const { stubbedFs, statSync } = mockFs(new Error("ENOENT"));
|
||||
|
||||
t.is(xcodeVersion(logger, stubbedFs), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Unable to determine Xcode version: ENOENT",
|
||||
]);
|
||||
});
|
||||
|
||||
test("xcodeVersion returns undefined if file is not a symlink", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const { stubbedFs, statSync } = mockFs(false);
|
||||
|
||||
t.is(xcodeVersion(logger, stubbedFs), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"exists, but is not a symbolic link",
|
||||
]);
|
||||
});
|
||||
|
||||
test("xcodeVersion returns undefined if resolving the symlink returns nothing", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const { stubbedFs, statSync, readlinkSync } = mockFs(true);
|
||||
|
||||
t.is(xcodeVersion(logger, stubbedFs), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"unexpectedly returned nothing",
|
||||
]);
|
||||
});
|
||||
|
||||
test("xcodeVersion returns undefined if resolved path doesn't include pattern", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const { stubbedFs, statSync, readlinkSync } = mockFs(
|
||||
true,
|
||||
"/Applications/Xcode.app/Contents/Developer",
|
||||
);
|
||||
|
||||
t.is(xcodeVersion(logger, stubbedFs), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"does not contain expected pattern",
|
||||
]);
|
||||
});
|
||||
|
||||
test("xcodeVersion returns undefined if match can't be parsed", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const { stubbedFs, statSync, readlinkSync } = mockFs(
|
||||
true,
|
||||
"/Applications/Xcode_00.0.app/Contents/Developer",
|
||||
);
|
||||
|
||||
t.is(xcodeVersion(logger, stubbedFs), undefined);
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Couldn't parse '00.0' as a semantic version.",
|
||||
]);
|
||||
});
|
||||
|
||||
test("xcodeVersion returns version from resolved path", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
const { stubbedFs, statSync, readlinkSync } = mockFs(
|
||||
true,
|
||||
"/Applications/Xcode_16.4.app/Contents/Developer",
|
||||
);
|
||||
|
||||
const result = xcodeVersion(logger, stubbedFs);
|
||||
|
||||
if (t.truthy(result)) {
|
||||
t.is(result.major, 16);
|
||||
t.is(result.minor, 4);
|
||||
}
|
||||
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
});
|
||||
|
||||
test("isSwiftCompatible doesn't throw for non-Swift languages", async (t) => {
|
||||
for (const language of Object.values(BuiltInLanguage)) {
|
||||
if (language === BuiltInLanguage.swift) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const codeql = await getCodeQLForTesting();
|
||||
await t.notThrowsAsync(
|
||||
isSwiftCompatible(
|
||||
initAllState(),
|
||||
createTestConfig({ languages: [language] }),
|
||||
codeql,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("isSwiftCompatible 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(
|
||||
isSwiftCompatible(
|
||||
initAllState({ platform: "darwin", env, logger }),
|
||||
createTestConfig({ languages: [BuiltInLanguage.swift] }),
|
||||
codeql,
|
||||
),
|
||||
);
|
||||
|
||||
t.is(supportsFeature.callCount, 1);
|
||||
t.deepEqual(supportsFeature.args[0], [
|
||||
ToolsFeature.SwiftSupportsAllPlatforms,
|
||||
]);
|
||||
}));
|
||||
|
||||
test("isSwiftCompatible doesn't throw for Swift on darwin", 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);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
await t.notThrowsAsync(
|
||||
isSwiftCompatible(
|
||||
initAllState({ platform: "darwin", env, logger }),
|
||||
createTestConfig({ languages: [BuiltInLanguage.swift] }),
|
||||
codeql,
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
const nonDarwinPlatforms: NodeJS.Platform[] = ["linux", "win32"];
|
||||
for (const nonDarwinPlatform of nonDarwinPlatforms) {
|
||||
test(`isSwiftCompatible 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 getCodeQLForTesting(
|
||||
"codeql-for-testing",
|
||||
logger,
|
||||
env,
|
||||
);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
await t.throwsAsync(
|
||||
isSwiftCompatible(
|
||||
initAllState({ platform: nonDarwinPlatform, env, logger }),
|
||||
createTestConfig({ languages: [BuiltInLanguage.swift] }),
|
||||
codeql,
|
||||
),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
test("isSwiftCompatible 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 codeql = await getCodeQLForTesting("codeql-for-testing", logger, env);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
await isSwiftCompatible(
|
||||
initAllState({
|
||||
logger,
|
||||
platform: "darwin",
|
||||
osRelease: "unexpected",
|
||||
env,
|
||||
}),
|
||||
createTestConfig({ languages: [BuiltInLanguage.swift] }),
|
||||
codeql,
|
||||
);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Unable to determine version of macOS, got: unexpected",
|
||||
]);
|
||||
}));
|
||||
|
||||
// `addDiagnostic` changes global state and we must stub it, so this test must be serial.
|
||||
test.serial(
|
||||
"isSwiftCompatible logs and adds diagnostic if macOS version is unsupported",
|
||||
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,
|
||||
);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
|
||||
|
||||
const config = createTestConfig({ languages: [BuiltInLanguage.swift] });
|
||||
await isSwiftCompatible(
|
||||
initAllState({
|
||||
logger,
|
||||
platform: "darwin",
|
||||
osRelease: "27.0.0",
|
||||
env,
|
||||
}),
|
||||
config,
|
||||
codeql,
|
||||
);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Traced Swift analysis is not supported on macOS 27",
|
||||
]);
|
||||
|
||||
t.is(addDiagnostic.callCount, 1);
|
||||
t.like(addDiagnostic.args[0], [
|
||||
config,
|
||||
BuiltInLanguage.swift,
|
||||
{
|
||||
attributes: {
|
||||
languages: [BuiltInLanguage.swift],
|
||||
macOSVersion: "27.0.0",
|
||||
},
|
||||
severity: "warning",
|
||||
source: {
|
||||
id: "codeql-action/unsupported-traced-swift-analysis-macos",
|
||||
name: "Traced Swift 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(
|
||||
"isSwiftCompatible 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.SwiftSkipUnsupportedTracedAnalysis,
|
||||
]);
|
||||
|
||||
const codeql = await getCodeQLForTesting(
|
||||
"codeql-for-testing",
|
||||
logger,
|
||||
env,
|
||||
);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
|
||||
|
||||
const config = createTestConfig({ languages: [BuiltInLanguage.swift] });
|
||||
await t.throwsAsync(
|
||||
isSwiftCompatible(
|
||||
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],
|
||||
macOSVersion: "27.0.0",
|
||||
},
|
||||
severity: "error",
|
||||
source: {
|
||||
id: "codeql-action/unsupported-traced-swift-analysis-macos",
|
||||
name: "Traced Swift 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(
|
||||
"isSwiftCompatible 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 { stubbedFs, statSync, readlinkSync } = mockFs(
|
||||
true,
|
||||
"/Applications/Xcode_26.0.app/Contents/Developer",
|
||||
);
|
||||
|
||||
const codeql = await getCodeQLForTesting(
|
||||
"codeql-for-testing",
|
||||
logger,
|
||||
env,
|
||||
);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
|
||||
|
||||
const config = createTestConfig({ languages: [BuiltInLanguage.swift] });
|
||||
await isSwiftCompatible(
|
||||
initAllState({
|
||||
logger,
|
||||
platform: "darwin",
|
||||
osRelease: "26.0.0",
|
||||
env,
|
||||
fs: stubbedFs as FileSystem,
|
||||
}),
|
||||
config,
|
||||
codeql,
|
||||
);
|
||||
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkUnexpectedLogMessages(t, logger.messages, [
|
||||
"Traced Swift 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(
|
||||
"isSwiftCompatible 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 { stubbedFs, statSync, readlinkSync } = mockFs(
|
||||
true,
|
||||
"/Applications/Xcode_27.0.app/Contents/Developer",
|
||||
);
|
||||
|
||||
const codeql = await getCodeQLForTesting(
|
||||
"codeql-for-testing",
|
||||
logger,
|
||||
env,
|
||||
);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
|
||||
|
||||
const config = createTestConfig({ languages: [BuiltInLanguage.swift] });
|
||||
await isSwiftCompatible(
|
||||
initAllState({
|
||||
logger,
|
||||
platform: "darwin",
|
||||
osRelease: "26.0.0",
|
||||
env,
|
||||
fs: stubbedFs as FileSystem,
|
||||
}),
|
||||
config,
|
||||
codeql,
|
||||
);
|
||||
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
"Traced Swift 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-swift-analysis-xcode",
|
||||
name: "Traced Swift 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(
|
||||
"isSwiftCompatible 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 { stubbedFs, statSync, readlinkSync } = mockFs(
|
||||
true,
|
||||
"/Applications/Xcode_27.0.app/Contents/Developer",
|
||||
);
|
||||
|
||||
const features = createFeatures([
|
||||
Feature.SwiftSkipUnsupportedTracedAnalysis,
|
||||
]);
|
||||
|
||||
const codeql = await getCodeQLForTesting(
|
||||
"codeql-for-testing",
|
||||
logger,
|
||||
env,
|
||||
);
|
||||
sinon.stub(codeql, "getVersion").resolves(makeVersionInfo("2.27.0"));
|
||||
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addDiagnostic");
|
||||
|
||||
const config = createTestConfig({ languages: [BuiltInLanguage.swift] });
|
||||
await t.throwsAsync(
|
||||
isSwiftCompatible(
|
||||
initAllState({
|
||||
logger,
|
||||
platform: "darwin",
|
||||
osRelease: "26.0.0",
|
||||
env,
|
||||
features,
|
||||
fs: stubbedFs as FileSystem,
|
||||
}),
|
||||
config,
|
||||
codeql,
|
||||
),
|
||||
);
|
||||
|
||||
t.is(statSync.callCount, 1);
|
||||
t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
t.is(readlinkSync.callCount, 1);
|
||||
t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]);
|
||||
|
||||
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-swift-analysis-xcode",
|
||||
name: "Traced Swift analysis is not supported on this version of Xcode",
|
||||
},
|
||||
visibility: {
|
||||
cliSummaryTable: true,
|
||||
statusPage: true,
|
||||
telemetry: true,
|
||||
},
|
||||
} satisfies Partial<diagnostics.DiagnosticMessage>,
|
||||
]);
|
||||
}),
|
||||
);
|
||||
218
src/languages/swift.ts
Normal file
218
src/languages/swift.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import * as semver from "semver";
|
||||
|
||||
import { ActionState, Logger } from "../action-common";
|
||||
import { CodeQL } from "../codeql";
|
||||
import { Config } from "../config-utils";
|
||||
import { addDiagnostic, makeDiagnostic } from "../diagnostics";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { FileSystem } from "../fs";
|
||||
import { macOSVersion } from "../platform";
|
||||
import { ToolsFeature } from "../tools-features";
|
||||
import { ConfigurationError, getErrorMessage } from "../util";
|
||||
|
||||
import { BuiltInLanguage } from ".";
|
||||
|
||||
/** 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)/,
|
||||
);
|
||||
|
||||
/** macOS 27 and above do not support traced extraction for Swift. */
|
||||
export const SWIFT_TRACED_UNSUPPORTED_MACOS = 27;
|
||||
|
||||
/** Xcode 27 and above do not support traced extraction for Swift. */
|
||||
export const SWIFT_TRACED_UNSUPPORTED_XCODE = 27;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
fs: FileSystem<"statSync" | "readlinkSync">,
|
||||
): semver.SemVer | undefined {
|
||||
try {
|
||||
// Stat the expected symbolic link to check that it exists and is a symbolic link.
|
||||
// The `readlinkSync` call below returns an empty string in either case and so
|
||||
// this check allows us to distinguish between the two cases.
|
||||
const stats = fs.statSync(XCODE_SELECT_LINK_PATH);
|
||||
|
||||
if (!stats.isSymbolicLink()) {
|
||||
logger.warning(
|
||||
`${XCODE_SELECT_LINK_PATH} exists, but is not a symbolic link.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Read what the symbolic link points to.
|
||||
const xcodePath = fs.readlinkSync(XCODE_SELECT_LINK_PATH);
|
||||
|
||||
if (xcodePath === "") {
|
||||
logger.warning(
|
||||
`Resolving ${XCODE_SELECT_LINK_PATH} unexpectedly returned nothing.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try to extract the version from the path.
|
||||
const matchResult = xcodePath.match(XCODE_APP_FILENAME_PATTERN);
|
||||
|
||||
if (matchResult?.groups === undefined) {
|
||||
logger.warning(
|
||||
`Xcode path '${xcodePath}' does not contain expected pattern.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const majorMinor = matchResult.groups["majorMinor"];
|
||||
const version = semver.coerce(majorMinor);
|
||||
|
||||
if (version === null) {
|
||||
logger.warning(`Couldn't parse '${majorMinor}' as a semantic version.`);
|
||||
return undefined;
|
||||
}
|
||||
return version;
|
||||
} catch (err) {
|
||||
logger.warning(
|
||||
`Unable to determine Xcode version: ${getErrorMessage(err)}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic indicating that `version` of `product` is unsupported for traced Swift 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 skipUnsupportedTracedAnalysis 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: Config,
|
||||
skipUnsupportedTracedAnalysis: boolean,
|
||||
product: "macOS" | "Xcode",
|
||||
version: semver.SemVer,
|
||||
) {
|
||||
const baseMessage = [
|
||||
`Traced Swift analysis is not supported on ${product} ${SWIFT_TRACED_UNSUPPORTED_MACOS} or above.`,
|
||||
`Configure your analysis to run on macOS ${SWIFT_TRACED_UNSUPPORTED_MACOS - 1} or below`,
|
||||
`and XCode ${SWIFT_TRACED_UNSUPPORTED_XCODE - 1} or below.`,
|
||||
].join(" ");
|
||||
|
||||
const attributeName = product === "macOS" ? "macOSVersion" : "xcodeVersion";
|
||||
|
||||
// Create a diagnostic that will show up on the TSP.
|
||||
addDiagnostic(
|
||||
config,
|
||||
BuiltInLanguage.swift,
|
||||
makeDiagnostic(
|
||||
`codeql-action/unsupported-traced-swift-analysis-${product.toLowerCase()}`,
|
||||
`Traced Swift analysis is not supported on this version of ${product}`,
|
||||
{
|
||||
attributes: {
|
||||
languages: config.languages,
|
||||
[attributeName]: version.toString(),
|
||||
},
|
||||
markdownMessage: baseMessage,
|
||||
severity: skipUnsupportedTracedAnalysis ? "error" : "warning",
|
||||
visibility: {
|
||||
cliSummaryTable: true,
|
||||
statusPage: true,
|
||||
telemetry: true,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Throw an error to abort the analysis if the FF is enabled or log the message.
|
||||
if (skipUnsupportedTracedAnalysis) {
|
||||
// ConfigurationErrors are converted to the "aborted" status by the exception handler
|
||||
// in `init-action.ts` that guards the call to `isSwiftCompatible`.
|
||||
throw new ConfigurationError(baseMessage);
|
||||
} else {
|
||||
// This will also show up as a workflow annotation.
|
||||
logger.warning(baseMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we can run a Swift analysis on the current runner.
|
||||
*
|
||||
* @param action The Action state.
|
||||
* @param config The Action configuration.
|
||||
*
|
||||
* @throws {ConfigurationError} If Swift analysis is not possible on the current runner.
|
||||
* @returns True if we can run a Swift analysis.
|
||||
*/
|
||||
export async function isSwiftCompatible(
|
||||
action: ActionState<["Base", "Logger", "FeatureFlags", "FS"]>,
|
||||
config: Config,
|
||||
codeql: CodeQL,
|
||||
) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Try to get the macOS version.
|
||||
const version = macOSVersion(action);
|
||||
|
||||
// If `version` is undefined, then we are not on macOS.
|
||||
if (version === undefined) {
|
||||
throw new ConfigurationError(
|
||||
`Swift analysis is only supported on macOS runner images. Please migrate to a macOS runner.`,
|
||||
);
|
||||
}
|
||||
|
||||
const skipUnsupportedTracedAnalysis = await action.features.getValue(
|
||||
Feature.SwiftSkipUnsupportedTracedAnalysis,
|
||||
);
|
||||
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 determine version of macOS, got: ${version}`,
|
||||
);
|
||||
} else if (version.major >= SWIFT_TRACED_UNSUPPORTED_MACOS) {
|
||||
handleUnsupportedVersion(
|
||||
action.logger,
|
||||
config,
|
||||
skipUnsupportedTracedAnalysis,
|
||||
"macOS",
|
||||
version,
|
||||
);
|
||||
}
|
||||
|
||||
// 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, action.fs);
|
||||
|
||||
if (
|
||||
xcodeVer !== undefined &&
|
||||
xcodeVer.major >= SWIFT_TRACED_UNSUPPORTED_XCODE
|
||||
) {
|
||||
handleUnsupportedVersion(
|
||||
action.logger,
|
||||
config,
|
||||
skipUnsupportedTracedAnalysis,
|
||||
"Xcode",
|
||||
xcodeVer,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import * as sinon from "sinon";
|
||||
import * as actionsUtil from "../actions-util";
|
||||
import * as apiClient from "../api-client";
|
||||
import type { ResolveDatabaseOutput } from "../codeql";
|
||||
import * as environment from "../environment";
|
||||
import * as gitUtils from "../git-utils";
|
||||
import { BuiltInLanguage } from "../languages";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
@@ -83,7 +82,7 @@ const testDownloadOverlayBaseDatabaseFromCache = makeMacro({
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
|
||||
sinon.stub(environment, "isInTestMode").returns(testCase.isInTestMode);
|
||||
sinon.stub(utils, "isInTestMode").returns(testCase.isInTestMode);
|
||||
|
||||
if (testCase.restoreCacheResult instanceof Error) {
|
||||
sinon
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import test from "ava";
|
||||
|
||||
import { BundlePlatform, getBundlePlatform } from "./platform";
|
||||
import { BundlePlatform, getBundlePlatform, macOSVersion } from "./platform";
|
||||
import { initAllState } from "./testing-utils";
|
||||
|
||||
for (const [platform, arch, expected] of [
|
||||
["linux", "x64", BundlePlatform.Linux64],
|
||||
@@ -16,3 +17,40 @@ 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(`macOSVersion returns undefined on ${platform}`, (t) => {
|
||||
t.is(macOSVersion(initAllState({ platform })), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
test("macOSVersion returns raw string if semver parsing fails", (t) => {
|
||||
const invalidSemVer = "sealOS-2026";
|
||||
t.is(
|
||||
macOSVersion(
|
||||
initAllState({ platform: "darwin", osRelease: invalidSemVer }),
|
||||
),
|
||||
invalidSemVer,
|
||||
);
|
||||
});
|
||||
|
||||
test("macOSVersion returns semver if parsing succeeds", (t) => {
|
||||
const validSemVer = "27.0.1";
|
||||
|
||||
const version = macOSVersion(
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import * as semver from "semver";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
|
||||
/** Platform identifiers used in CodeQL bundle asset names. */
|
||||
export enum BundlePlatform {
|
||||
Linux64 = "linux64",
|
||||
@@ -24,3 +28,28 @@ export function getBundlePlatform(
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to determine the version of macOS.
|
||||
*
|
||||
* @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 macOSVersion(
|
||||
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 OS version string.
|
||||
const version = semver.parse(action.osRelease);
|
||||
|
||||
if (version === null) {
|
||||
return action.osRelease;
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ async function run(
|
||||
core.setOutput("codeql-path", codeql.getPath());
|
||||
core.setOutput("codeql-version", (await codeql.getVersion()).version);
|
||||
|
||||
actionState.env.export(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
|
||||
core.exportVariable(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
|
||||
} catch (unwrappedError) {
|
||||
const error = wrapError(unwrappedError);
|
||||
core.setFailed(error.message);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { OutgoingHttpHeaders } from "http";
|
||||
import * as path from "path";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import { default as deepEqual } from "fast-deep-equal";
|
||||
import * as semver from "semver";
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
makeDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { EnvVar, exportEnvVar, getEnv } from "./environment";
|
||||
import { EnvVar, getEnv } from "./environment";
|
||||
import {
|
||||
CODEQL_VERSION_ZSTD_BUNDLE,
|
||||
CodeQLDefaultVersionInfo,
|
||||
@@ -1070,7 +1071,7 @@ export async function setupCodeQLBundle(
|
||||
|
||||
// Record that this job now has a copy of the CodeQL tools, so that a later step doesn't delete
|
||||
// the toolcache out from under the path we are about to return.
|
||||
exportEnvVar(EnvVar.HAS_SET_UP_CODEQL, "true");
|
||||
core.exportVariable(EnvVar.HAS_SET_UP_CODEQL, "true");
|
||||
|
||||
return {
|
||||
codeqlFolder,
|
||||
|
||||
@@ -23,13 +23,7 @@ import type { ComputedInput, InputName } from "./config/inputs";
|
||||
import { parseRegistriesWithoutCredentials } from "./config/pack-registries";
|
||||
import type { DependencyCacheRestoreStatusReport } from "./dependency-caching";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import {
|
||||
EnvVar,
|
||||
getEnv,
|
||||
ReadOnlyEnv,
|
||||
RegistryProxyVars,
|
||||
exportEnvVar,
|
||||
} from "./environment";
|
||||
import { EnvVar, getEnv, ReadOnlyEnv, RegistryProxyVars } from "./environment";
|
||||
import { getRef } from "./git-utils";
|
||||
import * as json from "./json";
|
||||
import type { Logger } from "./logging";
|
||||
@@ -77,7 +71,9 @@ export function getDisplayActionName(actionName: ActionName): string {
|
||||
* environment and returns it.
|
||||
* If a new UUID is generated, it is also exported as an environment variable.
|
||||
*/
|
||||
export function getJobUUID(action: ActionState<["Logger", "Env", "Actions"]>) {
|
||||
export function getJobUUID(
|
||||
action: ActionState<["Logger", "ReadOnlyEnv", "Actions"]>,
|
||||
) {
|
||||
// Check if we already have a UUID for the analysis and return it if so.
|
||||
const existingJobRunUuid = action.env.getOptional(EnvVar.JOB_RUN_UUID);
|
||||
|
||||
@@ -90,7 +86,7 @@ export function getJobUUID(action: ActionState<["Logger", "Env", "Actions"]>) {
|
||||
const jobRunUuid = uuid.v4();
|
||||
action.logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
|
||||
action.env.export(EnvVar.JOB_RUN_UUID, jobRunUuid);
|
||||
action.actions.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);
|
||||
return jobRunUuid;
|
||||
}
|
||||
|
||||
@@ -272,12 +268,12 @@ export function getJobStatusDisplayName(status: JobStatus): string {
|
||||
*/
|
||||
function setJobStatusIfUnsuccessful(actionStatus: ActionStatus) {
|
||||
if (actionStatus === "user-error") {
|
||||
exportEnvVar(
|
||||
core.exportVariable(
|
||||
EnvVar.JOB_STATUS,
|
||||
process.env[EnvVar.JOB_STATUS] ?? JobStatus.ConfigErrorStatus,
|
||||
);
|
||||
} else if (actionStatus === "failure" || actionStatus === "aborted") {
|
||||
exportEnvVar(
|
||||
core.exportVariable(
|
||||
EnvVar.JOB_STATUS,
|
||||
process.env[EnvVar.JOB_STATUS] ?? JobStatus.FailureStatus,
|
||||
);
|
||||
@@ -380,7 +376,7 @@ export async function createStatusReportBase(
|
||||
let workflowStartedAt = process.env[EnvVar.WORKFLOW_STARTED_AT];
|
||||
if (workflowStartedAt === undefined) {
|
||||
workflowStartedAt = actionStartedAt.toISOString();
|
||||
exportEnvVar(EnvVar.WORKFLOW_STARTED_AT, workflowStartedAt);
|
||||
core.exportVariable(EnvVar.WORKFLOW_STARTED_AT, workflowStartedAt);
|
||||
}
|
||||
const runnerOs = getRequiredEnvParam("RUNNER_OS");
|
||||
const codeQlCliVersion = getCachedCodeQlVersion(
|
||||
@@ -392,7 +388,7 @@ export async function createStatusReportBase(
|
||||
// re-export the testing environment variable so that it is available to subsequent steps,
|
||||
// even if it was only set for this step
|
||||
if (testingEnvironment) {
|
||||
exportEnvVar(EnvVar.TESTING_ENVIRONMENT, testingEnvironment);
|
||||
core.exportVariable(EnvVar.TESTING_ENVIRONMENT, testingEnvironment);
|
||||
}
|
||||
const isSteadyStateDefaultSetupRun =
|
||||
process.env["CODE_SCANNING_IS_STEADY_STATE_DEFAULT_SETUP"] === "true";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import * as fs from "fs";
|
||||
import { TextDecoder } from "node:util";
|
||||
import * as os from "os";
|
||||
import path from "path";
|
||||
|
||||
import * as github from "@actions/github";
|
||||
@@ -34,6 +36,7 @@ import { 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,
|
||||
@@ -202,6 +205,10 @@ class TestActionsEnv implements ActionsEnv {
|
||||
public getOptionalInput(_name: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public exportVariable(name: string, value: string): void {
|
||||
this.env.set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,6 +227,7 @@ type AllState = [
|
||||
"Actions",
|
||||
"Api",
|
||||
"FeatureFlags",
|
||||
"FS",
|
||||
];
|
||||
|
||||
/** Initialise a fresh `ActionState<AllState>` value. */
|
||||
@@ -232,11 +240,13 @@ export function initAllState(
|
||||
startedAt: new Date(),
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
osRelease: os.release(),
|
||||
logger: new RecordingLogger(),
|
||||
env,
|
||||
actions: getTestActionsEnv(env),
|
||||
apiClient: github.getOctokit("123"),
|
||||
features: createFeatures([]),
|
||||
fs,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -870,7 +880,7 @@ export function mockLanguagesInRepo(languages: string[]) {
|
||||
*/
|
||||
export const makeVersionInfo = (
|
||||
version: string,
|
||||
features?: { [name: string]: boolean },
|
||||
features?: { [key in ToolsFeature]?: boolean },
|
||||
overlayVersion?: number,
|
||||
): VersionInfo => ({
|
||||
version,
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum ToolsFeature {
|
||||
BundleSupportsOverlay = "bundleSupportsOverlay",
|
||||
IndirectTracingSupportsStaticBinaries = "indirectTracingSupportsStaticBinaries",
|
||||
SuppressesMissingFileBaselineWarning = "suppressesMissingFileBaselineWarning",
|
||||
SwiftSupportsAllPlatforms = "swiftSupportsAllPlatforms",
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import { getConfig } from "./config-utils";
|
||||
import { readDiffRangesJsonFile } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar, exportEnvVar } from "./environment";
|
||||
import { EnvVar } from "./environment";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import * as fingerprints from "./fingerprints";
|
||||
import * as gitUtils from "./git-utils";
|
||||
@@ -126,7 +126,7 @@ async function combineSarifFilesUsingCLI(
|
||||
logger.warning(
|
||||
`Uploading multiple SARIF runs with the same category is deprecated ${deprecationWarningMessage}. Please update your workflow to upload a single run per category. ${deprecationMoreInformationMessage}`,
|
||||
);
|
||||
exportEnvVar("CODEQL_MERGE_SARIF_DEPRECATION_WARNING", "true");
|
||||
core.exportVariable("CODEQL_MERGE_SARIF_DEPRECATION_WARNING", "true");
|
||||
}
|
||||
|
||||
// If not, use the naive method of combining the files.
|
||||
@@ -1032,7 +1032,7 @@ export function validateUniqueCategory(
|
||||
`Category: (${id ? id : "none"}) Tool: (${tool ? tool : "none"})`,
|
||||
);
|
||||
}
|
||||
exportEnvVar(sentinelEnvVar, sentinelEnvVar);
|
||||
core.exportVariable(sentinelEnvVar, sentinelEnvVar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
src/util.ts
39
src/util.ts
@@ -14,23 +14,13 @@ import * as apiCompatibility from "./api-compatibility.json";
|
||||
import type { CodeQL } from "./codeql";
|
||||
import type { Pack } from "./config/db-config";
|
||||
import type { Config } from "./config-utils";
|
||||
import {
|
||||
EnvVar,
|
||||
getRequiredEnvParam,
|
||||
isInTestMode,
|
||||
exportEnvVar,
|
||||
} from "./environment";
|
||||
import { EnvVar, getRequiredEnvParam } from "./environment";
|
||||
import * as json from "./json";
|
||||
import { Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
|
||||
// Re-export for backwards compatibility to avoid updating a lot of imports elsewhere.
|
||||
export {
|
||||
getRequiredEnvParam,
|
||||
getOptionalEnvVar,
|
||||
getEnv,
|
||||
isInTestMode,
|
||||
} from "./environment";
|
||||
export { getRequiredEnvParam, getOptionalEnvVar, getEnv } from "./environment";
|
||||
|
||||
/**
|
||||
* The name of the file containing the base database OIDs, as stored in the
|
||||
@@ -552,7 +542,7 @@ export function checkGitHubVersionInRange(
|
||||
);
|
||||
}
|
||||
hasBeenWarnedAboutVersion = true;
|
||||
exportEnvVar(CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR, true);
|
||||
core.exportVariable(CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR, true);
|
||||
}
|
||||
|
||||
export enum DisallowedAPIVersionReason {
|
||||
@@ -596,11 +586,11 @@ export function assertNever(value: never): never {
|
||||
* knowing what version of CodeQL we're running.
|
||||
*/
|
||||
export function initializeEnvironment(version: string) {
|
||||
exportEnvVar(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
|
||||
exportEnvVar(EnvVar.FEATURE_SANDWICH, "false");
|
||||
exportEnvVar(EnvVar.FEATURE_SARIF_COMBINE, "true");
|
||||
exportEnvVar(EnvVar.FEATURE_WILL_UPLOAD, "true");
|
||||
exportEnvVar(EnvVar.VERSION, version);
|
||||
core.exportVariable(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
|
||||
core.exportVariable(EnvVar.FEATURE_SANDWICH, "false");
|
||||
core.exportVariable(EnvVar.FEATURE_SARIF_COMBINE, "true");
|
||||
core.exportVariable(EnvVar.FEATURE_WILL_UPLOAD, "true");
|
||||
core.exportVariable(EnvVar.VERSION, version);
|
||||
}
|
||||
|
||||
export class HTTPError extends Error {
|
||||
@@ -721,6 +711,15 @@ export function isGoodVersion(versionSpec: string) {
|
||||
return !BROKEN_VERSIONS.includes(versionSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are in test mode. This is used by CodeQL Action PR checks.
|
||||
*
|
||||
* In test mode, we skip several uploads (SARIF results, status reports, DBs, ...).
|
||||
*/
|
||||
export function isInTestMode(): boolean {
|
||||
return process.env[EnvVar.TEST_MODE] === "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we specifically want to skip uploading SARIF files.
|
||||
*/
|
||||
@@ -949,7 +948,7 @@ export async function checkDiskUsage(
|
||||
} else {
|
||||
logger.debug(message);
|
||||
}
|
||||
exportEnvVar(EnvVar.HAS_WARNED_ABOUT_DISK_SPACE, "true");
|
||||
core.exportVariable(EnvVar.HAS_WARNED_ABOUT_DISK_SPACE, "true");
|
||||
}
|
||||
return {
|
||||
numAvailableBytes: diskUsage.bavail * blockSizeInBytes,
|
||||
@@ -998,7 +997,7 @@ export function checkActionVersion(
|
||||
"https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/",
|
||||
);
|
||||
// set LOG_VERSION_DEPRECATION env var to prevent the warning from being logged multiple times
|
||||
exportEnvVar(EnvVar.LOG_VERSION_DEPRECATION, "true");
|
||||
core.exportVariable(EnvVar.LOG_VERSION_DEPRECATION, "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user