Skip to content
284 changes: 148 additions & 136 deletions lib/entry-points.js

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions src/actions-util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
import { computeAutomationID } from "./api-client";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import { RecordingLogger, setupTests } from "./testing-utils";
import { initializeEnvironment, withTmpDir } from "./util";
import { getTestEnv, RecordingLogger, setupTests } from "./testing-utils";
import { withTmpDir } from "./util";

setupTests(test);

Expand Down Expand Up @@ -228,8 +228,9 @@ test.serial(
);

test.serial("initializeEnvironment", (t) => {
initializeEnvironment("1.2.3");
t.deepEqual(process.env[EnvVar.VERSION], "1.2.3");
const env = getTestEnv();
env.initialise("1.2.3");
t.deepEqual(env.getOptional(EnvVar.VERSION), "1.2.3");
});

test.serial("fixCodeQualityCategory", (t) => {
Expand Down
2 changes: 1 addition & 1 deletion src/analyze-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
let databaseUploadResults: DatabaseUploadResult[] = [];

try {
util.initializeEnvironment(actionsUtil.getActionVersion());
action.env.initialise(actionsUtil.getActionVersion());

// Make inputs accessible in the `post` step, details at
// https://github.com/github/codeql-action/issues/2553
Expand Down
4 changes: 2 additions & 2 deletions src/api-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ProxyAgent } from "undici";
import * as actionsUtil from "./actions-util";
import * as api from "./api-client";
import { DO_NOT_RETRY_STATUSES } from "./api-client";
import { ActionsEnvVars, RegistryProxyVars } from "./environment";
import { ActionsEnvVars, RegistryProxyVars, getEnv } from "./environment";
import { GITHUB_DOTCOM_API_URL, GITHUB_DOTCOM_URL } from "./github";
import {
callee,
Expand All @@ -21,7 +21,7 @@ import * as util from "./util";
setupTests(test);

test.beforeEach(() => {
util.initializeEnvironment(actionsUtil.getActionVersion());
getEnv().initialise(actionsUtil.getActionVersion());
});

test.serial("getApiClient", async (t) => {
Expand Down
15 changes: 11 additions & 4 deletions src/autobuild-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getGitHubVersion } from "./api-client";
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import { getCodeQL } from "./codeql";
import { Config, getConfig } from "./config-utils";
import { EnvVar } from "./environment";
import { EnvVar, type Env } from "./environment";
import { Language } from "./languages";
import { Logger } from "./logging";
import {
Expand All @@ -26,7 +26,6 @@ import {
checkDiskUsage,
checkGitHubVersionInRange,
ConfigurationError,
initializeEnvironment,
wrapError,
} from "./util";

Expand All @@ -38,14 +37,15 @@ interface AutobuildStatusReport extends StatusReportBase {
}

async function sendCompletedStatusReport(
env: Env,
config: Config | undefined,
logger: Logger,
startedAt: Date,
allLanguages: string[],
failingLanguage?: string,
cause?: Error,
) {
initializeEnvironment(getActionVersion());
env.initialise(getActionVersion());

const status = getActionsStatus(cause, failingLanguage);
const statusReportBase = await createStatusReportBase(
Expand Down Expand Up @@ -129,6 +129,7 @@ async function run({
`We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. ${error.message}`,
);
await sendCompletedStatusReport(
env,
config,
logger,
startedAt,
Expand All @@ -141,7 +142,13 @@ async function run({

env.export(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");

await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
await sendCompletedStatusReport(
env,
config,
logger,
startedAt,
languages ?? [],
);
}

/** Defines the `autobuild` Action. */
Expand Down
4 changes: 2 additions & 2 deletions src/codeql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import type { Config } from "./config-utils";
import * as defaults from "./defaults.json";
import { DocUrl } from "./doc-url";
import { getEnv } from "./environment";
import { BuiltInLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
import { ToolsSource } from "./setup-codeql";
Expand All @@ -37,14 +38,13 @@ import {
} from "./testing-utils";
import { ToolsDownloadStatusReport } from "./tools-download";
import * as util from "./util";
import { initializeEnvironment } from "./util";

setupTests(test);

let stubConfig: Config;

test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");

stubConfig = createTestConfig({
languages: [BuiltInLanguage.cpp],
Expand Down
51 changes: 28 additions & 23 deletions src/codeql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as path from "path";
import * as core from "@actions/core";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as yaml from "js-yaml";
import * as semver from "semver";

import {
CommandInvocationError,
Expand All @@ -19,6 +20,7 @@ import type { ActionConfig } from "./config/action-config";
import { appendExtraQueryExclusions, type Config } from "./config-utils";
import { DocUrl } from "./doc-url";
import { EnvVar, getEnv, exportEnvVar } from "./environment";
import { getErrorMessage, isDiskConfigurationError } from "./errors";
import {
CodeQLDefaultVersionInfo,
Feature,
Expand All @@ -34,7 +36,9 @@ import { ToolsDownloadStatusReport } from "./tools-download";
import { ToolsFeature, isSupportedToolsFeature } from "./tools-features";
import { shouldEnableIndirectTracing } from "./tracer-config";
import * as util from "./util";
import { BuildMode, CleanupLevel, getErrorMessage } from "./util";
import { BuildMode, CleanupLevel } from "./util";

export { isDiskConfigurationError } from "./errors";

type Options = Array<string | number | boolean>;

Expand Down Expand Up @@ -70,6 +74,11 @@ export interface CodeQL {
* Get a string containing the semver version of the CodeQL executable.
*/
getVersion(): Promise<VersionInfo>;
/**
* Determines whether the CLI installation represented by this {@link CodeQL} instance
* has at least the {@link requiredVersion}.
*/
isVersionAtLeast(requiredVersion: string): Promise<boolean>;
/**
* Print version information about CodeQL.
*/
Expand Down Expand Up @@ -274,26 +283,6 @@ const GHES_MOST_RECENT_DEPRECATION_DATE = "2026-09-24";
/** The CLI verbosity level to use for extraction in debug mode. */
const EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++";

/**
* Decides whether `e` is a disk-related error outside of our control
* that should be classified as a `ConfigurationError`.
*
* @param e The error to check.
* @returns True if the error should be treated as a `ConfigurationError` or false if not.
*/
export function isDiskConfigurationError(e: unknown): boolean {
if (!(e instanceof Error)) {
return false;
}

return (
// out of disk space
e.message.includes("ENOSPC") ||
// access denied
e.message.includes("EACCES")
);
}

/**
* Set up CodeQL CLI access.
*
Expand Down Expand Up @@ -431,6 +420,19 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
getVersion: resolveFunction(partialCodeql, "getVersion", async () => ({
version: "1.0.0",
})),
isVersionAtLeast: resolveFunction(
partialCodeql,
"codeQlVersionAtLeast",
async (requiredVersion) => {
return (
!!partialCodeql.getVersion &&
semver.gte(
(await partialCodeql.getVersion()).version,
requiredVersion,
)
);
},
),
printVersion: resolveFunction(partialCodeql, "printVersion"),
supportsFeature: resolveFunction(
partialCodeql,
Expand Down Expand Up @@ -534,6 +536,9 @@ async function getCodeQLForCmd(
}
return result;
},
async isVersionAtLeast(requiredVersion: string) {
return semver.gte((await this.getVersion()).version, requiredVersion);
},
async printVersion() {
// Reuse the cached version information rather than invoking the CLI again.
core.info(JSON.stringify(await this.getVersion(), null, 2));
Expand Down Expand Up @@ -1028,7 +1033,7 @@ async function getCodeQLForCmd(
// CodeQL object is created.
if (
checkVersion &&
!(await util.codeQlVersionAtLeast(codeql, CODEQL_MINIMUM_VERSION))
!(await codeql.isVersionAtLeast(CODEQL_MINIMUM_VERSION))
) {
throw new util.ConfigurationError(
`Expected a CodeQL CLI with version at least ${CODEQL_MINIMUM_VERSION} but got version ${
Expand All @@ -1038,7 +1043,7 @@ async function getCodeQLForCmd(
} else if (
checkVersion &&
process.env[EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING] !== "true" &&
!(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION))
!(await codeql.isVersionAtLeast(CODEQL_NEXT_MINIMUM_VERSION))
) {
const result = await codeql.getVersion();
core.warning(
Expand Down
8 changes: 2 additions & 6 deletions src/config-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ import {
GitHubVersion,
ConfigurationError,
BuildMode,
codeQlVersionAtLeast,
cloneObject,
isDefined,
checkDiskUsage,
Expand Down Expand Up @@ -652,10 +651,7 @@ async function runnerHasSufficientMemory(
logger: Logger,
): Promise<boolean> {
if (
await codeQlVersionAtLeast(
codeql,
CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE,
)
await codeql.isVersionAtLeast(CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE)
) {
logger.debug(
`Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.`,
Expand Down Expand Up @@ -899,7 +895,7 @@ async function validateOverlayDatabaseMode(
);
return new Failure(OverlayDisabledReason.IncompatibleBuildMode);
}
if (!(await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION))) {
if (!(await codeql.isVersionAtLeast(CODEQL_OVERLAY_MINIMUM_VERSION))) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because ` +
`the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. ` +
Expand Down
11 changes: 3 additions & 8 deletions src/database-upload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
cleanupAndUploadDatabases,
uploadBundledDatabase,
} from "./database-upload";
import { getEnv } from "./environment";
import { Feature } from "./feature-flags";
import * as gitUtils from "./git-utils";
import { BuiltInLanguage } from "./languages";
Expand All @@ -31,18 +32,12 @@ import {
setupActionsVars,
setupTests,
} from "./testing-utils";
import {
CleanupLevel,
GitHubVariant,
HTTPError,
initializeEnvironment,
withTmpDir,
} from "./util";
import { CleanupLevel, GitHubVariant, HTTPError, withTmpDir } from "./util";

setupTests(test);

test.beforeEach(() => {
initializeEnvironment("1.2.3");
getEnv().initialise("1.2.3");
});

const testRepoName: RepositoryNwo = { owner: "github", repo: "example" };
Expand Down
30 changes: 20 additions & 10 deletions src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,16 @@ export function getOptionalEnvVar(paramName: string): string | undefined {
* in tests, while clearly signalling in regular code that the consumer of the `ReadOnlyEnv` instance
* will only read from it.
*/
export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
constructor(protected readonly vars: Record<string, T>) {}
export class ReadOnlyEnv {
constructor(protected readonly vars: Record<string, string | undefined>) {}

/** Clones the object while detaching the underlying environment from the original. */
public clone(): this {
return Object.create(this, { vars: { value: { ...this.vars } } }) as this;
}

/** Gets a copy of the underlying environment. */
public get(): Record<string, T> {
public get(): Record<string, string | undefined> {
return { ...this.vars };
}

Expand All @@ -305,7 +305,7 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
}

/** Gets the entries of the underlying `ProcessEnv`. */
public entries(): Array<[string, T]> {
public entries(): Array<[string, string | undefined]> {
return Object.entries(this.vars);
}

Expand Down Expand Up @@ -335,19 +335,17 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
* Use `ReadOnlyEnv` instead if you only plan to read from the environment.
* This type allows writing to the environment.
*/
export class Env<
T extends string | undefined = string | undefined,
> extends ReadOnlyEnv<T> {
export class Env extends ReadOnlyEnv {
private changed: boolean = false;

/** Sets an environment variable. */
public set(name: string, value: T): void {
public set(name: string, value: string): void {
this.vars[name] = value;
this.changed = true;
}

/** Sets all environment variables given by `vars`. */
public setAll(vars: Record<string, T>): void {
public setAll(vars: Record<string, string>): void {
for (const [key, val] of Object.entries(vars)) {
this.set(key, val);
}
Expand All @@ -361,7 +359,7 @@ export class Env<
* @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 {
public export(name: string, val: string): 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.
Expand All @@ -373,6 +371,18 @@ export class Env<
}
}

/**
* Set some initial environment variables that we can set even without
* knowing what version of CodeQL we're running.
*/
public initialise(version: string) {
this.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
this.export(EnvVar.FEATURE_SANDWICH, "false");
this.export(EnvVar.FEATURE_SARIF_COMBINE, "true");
this.export(EnvVar.FEATURE_WILL_UPLOAD, "true");
this.export(EnvVar.VERSION, version);
}

/** Gets a value indicating whether `set` was called at least once. */
public hasChanged(): boolean {
return this.changed;
Expand Down
Loading
Loading