mirror of
https://github.com/github/codeql-action.git
synced 2026-10-04 01:45:16 +00:00
Compare commits
34 Commits
v3
...
mbg/improv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2215db9ae | ||
|
|
5769ffc476 | ||
|
|
7b6a151fb5 | ||
|
|
29b01b6799 | ||
|
|
fa2bea7c7a | ||
|
|
9fb2fa5f46 | ||
|
|
054b25e1c2 | ||
|
|
fa8392b7e5 | ||
|
|
38c1d74ffa | ||
|
|
19aa55de51 | ||
|
|
a7334dd080 | ||
|
|
aa2cb993a1 | ||
|
|
9c970806c4 | ||
|
|
b5f938c947 | ||
|
|
73dc34459c | ||
|
|
266e866100 | ||
|
|
6aa6d5553a | ||
|
|
f42df5b5c1 | ||
|
|
72509a81ba | ||
|
|
fa4712895b | ||
|
|
660f7c5f24 | ||
|
|
fe0a932a6a | ||
|
|
8e36092a16 | ||
|
|
2294a7a8b1 | ||
|
|
c0369726ee | ||
|
|
a5c2338ec0 | ||
|
|
f042742baf | ||
|
|
a876e23075 | ||
|
|
6fb4a88ed6 | ||
|
|
f5716fadf4 | ||
|
|
54a43494ca | ||
|
|
ba615b5d9d | ||
|
|
5dab0ccf51 | ||
|
|
99fe83948e |
@@ -2,6 +2,10 @@
|
||||
|
||||
See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs.
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.38.2 - 24 Sept 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.27.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.1). [#4160](https://github.com/github/codeql-action/pull/4160)
|
||||
|
||||
1349
lib/entry-points.js
generated
1349
lib/entry-points.js
generated
File diff suppressed because it is too large
Load Diff
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.38.2",
|
||||
"version": "4.38.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.38.2",
|
||||
"version": "4.38.3",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"pr-checks"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.38.2",
|
||||
"version": "4.38.3",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getCodeQLDatabasePath,
|
||||
ConfigurationError,
|
||||
getEnv,
|
||||
getErrorMessage,
|
||||
} from "./util";
|
||||
|
||||
/**
|
||||
@@ -94,7 +95,7 @@ export function getActionVersion(): string {
|
||||
*
|
||||
* This will be "dynamic" for default setup workflow runs.
|
||||
*/
|
||||
export function getWorkflowEventName(env: Env = getEnv()) {
|
||||
export function getWorkflowEventName(env: ReadOnlyEnv = getEnv()) {
|
||||
return env.getRequired(ActionsEnvVars.GITHUB_EVENT_NAME);
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ function getRelativeScriptPath(env: Env): string {
|
||||
}
|
||||
|
||||
/** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */
|
||||
export function getWorkflowEvent(env: Env = getEnv()): any {
|
||||
export function getWorkflowEvent(env: ReadOnlyEnv = getEnv()): any {
|
||||
const eventJsonFile = env.getRequired(ActionsEnvVars.GITHUB_EVENT_PATH);
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8"));
|
||||
@@ -412,14 +413,23 @@ export const persistInputs = function (env: Env = getEnv()) {
|
||||
/**
|
||||
* Restores all inputs to the action from the persisted state.
|
||||
*/
|
||||
export const restoreInputs = function () {
|
||||
const persistedInputs = core.getState(persistedInputsKey);
|
||||
if (persistedInputs) {
|
||||
for (const [name, value] of JSON.parse(persistedInputs)) {
|
||||
process.env[name] = value;
|
||||
export function restoreInputs(logger: Logger) {
|
||||
try {
|
||||
const persistedInputsValue = core.getState(persistedInputsKey);
|
||||
if (persistedInputsValue) {
|
||||
const persistedInputs = JSON.parse(persistedInputsValue);
|
||||
|
||||
for (const [name, value] of persistedInputs) {
|
||||
process.env[name] = value;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Unable to restore inputs: ${getErrorMessage(err)}`);
|
||||
throw new Error(
|
||||
"Failed to restore inputs from the state set by this action's main execution.",
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface PullRequestBranches {
|
||||
base: string;
|
||||
|
||||
@@ -25,8 +25,8 @@ export async function runWrapper() {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
try {
|
||||
actionsUtil.restoreInputs();
|
||||
const logger = getActionsLogger();
|
||||
actionsUtil.restoreInputs(logger);
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ test.serial(
|
||||
requiredInputStub.withArgs("token").returns("fake-token");
|
||||
requiredInputStub.withArgs("upload-database").returns("false");
|
||||
requiredInputStub.withArgs("output").returns("out");
|
||||
requiredInputStub.withArgs("checkout_path").returns("");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("expect-error").returns("false");
|
||||
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
|
||||
@@ -104,6 +105,7 @@ test.serial(
|
||||
requiredInputStub.withArgs("token").returns("fake-token");
|
||||
requiredInputStub.withArgs("upload-database").returns("false");
|
||||
requiredInputStub.withArgs("output").returns("out");
|
||||
requiredInputStub.withArgs("checkout_path").returns("");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("expect-error").returns("false");
|
||||
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as analyses from "./analyses";
|
||||
import {
|
||||
CodeQLAnalysisError,
|
||||
dbIsFinalized,
|
||||
determineCheckoutPath,
|
||||
QueriesStatusReport,
|
||||
runFinalize,
|
||||
runQueries,
|
||||
@@ -212,13 +213,11 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
|
||||
await runAutobuild(config, BuiltInLanguage.go, logger);
|
||||
}
|
||||
|
||||
async function run({
|
||||
startedAt,
|
||||
logger,
|
||||
actions,
|
||||
}: ActionState<["Base", "Logger", "Actions"]>) {
|
||||
async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
const startedAt = action.startedAt;
|
||||
const logger = action.logger;
|
||||
|
||||
let uploadResults:
|
||||
| Partial<Record<analyses.AnalysisKind, UploadResult>>
|
||||
@@ -311,7 +310,7 @@ async function run({
|
||||
logger,
|
||||
);
|
||||
|
||||
const checkoutPath = actions.getRequiredInput("checkout_path");
|
||||
const checkoutPath = await determineCheckoutPath(action, config);
|
||||
|
||||
// Setup diff informed analysis if needed (based on whether init created the file)
|
||||
const diffRangePackDir = await setupDiffInformedQueryRun(
|
||||
@@ -407,7 +406,7 @@ async function run({
|
||||
// Note: Take care with the ordering of this call since databases may be cleaned up
|
||||
// at the `overlay` or `clear` level.
|
||||
databaseUploadResults = await cleanupAndUploadDatabases(
|
||||
{ logger, features },
|
||||
{ ...action, features },
|
||||
repositoryNwo,
|
||||
codeql,
|
||||
config,
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
resolveQuerySuiteAlias,
|
||||
addSarifExtension,
|
||||
diffRangeExtensionPackContents,
|
||||
determineCheckoutPath,
|
||||
} from "./analyze";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
@@ -21,12 +23,98 @@ import {
|
||||
setupActionsVars,
|
||||
createFeatures,
|
||||
createTestConfig,
|
||||
callee,
|
||||
} from "./testing-utils";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - logs when checkout_path is not in a work tree",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({}));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(undefined);
|
||||
|
||||
await target
|
||||
.logs(t, "is not in the work tree of a git repository")
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - logs when checkout_path is not a repo root",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({}));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves("/checkout");
|
||||
|
||||
await target
|
||||
.logs(t, "is not the root of the repository")
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - logs when checkout_path is not the same as repo root in config",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({ repositoryRoot: "/some/other/path" }));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(expectedPath);
|
||||
|
||||
await target
|
||||
.logs(t, "does not match that found by the 'codeql-action/init' step")
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - doesn't log any of the messages when all is as expected",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({ repositoryRoot: expectedPath }));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(expectedPath);
|
||||
|
||||
await target
|
||||
.notLogs(
|
||||
t,
|
||||
"is not in the work tree of a git repository",
|
||||
"is not the root of the repository",
|
||||
"does not match that found by the 'codeql-action/init' step",
|
||||
)
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks the status report produced by the analyze Action.
|
||||
*
|
||||
|
||||
@@ -5,6 +5,7 @@ import { performance } from "perf_hooks";
|
||||
import * as io from "@actions/io";
|
||||
import * as yaml from "js-yaml";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
import { getTemporaryDirectory } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import { setupCppAutobuild } from "./autobuild";
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
} from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { FeatureEnablement, Feature } from "./feature-flags";
|
||||
import { getGitRoot } from "./git-utils";
|
||||
import { BuiltInLanguage, Language } from "./languages";
|
||||
import { Logger, withGroupAsync } from "./logging";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
@@ -85,6 +87,58 @@ export interface QueriesStatusReport
|
||||
event_reports?: EventReport[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the path at which the repository being analysed is checked out at.
|
||||
* Returns the value of the required `checkout_path` input and validates that it
|
||||
* refers to the root of a repository.
|
||||
*
|
||||
* @param action The action state.
|
||||
* @param config The CodeQL Action configuration state.
|
||||
*/
|
||||
export async function determineCheckoutPath(
|
||||
action: ActionState<["Logger", "Actions"]>,
|
||||
config: configUtils.Config,
|
||||
) {
|
||||
const checkoutPathInput = action.actions.getRequiredInput("checkout_path");
|
||||
|
||||
// Try to obtain the root path of the repository and validate that it matches the input.
|
||||
const absCheckoutPathInput = path.resolve(checkoutPathInput);
|
||||
const repositoryRoot = await getGitRoot(absCheckoutPathInput);
|
||||
|
||||
if (repositoryRoot === undefined) {
|
||||
action.logger.warning(
|
||||
[
|
||||
`The directory at '${absCheckoutPathInput}' is not in the work tree of a git repository.`,
|
||||
"If the repository being analyzed is checked out elsewhere,",
|
||||
"you must explicitly set the 'checkout_path' input for the 'codeql-action/analyze' step to",
|
||||
"the checkout path.",
|
||||
].join(" "),
|
||||
);
|
||||
} else if (repositoryRoot !== absCheckoutPathInput) {
|
||||
action.logger.warning(
|
||||
[
|
||||
`The directory at '${absCheckoutPathInput}' is not the root of the repository ('${repositoryRoot}').`,
|
||||
"Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout.",
|
||||
].join(" "),
|
||||
);
|
||||
} else if (
|
||||
config.repositoryRoot !== undefined &&
|
||||
repositoryRoot !== config.repositoryRoot
|
||||
) {
|
||||
// The repository root that was persisted by the `init` step doesn't match the one we have found here.
|
||||
action.logger.warning(
|
||||
[
|
||||
`The repository path at '${repositoryRoot}' does not match that found by the 'codeql-action/init' step: '${config.repositoryRoot}'.`,
|
||||
"Ensure that the 'checkout_path' input for the 'codeql-action/analyze' step is set to the path of the same repository that",
|
||||
"the 'codeql-action/init' step determined. This is either the GitHub Actions workspace or the repository root corresponding to",
|
||||
"the 'source-root' input if that was provided.",
|
||||
].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
return absCheckoutPathInput;
|
||||
}
|
||||
|
||||
async function setupPythonExtractor(logger: Logger) {
|
||||
const codeqlPython = process.env["CODEQL_PYTHON"];
|
||||
if (codeqlPython === undefined || codeqlPython.length === 0) {
|
||||
|
||||
@@ -249,9 +249,10 @@ export async function getGitHubVersionFromApi(
|
||||
*/
|
||||
export async function getGitHubVersion(): Promise<GitHubVersion> {
|
||||
if (cachedGitHubVersion === undefined) {
|
||||
const apiDetails = getApiDetails();
|
||||
cachedGitHubVersion = await getGitHubVersionFromApi(
|
||||
getApiClient(),
|
||||
getApiDetails(),
|
||||
createApiClientWithDetails(apiDetails),
|
||||
apiDetails,
|
||||
);
|
||||
}
|
||||
return cachedGitHubVersion;
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
import test from "ava";
|
||||
|
||||
import { getCodeQLBundleFromUrl } from "./codeql-bundle";
|
||||
import { getCodeQLBundleFromUrl, getCodeQLBundleName } from "./codeql-bundle";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { BundlePlatform } from "./platform";
|
||||
|
||||
test("getCodeQLBundleName returns a per-language bundle name only when a language is specified", (t) => {
|
||||
t.is(
|
||||
getCodeQLBundleName("zstd", BundlePlatform.Linux64, BuiltInLanguage.java),
|
||||
"codeql-bundle-java-linux64.tar.zst",
|
||||
);
|
||||
t.is(
|
||||
getCodeQLBundleName("zstd", BundlePlatform.Linux64),
|
||||
"codeql-bundle-linux64.tar.zst",
|
||||
);
|
||||
});
|
||||
|
||||
test("getCodeQLBundleName names the Swift bundle for macOS", (t) => {
|
||||
t.is(
|
||||
getCodeQLBundleName("zstd", BundlePlatform.Osx64, BuiltInLanguage.swift),
|
||||
"codeql-bundle-swift-osx64.tar.zst",
|
||||
);
|
||||
});
|
||||
|
||||
for (const [assetName, language] of [
|
||||
["codeql-bundle-java-linux64.tar.zst", BuiltInLanguage.java],
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
|
||||
import { BundlePlatform } from "./platform";
|
||||
import type { CompressionMethod } from "./tar";
|
||||
|
||||
/** Describes the contents and location of a downloadable CodeQL bundle. */
|
||||
export type CodeQLBundle =
|
||||
@@ -11,6 +13,42 @@ export type CodeQLBundle =
|
||||
combinedBundleURL?: string;
|
||||
};
|
||||
|
||||
/** A resolved download, including its bundle identity and version. */
|
||||
export interface CodeQLDownloadSource {
|
||||
/** Distinguishes downloads from local archives and cached installations. */
|
||||
sourceType: "download";
|
||||
/** The bundle to download. */
|
||||
bundle: CodeQLBundle;
|
||||
/** The compression format of the bundle archive. */
|
||||
compressionMethod: CompressionMethod;
|
||||
/** Bundle version of the tools, if known. */
|
||||
bundleVersion?: string;
|
||||
/** Requested CLI version, if known. */
|
||||
cliVersion?: string;
|
||||
/** Resolved version for telemetry, independent of whether the bundle can be cached. */
|
||||
toolsVersion: string;
|
||||
}
|
||||
|
||||
/** Returns the exact bundle asset name for a platform and optional language. */
|
||||
export function getCodeQLBundleName(
|
||||
compressionMethod: CompressionMethod,
|
||||
platform: BundlePlatform | undefined,
|
||||
language?: BuiltInLanguage,
|
||||
): string {
|
||||
const extensions: Record<CompressionMethod, string> = {
|
||||
gzip: ".tar.gz",
|
||||
zstd: ".tar.zst",
|
||||
};
|
||||
const extension = extensions[compressionMethod];
|
||||
if (platform === undefined) {
|
||||
return `codeql-bundle${extension}`;
|
||||
}
|
||||
if (language !== undefined) {
|
||||
return `codeql-bundle-${language}-${platform}${extension}`;
|
||||
}
|
||||
return `codeql-bundle-${platform}${extension}`;
|
||||
}
|
||||
|
||||
const PER_LANGUAGE_BUNDLE_NAME =
|
||||
/^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/;
|
||||
|
||||
|
||||
@@ -952,7 +952,13 @@ async function getCodeQLForCmd(
|
||||
},
|
||||
},
|
||||
).exec();
|
||||
return JSON.parse(extractorPath) as string;
|
||||
try {
|
||||
return JSON.parse(extractorPath) as string;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to parse extractor path for '${language}' from CLI: ${getErrorMessage(err)}\nOutput was: ${extractorPath}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
async resolveQueriesStartingPacks(queries: string[]): Promise<string[]> {
|
||||
const codeqlArgs = [
|
||||
@@ -1213,7 +1219,7 @@ export async function getTrapCachingExtractorConfigArgsForLang(
|
||||
): Promise<string[]> {
|
||||
const cacheDir = config.trapCaches[language];
|
||||
if (cacheDir === undefined) return [];
|
||||
const write = await isAnalyzingDefaultBranch();
|
||||
const write = await isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot);
|
||||
return [
|
||||
`-O=${language}.trap.cache.dir=${cacheDir}`,
|
||||
`-O=${language}.trap.cache.bound=${TRAP_CACHE_SIZE_MB}`,
|
||||
|
||||
@@ -172,6 +172,7 @@ test.serial("load empty config", async (t) => {
|
||||
createTestInitConfigInputs({
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
sourceRoot: tempDir,
|
||||
tempDir,
|
||||
codeql,
|
||||
logger,
|
||||
@@ -186,6 +187,7 @@ test.serial("load empty config", async (t) => {
|
||||
logger,
|
||||
}),
|
||||
{},
|
||||
undefined,
|
||||
);
|
||||
|
||||
t.deepEqual(config, expectedConfig);
|
||||
@@ -216,6 +218,7 @@ test.serial("load code quality config", async (t) => {
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
sourceRoot: tempDir,
|
||||
tempDir,
|
||||
codeql,
|
||||
logger,
|
||||
@@ -296,6 +299,7 @@ test.serial(
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
sourceRoot: tempDir,
|
||||
tempDir,
|
||||
codeql,
|
||||
repositoryProperties,
|
||||
@@ -512,6 +516,7 @@ test.serial("load non-empty input", async (t) => {
|
||||
// And the config we expect it to parse to
|
||||
const expectedConfig = createTestConfig({
|
||||
languages: [BuiltInLanguage.javascript],
|
||||
repositoryRoot: undefined,
|
||||
buildMode: BuildMode.None,
|
||||
originalUserInput: userConfig,
|
||||
computedConfig: userConfig,
|
||||
@@ -532,6 +537,7 @@ test.serial("load non-empty input", async (t) => {
|
||||
state,
|
||||
createTestInitConfigInputs({
|
||||
languagesInput,
|
||||
sourceRoot: tempDir,
|
||||
buildModeInput: "none",
|
||||
configFile: configFilePath,
|
||||
debugArtifactName: "my-artifact",
|
||||
@@ -1092,11 +1098,6 @@ const checkOverlayEnablementMacro = makeMacro({
|
||||
return lang === BuiltInLanguage.java;
|
||||
});
|
||||
|
||||
// Mock git root detection
|
||||
if (setup.gitRoot !== undefined) {
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(setup.gitRoot);
|
||||
}
|
||||
|
||||
// Mock submodule detection
|
||||
sinon.stub(gitUtils, "hasSubmodules").returns(setup.hasSubmodules);
|
||||
|
||||
@@ -1109,6 +1110,7 @@ const checkOverlayEnablementMacro = makeMacro({
|
||||
codeql,
|
||||
features,
|
||||
setup.languages,
|
||||
setup.gitRoot, // repositoryRoot
|
||||
tempDir, // sourceRoot
|
||||
setup.buildMode,
|
||||
undefined,
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { EnvVar, getEnv } from "./environment";
|
||||
import * as errorMessages from "./error-messages";
|
||||
import { Feature, FeatureEnablement, FeatureWithoutCLI } from "./feature-flags";
|
||||
import {
|
||||
@@ -385,6 +385,7 @@ export async function initActionState(
|
||||
enableFileCoverageInformation,
|
||||
}: InitConfigInputs,
|
||||
userConfig: UserConfig,
|
||||
repositoryRoot: string | undefined,
|
||||
): Promise<Config> {
|
||||
const languages = await getLanguages(
|
||||
codeql,
|
||||
@@ -436,6 +437,7 @@ export async function initActionState(
|
||||
|
||||
return {
|
||||
version: getActionVersion(),
|
||||
repositoryRoot,
|
||||
analysisKinds,
|
||||
languages,
|
||||
buildMode,
|
||||
@@ -465,12 +467,18 @@ async function downloadCacheWithTime(
|
||||
codeQL: CodeQL,
|
||||
languages: Language[],
|
||||
logger: Logger,
|
||||
repositoryRoot: string | undefined,
|
||||
): Promise<{
|
||||
trapCaches: { [language: string]: string };
|
||||
trapCacheDownloadTime: number;
|
||||
}> {
|
||||
const start = performance.now();
|
||||
const trapCaches = await downloadTrapCaches(codeQL, languages, logger);
|
||||
const trapCaches = await downloadTrapCaches(
|
||||
codeQL,
|
||||
languages,
|
||||
logger,
|
||||
repositoryRoot,
|
||||
);
|
||||
const trapCacheDownloadTime = performance.now() - start;
|
||||
return { trapCaches, trapCacheDownloadTime };
|
||||
}
|
||||
@@ -718,6 +726,7 @@ export async function checkOverlayEnablement(
|
||||
codeql: CodeQL,
|
||||
features: FeatureEnablement,
|
||||
languages: Language[],
|
||||
repositoryRoot: string | undefined,
|
||||
sourceRoot: string,
|
||||
buildMode: BuildMode | undefined,
|
||||
ramInput: string | undefined,
|
||||
@@ -747,6 +756,7 @@ export async function checkOverlayEnablement(
|
||||
true,
|
||||
codeql,
|
||||
languages,
|
||||
repositoryRoot,
|
||||
sourceRoot,
|
||||
buildMode,
|
||||
gitVersion,
|
||||
@@ -820,7 +830,7 @@ export async function checkOverlayEnablement(
|
||||
`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
"with caching because we are analyzing a pull request.",
|
||||
);
|
||||
} else if (await isAnalyzingDefaultBranch()) {
|
||||
} else if (await isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) {
|
||||
overlayDatabaseMode = OverlayDatabaseMode.OverlayBase;
|
||||
logger.info(
|
||||
`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
@@ -836,6 +846,7 @@ export async function checkOverlayEnablement(
|
||||
false,
|
||||
codeql,
|
||||
languages,
|
||||
repositoryRoot,
|
||||
sourceRoot,
|
||||
buildMode,
|
||||
gitVersion,
|
||||
@@ -855,6 +866,7 @@ async function validateOverlayDatabaseMode(
|
||||
overlayModeSetExplicitly: boolean,
|
||||
codeql: CodeQL,
|
||||
languages: Language[],
|
||||
repositoryRoot: string | undefined,
|
||||
sourceRoot: string,
|
||||
buildMode: BuildMode | undefined,
|
||||
gitVersion: GitVersionInfo | undefined,
|
||||
@@ -890,8 +902,7 @@ async function validateOverlayDatabaseMode(
|
||||
);
|
||||
return new Failure(OverlayDisabledReason.IncompatibleCodeQl);
|
||||
}
|
||||
const gitRoot = await getGitRoot(sourceRoot);
|
||||
if (gitRoot === undefined) {
|
||||
if (repositoryRoot === undefined) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`the source root "${sourceRoot}" is not inside a git repository. ` +
|
||||
@@ -899,7 +910,7 @@ async function validateOverlayDatabaseMode(
|
||||
);
|
||||
return new Failure(OverlayDisabledReason.NoGitRoot);
|
||||
}
|
||||
if (hasSubmodules(gitRoot)) {
|
||||
if (hasSubmodules(repositoryRoot)) {
|
||||
if (gitVersion === undefined) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
@@ -1160,9 +1171,11 @@ export async function initConfig(
|
||||
const { logger, features } = actionState;
|
||||
const { tempDir } = inputs;
|
||||
|
||||
const repositoryRoot = await getGitRoot(inputs.sourceRoot);
|
||||
|
||||
const userConfig = await determineUserConfig(actionState, tempDir, inputs);
|
||||
|
||||
const config = await initActionState(inputs, userConfig);
|
||||
const config = await initActionState(inputs, userConfig, repositoryRoot);
|
||||
|
||||
// If Code Quality analysis is the only enabled analysis kind, then we will initialise
|
||||
// the database for Code Quality. That entails disabling the default queries and only
|
||||
@@ -1244,6 +1257,7 @@ export async function initConfig(
|
||||
inputs.codeql,
|
||||
inputs.features,
|
||||
config.languages,
|
||||
repositoryRoot,
|
||||
inputs.sourceRoot,
|
||||
config.buildMode,
|
||||
inputs.ramInput,
|
||||
@@ -1297,6 +1311,7 @@ export async function initConfig(
|
||||
inputs.codeql,
|
||||
config.languages,
|
||||
logger,
|
||||
repositoryRoot,
|
||||
);
|
||||
config.trapCaches = trapCaches;
|
||||
config.trapCacheDownloadTime = trapCacheDownloadTime;
|
||||
@@ -1425,7 +1440,12 @@ export async function getConfig(
|
||||
}
|
||||
if (config.version !== getActionVersion()) {
|
||||
throw new ConfigurationError(
|
||||
`Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'`,
|
||||
[
|
||||
`Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'.`,
|
||||
"All steps in a workflow that use `github/codeql-action` must use the same version to work correctly.",
|
||||
"If you are using Dependabot to manage dependency updates, you can configure a dependency group to update all `github/codeql-action` steps at the same time.",
|
||||
"For more information, see https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#groups--",
|
||||
].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface Config {
|
||||
* The version of the CodeQL Action that the configuration is for.
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* The path at which the repository being analysed is checked out at, if available.
|
||||
* Persisted in the CodeQL Action configuration state, so that we can consult it in later workflow steps.
|
||||
*/
|
||||
repositoryRoot: string | undefined;
|
||||
/**
|
||||
* Set of analysis kinds that are enabled.
|
||||
*/
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface DatabaseUploadResult {
|
||||
}
|
||||
|
||||
export async function cleanupAndUploadDatabases(
|
||||
action: ActionState<["Logger", "FeatureFlags"]>,
|
||||
action: ActionState<["ReadOnlyEnv", "Logger", "FeatureFlags"]>,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
@@ -81,7 +81,7 @@ export async function cleanupAndUploadDatabases(
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch(action.env, checkoutPath))) {
|
||||
// We only want to upload a database if we are analyzing the default branch.
|
||||
logger.debug("Not analyzing default branch. Skipping upload.");
|
||||
return [];
|
||||
@@ -113,7 +113,7 @@ export async function cleanupAndUploadDatabases(
|
||||
includeDiagnostics: false,
|
||||
});
|
||||
bundledDbSize = fs.statSync(bundledDb).size;
|
||||
const commitOid = await gitUtils.getCommitOid(checkoutPath);
|
||||
const commitOid = await gitUtils.getCommitOid(action.env, checkoutPath);
|
||||
// Upload with manual retry logic. We disable Octokit's built-in retries
|
||||
// because the request body is a ReadStream, which can only be consumed
|
||||
// once.
|
||||
|
||||
@@ -307,6 +307,13 @@ export class Env<
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
/** Sets all environment variables given by `vars`. */
|
||||
public setAll(vars: Record<string, T>): void {
|
||||
for (const [key, val] of Object.entries(vars)) {
|
||||
this.set(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a value indicating whether `set` was called at least once. */
|
||||
public hasChanged(): boolean {
|
||||
return this.changed;
|
||||
|
||||
@@ -7,35 +7,42 @@ import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { setupActionsVars, setupTests } from "./testing-utils";
|
||||
import { getTestEnv, setupActionsVars, setupTests } from "./testing-utils";
|
||||
import { withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial("getRef() throws on the empty string", async (t) => {
|
||||
process.env["GITHUB_REF"] = "";
|
||||
await t.throwsAsync(gitUtils.getRef);
|
||||
test("getRef() throws on the empty string", async (t) => {
|
||||
const env = getTestEnv({ [ActionsEnvVars.GITHUB_REF]: "" });
|
||||
await t.throwsAsync(() => gitUtils.getRef(env, ""));
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"getRef() returns merge PR ref if GITHUB_SHA still checked out",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_SHA]: currentSha,
|
||||
});
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(sinon.match.string, "HEAD").resolves(currentSha);
|
||||
callback
|
||||
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
|
||||
.resolves(currentSha);
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
|
||||
t.is(callback.callCount, 1);
|
||||
t.true(callback.calledOnceWith(tmpDir, "HEAD"));
|
||||
t.true(callback.calledOnceWith(env, tmpDir, "HEAD"));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -44,24 +51,33 @@ test.serial(
|
||||
"getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = "b".repeat(40);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_SHA]: "b".repeat(40),
|
||||
});
|
||||
const sha = "a".repeat(40);
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback
|
||||
.withArgs(sinon.match.string, "refs/remotes/pull/1/merge")
|
||||
.withArgs(
|
||||
sinon.match.any,
|
||||
sinon.match.string,
|
||||
"refs/remotes/pull/1/merge",
|
||||
)
|
||||
.resolves(sha);
|
||||
callback
|
||||
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
|
||||
.resolves(sha);
|
||||
callback.withArgs(sinon.match.any, "HEAD").resolves(sha);
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
|
||||
t.is(callback.callCount, 2);
|
||||
t.true(callback.calledWith(tmpDir, "HEAD"));
|
||||
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
|
||||
t.true(callback.calledWith(env, tmpDir, "HEAD"));
|
||||
t.true(callback.calledWith(env, tmpDir, "refs/remotes/pull/1/merge"));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -70,23 +86,28 @@ test.serial(
|
||||
"getRef() returns head PR ref if GITHUB_REF no longer checked out",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge",
|
||||
[ActionsEnvVars.GITHUB_SHA]: "a".repeat(40),
|
||||
});
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback
|
||||
.withArgs(tmpDir, "refs/remotes/pull/1/merge")
|
||||
.withArgs(sinon.match.any, tmpDir, "refs/remotes/pull/1/merge")
|
||||
.resolves("a".repeat(40));
|
||||
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
|
||||
callback
|
||||
.withArgs(sinon.match.any, tmpDir, "HEAD")
|
||||
.resolves("b".repeat(40));
|
||||
callback.throws(new Error("Unexpected getCommitOid call in test."));
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, "refs/pull/1/head");
|
||||
|
||||
t.is(callback.callCount, 2);
|
||||
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
|
||||
t.true(callback.calledWith(tmpDir, "HEAD"));
|
||||
t.true(callback.calledWith(env, tmpDir, "refs/remotes/pull/1/merge"));
|
||||
t.true(callback.calledWith(env, tmpDir, "HEAD"));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -95,7 +116,6 @@ test.serial(
|
||||
"getRef() returns ref provided as an input and ignores current HEAD",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
@@ -104,14 +124,22 @@ test.serial(
|
||||
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40));
|
||||
|
||||
// These values are be ignored
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge",
|
||||
[ActionsEnvVars.GITHUB_SHA]: "a".repeat(40),
|
||||
});
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("b".repeat(40));
|
||||
callback.withArgs(sinon.match.any, "HEAD").resolves("b".repeat(40));
|
||||
callback
|
||||
.withArgs(sinon.match.any, tmpDir, "refs/pull/1/merge")
|
||||
.resolves("b".repeat(40));
|
||||
callback
|
||||
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
|
||||
.resolves("b".repeat(40));
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, "refs/pull/2/merge");
|
||||
|
||||
// getCommitOid shouldn't be called, because the ref should be taken from the input
|
||||
@@ -124,14 +152,17 @@ test.serial(
|
||||
"getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/HEAD";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = expectedRef;
|
||||
process.env["GITHUB_REF"] = "";
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[EnvVar.CODE_SCANNING_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_REF]: "",
|
||||
[ActionsEnvVars.GITHUB_SHA]: currentSha,
|
||||
});
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
},
|
||||
@@ -141,14 +172,17 @@ test.serial(
|
||||
"getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[EnvVar.CODE_SCANNING_REF]: "refs/pull/1/HEAD",
|
||||
[ActionsEnvVars.GITHUB_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_SHA]: currentSha,
|
||||
});
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
},
|
||||
@@ -158,7 +192,9 @@ test.serial(
|
||||
"getRef() throws an error if only `ref` is provided as an input",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
@@ -167,7 +203,7 @@ test.serial(
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
await gitUtils.getRef(env, tmpDir);
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
@@ -183,8 +219,12 @@ test.serial(
|
||||
"getRef() throws an error if only `sha` is provided as an input",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_WORKSPACE"] = "/tmp";
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_WORKSPACE]: "/tmp",
|
||||
});
|
||||
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
@@ -193,7 +233,7 @@ test.serial(
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
await gitUtils.getRef(env, tmpDir);
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
@@ -206,13 +246,16 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "false";
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_EVENT_NAME]: "push",
|
||||
CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH: "true",
|
||||
});
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, ""), true);
|
||||
|
||||
env.set("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH", "false");
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
const envFile = path.join(tmpDir, "event.json");
|
||||
fs.writeFileSync(
|
||||
envFile,
|
||||
@@ -222,17 +265,17 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
process.env["GITHUB_EVENT_PATH"] = envFile;
|
||||
env.set(ActionsEnvVars.GITHUB_EVENT_PATH, envFile);
|
||||
|
||||
process.env["GITHUB_REF"] = "main";
|
||||
process.env["GITHUB_SHA"] = "1234";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "main");
|
||||
env.set(ActionsEnvVars.GITHUB_SHA, "1234");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
|
||||
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
|
||||
|
||||
process.env["GITHUB_REF"] = "feature";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "feature");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false);
|
||||
|
||||
fs.writeFileSync(
|
||||
envFile,
|
||||
@@ -240,9 +283,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
schedule: "0 0 * * *",
|
||||
}),
|
||||
);
|
||||
process.env["GITHUB_EVENT_NAME"] = "schedule";
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule");
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
|
||||
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub
|
||||
@@ -251,9 +294,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
getAdditionalInputStub
|
||||
.withArgs("sha")
|
||||
.resolves("0000000000000000000000000000000000000000");
|
||||
process.env["GITHUB_EVENT_NAME"] = "schedule";
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
|
||||
env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule");
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getWorkflowEvent,
|
||||
getWorkflowEventName,
|
||||
} from "./actions-util";
|
||||
import { ActionsEnvVars, EnvVar, type ReadOnlyEnv } from "./environment";
|
||||
import { ConfigurationError, getRequiredEnvParam } from "./util";
|
||||
|
||||
/**
|
||||
@@ -101,6 +102,7 @@ export const runGitCommand = async function (
|
||||
* Gets the SHA of the commit that is currently checked out.
|
||||
*/
|
||||
export const getCommitOid = async function (
|
||||
env: ReadOnlyEnv,
|
||||
checkoutPath: string,
|
||||
ref = "HEAD",
|
||||
): Promise<string> {
|
||||
@@ -119,7 +121,9 @@ export const getCommitOid = async function (
|
||||
);
|
||||
return stdout.trim();
|
||||
} catch {
|
||||
return getOptionalInput("sha") || getRequiredEnvParam("GITHUB_SHA");
|
||||
return (
|
||||
getOptionalInput("sha") || env.getRequired(ActionsEnvVars.GITHUB_SHA)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,7 +245,7 @@ export const getGitRoot = async function (
|
||||
["rev-parse", "--show-toplevel"],
|
||||
`Cannot find Git repository root from the source root ${sourceRoot}.`,
|
||||
);
|
||||
return stdout.trim();
|
||||
return path.resolve(stdout.trim());
|
||||
} catch {
|
||||
// Errors are already logged by runGitCommand()
|
||||
return undefined;
|
||||
@@ -314,18 +318,18 @@ export const getFileOidsUnderPath = async function (
|
||||
return fileOidMap;
|
||||
};
|
||||
|
||||
function getRefFromEnv(): string {
|
||||
function getRefFromEnv(env: ReadOnlyEnv): string {
|
||||
// To workaround a limitation of Actions dynamic workflows not setting
|
||||
// the GITHUB_REF in some cases, we accept also the ref within the
|
||||
// CODE_SCANNING_REF variable. When possible, however, we prefer to use
|
||||
// the GITHUB_REF as that is a protected variable and cannot be overwritten.
|
||||
let refEnv: string;
|
||||
try {
|
||||
refEnv = getRequiredEnvParam("GITHUB_REF");
|
||||
refEnv = env.getRequired(ActionsEnvVars.GITHUB_REF);
|
||||
} catch (e) {
|
||||
// If the GITHUB_REF is not set, we try to rescue by getting the
|
||||
// CODE_SCANNING_REF.
|
||||
const maybeRef = process.env["CODE_SCANNING_REF"];
|
||||
const maybeRef = env.getOptional(EnvVar.CODE_SCANNING_REF);
|
||||
if (maybeRef === undefined || maybeRef.length === 0) {
|
||||
throw e;
|
||||
}
|
||||
@@ -337,15 +341,16 @@ function getRefFromEnv(): string {
|
||||
/**
|
||||
* Get the ref currently being analyzed.
|
||||
*/
|
||||
export async function getRef(): Promise<string> {
|
||||
export async function getRef(
|
||||
env: ReadOnlyEnv,
|
||||
checkoutPath: string | undefined,
|
||||
): Promise<string> {
|
||||
// Will be in the form "refs/heads/master" on a push event
|
||||
// or in the form "refs/pull/N/merge" on a pull_request event
|
||||
const refInput = getOptionalInput("ref");
|
||||
const shaInput = getOptionalInput("sha");
|
||||
const checkoutPath =
|
||||
getOptionalInput("checkout_path") ||
|
||||
getOptionalInput("source-root") ||
|
||||
getRequiredEnvParam("GITHUB_WORKSPACE");
|
||||
checkoutPath =
|
||||
checkoutPath ?? env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE);
|
||||
|
||||
const hasRefInput = !!refInput;
|
||||
const hasShaInput = !!shaInput;
|
||||
@@ -356,8 +361,8 @@ export async function getRef(): Promise<string> {
|
||||
);
|
||||
}
|
||||
|
||||
const ref = refInput || getRefFromEnv();
|
||||
const sha = shaInput || getRequiredEnvParam("GITHUB_SHA");
|
||||
const ref = refInput || getRefFromEnv(env);
|
||||
const sha = shaInput || env.getRequired(ActionsEnvVars.GITHUB_SHA);
|
||||
|
||||
// If the ref is a user-provided input, we have to skip logic
|
||||
// and assume that it is really where they want to upload the results.
|
||||
@@ -374,7 +379,7 @@ export async function getRef(): Promise<string> {
|
||||
return ref;
|
||||
}
|
||||
|
||||
const head = await getCommitOid(checkoutPath, "HEAD");
|
||||
const head = await getCommitOid(env, checkoutPath, "HEAD");
|
||||
|
||||
// in actions/checkout@v2+ we can check if git rev-parse HEAD == GITHUB_SHA
|
||||
// in actions/checkout@v1 this may not be true as it checks out the repository
|
||||
@@ -384,6 +389,7 @@ export async function getRef(): Promise<string> {
|
||||
const hasChangedRef =
|
||||
sha !== head &&
|
||||
(await getCommitOid(
|
||||
env,
|
||||
checkoutPath,
|
||||
ref.replace(/^refs\/pull\//, "refs/remotes/pull/"),
|
||||
)) !== head;
|
||||
@@ -410,20 +416,23 @@ function removeRefsHeadsPrefix(ref: string): string {
|
||||
* environment variable can be set in cases where repository information might not be available, for
|
||||
* example dynamic workflows.
|
||||
*/
|
||||
export async function isAnalyzingDefaultBranch(): Promise<boolean> {
|
||||
if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") {
|
||||
export async function isAnalyzingDefaultBranch(
|
||||
env: ReadOnlyEnv,
|
||||
checkoutPath: string | undefined,
|
||||
): Promise<boolean> {
|
||||
if (env.getOptional("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH") === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the current ref and trim and refs/heads/ prefix
|
||||
let currentRef = await getRef();
|
||||
let currentRef = await getRef(env, checkoutPath);
|
||||
currentRef = removeRefsHeadsPrefix(currentRef);
|
||||
|
||||
const event = getWorkflowEvent();
|
||||
const event = getWorkflowEvent(env);
|
||||
let defaultBranch = event?.repository?.default_branch;
|
||||
|
||||
if (getWorkflowEventName() === "schedule") {
|
||||
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv());
|
||||
if (getWorkflowEventName(env) === "schedule") {
|
||||
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv(env));
|
||||
}
|
||||
|
||||
return currentRef === defaultBranch;
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
} from "./util";
|
||||
import {
|
||||
getCategoryInputOrThrow,
|
||||
getCheckoutPathInputOrThrow,
|
||||
getRepositoryRootOrThrow,
|
||||
getUploadInputOrThrow,
|
||||
getWorkflow,
|
||||
} from "./workflow";
|
||||
@@ -144,7 +144,15 @@ async function prepareFailedSarif(
|
||||
});
|
||||
}
|
||||
const category = getCategoryInputOrThrow(workflow, jobName, matrix);
|
||||
const checkoutPath = getCheckoutPathInputOrThrow(workflow, jobName, matrix);
|
||||
|
||||
// Try to determine the path at which the repository that we failed to analyse is checked out at.
|
||||
// We need this to relativise the paths in the SARIF.
|
||||
const checkoutPath = getRepositoryRootOrThrow(
|
||||
workflow,
|
||||
jobName,
|
||||
matrix,
|
||||
config,
|
||||
);
|
||||
|
||||
const result = await generateFailedSarif(
|
||||
logger,
|
||||
|
||||
@@ -50,6 +50,7 @@ async function run(startedAt: Date) {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
const env = getEnv();
|
||||
let config: Config | undefined;
|
||||
let uploadFailedSarifResult:
|
||||
| initActionPostHelper.UploadFailedSarifResult
|
||||
@@ -62,7 +63,7 @@ async function run(startedAt: Date) {
|
||||
const jobStatus = getOptionalInput("job-status");
|
||||
|
||||
// Restore inputs from `init` Action.
|
||||
restoreInputs();
|
||||
restoreInputs(logger);
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
@@ -91,7 +92,7 @@ async function run(startedAt: Date) {
|
||||
repositoryNwo,
|
||||
features,
|
||||
jobStatus,
|
||||
getEnv(),
|
||||
env,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -100,7 +101,7 @@ async function run(startedAt: Date) {
|
||||
// do this under these circumstances to avoid slowing down analyses for PRs
|
||||
// and where caching may not be enabled.
|
||||
if (
|
||||
(await gitUtils.isAnalyzingDefaultBranch()) &&
|
||||
(await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot)) &&
|
||||
config.dependencyCachingEnabled !== CachingKind.None
|
||||
) {
|
||||
dependencyCachingUsage = await getDependencyCacheUsage(logger);
|
||||
|
||||
@@ -716,7 +716,7 @@ async function run(
|
||||
);
|
||||
}
|
||||
|
||||
const tracerConfig = await getCombinedTracerConfig(codeql, config);
|
||||
const tracerConfig = await getCombinedTracerConfig(logger, codeql, config);
|
||||
if (tracerConfig !== undefined) {
|
||||
for (const [key, value] of Object.entries(tracerConfig.env)) {
|
||||
core.exportVariable(key, value);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CleanupLevel,
|
||||
getBaseDatabaseOidsFilePath,
|
||||
getCodeQLDatabasePath,
|
||||
getEnv,
|
||||
getErrorMessage,
|
||||
isInTestMode,
|
||||
tryGetFolderBytes,
|
||||
@@ -377,7 +378,7 @@ export async function getCacheSaveKey(
|
||||
`Failed to get workflow run ID or attempt ID. Reason: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
const sha = await getCommitOid(checkoutPath);
|
||||
const sha = await getCommitOid(getEnv(), checkoutPath);
|
||||
const restoreKeyPrefix = await getCacheRestoreKeyPrefix(
|
||||
config,
|
||||
codeQlVersion,
|
||||
|
||||
@@ -131,3 +131,16 @@ export async function getPerLanguageBundleLanguage(
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
/** Explains why an eligible per-language bundle is being replaced by a combined bundle. */
|
||||
export function logPerLanguageBundleFallback(
|
||||
{ logger }: ActionState<["Logger"]>,
|
||||
language: BuiltInLanguage,
|
||||
location: string,
|
||||
): void {
|
||||
logger.warning(
|
||||
`No per-language CodeQL bundle for '${language}' was found at ${location}, so ` +
|
||||
"falling back to the bundle that contains all languages. This analysis will still " +
|
||||
"produce correct results, but will take longer to set up.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as fs from "fs";
|
||||
|
||||
import { Logger } from "../logging";
|
||||
import { getErrorMessage } from "../util";
|
||||
|
||||
import * as sarif from "sarif";
|
||||
|
||||
@@ -48,7 +49,13 @@ export function getToolNames(sarifFile: Partial<sarif.Log>): string[] {
|
||||
* @returns The resulting JSON value, cast to a SARIF `Log`.
|
||||
*/
|
||||
export function readSarifFile(sarifFilePath: string): Partial<sarif.Log> {
|
||||
return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as sarif.Log;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as sarif.Log;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Parsing SARIF file at '${sarifFilePath}' failed: ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a list of paths to sarif files and combines them together,
|
||||
|
||||
@@ -435,7 +435,7 @@ test.serial(
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/codeql-bundle-linux64.tar.zst`;
|
||||
t.deepEqual(source, {
|
||||
bundle: { kind: "combined", url: expectedURL },
|
||||
bundleVersion: expectedDate,
|
||||
@@ -505,7 +505,7 @@ test.serial(
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/codeql-bundle-linux64.tar.zst`;
|
||||
t.deepEqual(source, {
|
||||
bundle: { kind: "combined", url: expectedURL },
|
||||
bundleVersion: expectedDate,
|
||||
@@ -1143,30 +1143,6 @@ const PER_LANGUAGE_CLI_VERSION = {
|
||||
],
|
||||
};
|
||||
|
||||
test.serial(
|
||||
"getCodeQLBundleName returns a per-language bundle name only when a language is specified",
|
||||
(t) => {
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(process, "arch").value("x64");
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.java),
|
||||
"codeql-bundle-java-linux64.tar.zst",
|
||||
);
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd"),
|
||||
"codeql-bundle-linux64.tar.zst",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("getCodeQLBundleName names the Swift bundle for macOS", (t) => {
|
||||
sinon.stub(process, "platform").value("darwin");
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.swift),
|
||||
"codeql-bundle-swift-osx64.tar.zst",
|
||||
);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource downloads the per-language bundle for a single explicit language",
|
||||
async (t) => {
|
||||
|
||||
@@ -17,7 +17,12 @@ import {
|
||||
isRunningLocalAction,
|
||||
} from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import { CodeQLBundle, getCodeQLBundleFromUrl } from "./codeql-bundle";
|
||||
import {
|
||||
CodeQLBundle,
|
||||
CodeQLDownloadSource,
|
||||
getCodeQLBundleFromUrl,
|
||||
getCodeQLBundleName,
|
||||
} from "./codeql-bundle";
|
||||
import * as defaults from "./defaults.json";
|
||||
import {
|
||||
addNoLanguageDiagnostic,
|
||||
@@ -35,7 +40,10 @@ import {
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { getCodeQlVersionsForOverlayBaseDatabases } from "./overlay/caching";
|
||||
import { getPerLanguageBundleLanguage } from "./per-language-bundles";
|
||||
import {
|
||||
getPerLanguageBundleLanguage,
|
||||
logPerLanguageBundleFallback,
|
||||
} from "./per-language-bundles";
|
||||
import { getBundlePlatform } from "./platform";
|
||||
import * as tar from "./tar";
|
||||
import {
|
||||
@@ -49,6 +57,8 @@ import {
|
||||
import * as util from "./util";
|
||||
import { isGoodVersion } from "./util";
|
||||
|
||||
export type { CodeQLDownloadSource } from "./codeql-bundle";
|
||||
|
||||
export enum ToolsSource {
|
||||
Unknown = "UNKNOWN",
|
||||
Local = "LOCAL",
|
||||
@@ -64,41 +74,6 @@ const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"];
|
||||
const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"];
|
||||
const CODEQL_TOOLCACHE_INPUT = "toolcache";
|
||||
|
||||
function getCodeQLBundleExtension(
|
||||
compressionMethod: tar.CompressionMethod,
|
||||
): string {
|
||||
switch (compressionMethod) {
|
||||
case "gzip":
|
||||
return ".tar.gz";
|
||||
case "zstd":
|
||||
return ".tar.zst";
|
||||
default:
|
||||
util.assertNever(compressionMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the CodeQL bundle asset to download.
|
||||
*
|
||||
* @param compressionMethod The compression method of the bundle.
|
||||
* @param language Optional language for a per-language bundle. If omitted, returns a combined bundle name.
|
||||
*/
|
||||
export function getCodeQLBundleName(
|
||||
compressionMethod: tar.CompressionMethod,
|
||||
language?: BuiltInLanguage,
|
||||
): string {
|
||||
const extension = getCodeQLBundleExtension(compressionMethod);
|
||||
const platform = getBundlePlatform();
|
||||
|
||||
if (platform === undefined) {
|
||||
return `codeql-bundle${extension}`;
|
||||
}
|
||||
if (language !== undefined) {
|
||||
return `codeql-bundle-${language}-${platform}${extension}`;
|
||||
}
|
||||
return `codeql-bundle-${platform}${extension}`;
|
||||
}
|
||||
|
||||
export function getCodeQLActionRepository(logger: Logger): string {
|
||||
if (isRunningLocalAction()) {
|
||||
// This handles the case where the Action does not come from an Action repository,
|
||||
@@ -223,22 +198,6 @@ export function convertToSemVer(version: string, logger: Logger): string {
|
||||
return s;
|
||||
}
|
||||
|
||||
/** A resolved download, including its bundle identity and version. */
|
||||
export interface CodeQLDownloadSource {
|
||||
/** Distinguishes downloads from local archives and cached installations. */
|
||||
sourceType: "download";
|
||||
/** The bundle to download. */
|
||||
bundle: CodeQLBundle;
|
||||
/** The compression format of the bundle archive. */
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
/** Bundle version of the tools, if known. */
|
||||
bundleVersion?: string;
|
||||
/** Requested CLI version, if known. */
|
||||
cliVersion?: string;
|
||||
/** Resolved version for telemetry, independent of whether the bundle can be cached. */
|
||||
toolsVersion: string;
|
||||
}
|
||||
|
||||
export type CodeQLToolsSource =
|
||||
| {
|
||||
codeqlTarPath: string;
|
||||
@@ -757,13 +716,14 @@ export async function getCodeQLSource(
|
||||
? "zstd"
|
||||
: "gzip";
|
||||
|
||||
const platform = getBundlePlatform();
|
||||
const perLanguageBundleLanguage = await getPerLanguageBundleLanguage(
|
||||
{ env: getEnv(), features, logger },
|
||||
{
|
||||
rawLanguages,
|
||||
cliVersion,
|
||||
compressionMethod,
|
||||
platform: getBundlePlatform(),
|
||||
platform,
|
||||
variant,
|
||||
},
|
||||
);
|
||||
@@ -773,7 +733,7 @@ export async function getCodeQLSource(
|
||||
getCodeQLBundleDownloadURL(
|
||||
bundleTagName,
|
||||
apiDetails,
|
||||
getCodeQLBundleName(compressionMethod, language),
|
||||
getCodeQLBundleName(compressionMethod, platform, language),
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -1153,11 +1113,7 @@ export async function downloadCodeQLBundle(
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
logger.warning(
|
||||
`No per-language CodeQL bundle for '${bundle.language}' was found at ${bundle.url}, so ` +
|
||||
"falling back to the bundle that contains all languages. This analysis will still " +
|
||||
"produce correct results, but will take longer to set up.",
|
||||
);
|
||||
logPerLanguageBundleFallback(action, bundle.language, bundle.url);
|
||||
|
||||
const result = await downloadCodeQL(
|
||||
{
|
||||
@@ -1215,11 +1171,12 @@ async function getLatestNightlyBundle(
|
||||
? "zstd"
|
||||
: "gzip";
|
||||
|
||||
const platform = getBundlePlatform();
|
||||
const language = await getPerLanguageBundleLanguage(action, {
|
||||
rawLanguages,
|
||||
cliVersion: undefined,
|
||||
compressionMethod,
|
||||
platform: getBundlePlatform(),
|
||||
platform,
|
||||
variant,
|
||||
isLatestNightly: true,
|
||||
});
|
||||
@@ -1241,14 +1198,18 @@ async function getLatestNightlyBundle(
|
||||
}
|
||||
const assetUrl = (name: string) =>
|
||||
`https://github.com/${CODEQL_NIGHTLIES_REPOSITORY_OWNER}/${CODEQL_NIGHTLIES_REPOSITORY_NAME}/releases/download/${latestRelease.tag_name}/${name}`;
|
||||
const url = assetUrl(getCodeQLBundleName(compressionMethod, language));
|
||||
const url = assetUrl(
|
||||
getCodeQLBundleName(compressionMethod, platform, language),
|
||||
);
|
||||
return language === undefined
|
||||
? { kind: "combined", url }
|
||||
: {
|
||||
kind: "per-language",
|
||||
url,
|
||||
language,
|
||||
combinedBundleURL: assetUrl(getCodeQLBundleName(compressionMethod)),
|
||||
combinedBundleURL: assetUrl(
|
||||
getCodeQLBundleName(compressionMethod, platform),
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function runWrapper() {
|
||||
|
||||
try {
|
||||
// Restore inputs from `start-proxy` Action.
|
||||
actionsUtil.restoreInputs();
|
||||
actionsUtil.restoreInputs(logger);
|
||||
|
||||
// Kill the running proxy
|
||||
const pid = core.getState("proxy-process-pid");
|
||||
@@ -38,12 +38,6 @@ export async function runWrapper() {
|
||||
logger.info(
|
||||
"Debug mode is on. Uploading proxy log as Actions debugging artifact...",
|
||||
);
|
||||
if (config?.gitHubVersion.type === undefined) {
|
||||
logger.warning(
|
||||
`Did not upload debug artifacts because cannot determine the GitHub variant running.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ export async function createStatusReportBase(
|
||||
try {
|
||||
const commitOid =
|
||||
getOptionalInput("sha") || process.env["GITHUB_SHA"] || "";
|
||||
const ref = await getRef();
|
||||
const ref = await getRef(getEnv(), config?.repositoryRoot);
|
||||
const jobRunUUID = process.env[EnvVar.JOB_RUN_UUID] || "";
|
||||
const workflowRunID = getWorkflowRunID();
|
||||
const workflowRunAttempt = getWorkflowRunAttempt();
|
||||
|
||||
@@ -968,6 +968,7 @@ export function createTestConfig(overrides: Partial<Config>): Config {
|
||||
{},
|
||||
{
|
||||
version: getActionVersion(),
|
||||
repositoryRoot: undefined,
|
||||
analysisKinds: [AnalysisKind.CodeScanning],
|
||||
languages: [],
|
||||
buildMode: undefined,
|
||||
|
||||
@@ -7,7 +7,12 @@ import * as sinon from "sinon";
|
||||
import { CodeQL, getCodeQLForTesting } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { createTestConfig, makeVersionInfo, setupTests } from "./testing-utils";
|
||||
import {
|
||||
createTestConfig,
|
||||
makeVersionInfo,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import { ToolsFeature } from "./tools-features";
|
||||
import { getCombinedTracerConfig } from "./tracer-config";
|
||||
import * as util from "./util";
|
||||
@@ -42,18 +47,20 @@ async function stubCodeql(
|
||||
}
|
||||
|
||||
test("getCombinedTracerConfig - return undefined when no languages are traced languages", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const config = getTestConfig(tmpDir);
|
||||
// No traced languages
|
||||
config.languages = [BuiltInLanguage.javascript, BuiltInLanguage.python];
|
||||
t.deepEqual(
|
||||
await getCombinedTracerConfig(await stubCodeql(), config),
|
||||
await getCombinedTracerConfig(logger, await stubCodeql(), config),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCombinedTracerConfig", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const config = getTestConfig(tmpDir);
|
||||
|
||||
@@ -82,7 +89,11 @@ test("getCombinedTracerConfig", async (t) => {
|
||||
);
|
||||
fs.writeFileSync(startTracingJson, JSON.stringify(startTracingEnv));
|
||||
|
||||
const result = await getCombinedTracerConfig(await stubCodeql(), config);
|
||||
const result = await getCombinedTracerConfig(
|
||||
logger,
|
||||
await stubCodeql(),
|
||||
config,
|
||||
);
|
||||
t.notDeepEqual(result, undefined);
|
||||
|
||||
t.false(Object.prototype.hasOwnProperty.call(result?.env, "CODEQL_RUNNER"));
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as path from "path";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { type Config } from "./config-utils";
|
||||
import { Logger } from "./logging";
|
||||
import { asyncSome, BuildMode } from "./util";
|
||||
import { asyncSome, BuildMode, getErrorMessage } from "./util";
|
||||
|
||||
export type TracerConfig = {
|
||||
env: { [key: string]: string };
|
||||
@@ -77,23 +77,28 @@ export async function endTracingForCluster(
|
||||
}
|
||||
|
||||
async function getTracerConfigForCluster(
|
||||
logger: Logger,
|
||||
config: Config,
|
||||
): Promise<TracerConfig> {
|
||||
const tracingEnvVariables = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.resolve(
|
||||
config.dbLocation,
|
||||
"temp/tracingEnvironment/start-tracing.json",
|
||||
),
|
||||
"utf8",
|
||||
),
|
||||
const filePath = path.resolve(
|
||||
config.dbLocation,
|
||||
"temp/tracingEnvironment/start-tracing.json",
|
||||
);
|
||||
return {
|
||||
env: tracingEnvVariables,
|
||||
};
|
||||
try {
|
||||
const tracingEnvVariables = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
return {
|
||||
env: tracingEnvVariables,
|
||||
};
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Failed to parse tracing environment from '${filePath}': ${getErrorMessage(err)}`,
|
||||
);
|
||||
throw new Error(`Failed to parse tracing environment from '${filePath}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCombinedTracerConfig(
|
||||
logger: Logger,
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
): Promise<TracerConfig | undefined> {
|
||||
@@ -101,5 +106,5 @@ export async function getCombinedTracerConfig(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return await getTracerConfigForCluster(config);
|
||||
return await getTracerConfigForCluster(logger, config);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ test.serial(
|
||||
stubCodeql,
|
||||
[BuiltInLanguage.javascript, BuiltInLanguage.cpp],
|
||||
logger,
|
||||
undefined,
|
||||
);
|
||||
t.assert(
|
||||
stubRestore.calledOnceWith(
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
asHTTPError,
|
||||
getEnv,
|
||||
getErrorMessage,
|
||||
tryGetFolderBytes,
|
||||
waitForResultWithTimeLimit,
|
||||
@@ -43,6 +44,7 @@ const MAX_CACHE_OPERATION_MS = 120_000; // Two minutes
|
||||
* @param codeql The CodeQL instance to use.
|
||||
* @param languages The languages being analyzed.
|
||||
* @param logger A logger to record some informational messages to.
|
||||
* @param repositoryRoot The path at which the repository is checked out at.
|
||||
* @returns A partial map from languages to TRAP cache paths on disk, with
|
||||
* languages for which we shouldn't use TRAP caching omitted.
|
||||
*/
|
||||
@@ -50,6 +52,7 @@ export async function downloadTrapCaches(
|
||||
codeql: CodeQL,
|
||||
languages: Language[],
|
||||
logger: Logger,
|
||||
repositoryRoot: string | undefined,
|
||||
): Promise<{ [language: string]: string }> {
|
||||
const result: { [language: string]: string } = {};
|
||||
const languagesSupportingCaching = await getLanguagesSupportingCaching(
|
||||
@@ -72,7 +75,7 @@ export async function downloadTrapCaches(
|
||||
result[language] = cacheDir;
|
||||
}
|
||||
|
||||
if (await gitUtils.isAnalyzingDefaultBranch()) {
|
||||
if (await gitUtils.isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) {
|
||||
logger.info(
|
||||
"Analyzing default branch. Skipping downloading of TRAP caches.",
|
||||
);
|
||||
@@ -132,7 +135,12 @@ export async function uploadTrapCaches(
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<boolean> {
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch())) return false; // Only upload caches from the default branch
|
||||
// Only upload caches from the default branch
|
||||
if (
|
||||
!(await gitUtils.isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const language of config.languages) {
|
||||
const cacheDir = config.trapCaches[language];
|
||||
@@ -180,6 +188,8 @@ export async function cleanupTrapCaches(
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<TrapCacheCleanupStatusReport> {
|
||||
const env = getEnv();
|
||||
|
||||
if (!(await features.getValue(Feature.CleanupTrapCaches))) {
|
||||
return {
|
||||
trap_cache_cleanup_skipped_because: "feature disabled",
|
||||
@@ -189,7 +199,7 @@ export async function cleanupTrapCaches(
|
||||
"TRAP cache cleanup is deprecated and will be removed in May 2026. " +
|
||||
"We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action.",
|
||||
);
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot))) {
|
||||
return {
|
||||
trap_cache_cleanup_skipped_because: "not analyzing default branch",
|
||||
};
|
||||
@@ -200,7 +210,7 @@ export async function cleanupTrapCaches(
|
||||
|
||||
const allCaches = await apiClient.listActionsCaches(
|
||||
CODEQL_TRAP_CACHE_PREFIX,
|
||||
await gitUtils.getRef(),
|
||||
await gitUtils.getRef(env, config.repositoryRoot),
|
||||
);
|
||||
|
||||
for (const language of config.languages) {
|
||||
|
||||
@@ -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 } from "./environment";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import * as fingerprints from "./fingerprints";
|
||||
import * as gitUtils from "./git-utils";
|
||||
@@ -761,12 +761,13 @@ export async function uploadPostProcessedFiles(
|
||||
const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64");
|
||||
const checkoutURI = url.pathToFileURL(checkoutPath).href;
|
||||
|
||||
const env = util.getEnv();
|
||||
const payload = uploadTarget.transformPayload(
|
||||
buildPayload(
|
||||
await gitUtils.getCommitOid(checkoutPath),
|
||||
await gitUtils.getRef(),
|
||||
await gitUtils.getCommitOid(env, checkoutPath),
|
||||
await gitUtils.getRef(env, checkoutPath),
|
||||
postProcessingResults.analysisKey,
|
||||
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
|
||||
env.getRequired(ActionsEnvVars.GITHUB_WORKFLOW),
|
||||
zippedSarif,
|
||||
actionsUtil.getWorkflowRunID(),
|
||||
actionsUtil.getWorkflowRunAttempt(),
|
||||
|
||||
@@ -17,9 +17,10 @@ export async function runWrapper() {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
try {
|
||||
// Restore inputs from `upload-sarif` Action.
|
||||
actionsUtil.restoreInputs();
|
||||
const logger = getActionsLogger();
|
||||
|
||||
// Restore inputs from `upload-sarif` Action.
|
||||
actionsUtil.restoreInputs(logger);
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
|
||||
|
||||
14
src/util.ts
14
src/util.ts
@@ -894,7 +894,13 @@ export function parseMatrixInput(
|
||||
if (matrixInput === undefined || matrixInput === "null") {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(matrixInput) as { [key: string]: string };
|
||||
try {
|
||||
return JSON.parse(matrixInput) as { [key: string]: string };
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to parse matrix input '${matrixInput}': ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function wrapError(error: unknown): Error {
|
||||
@@ -1037,7 +1043,11 @@ export enum BuildMode {
|
||||
}
|
||||
|
||||
export function cloneObject<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj)) as T;
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(obj)) as T;
|
||||
} catch (err) {
|
||||
throw new Error(`Cloning object failed: ${getErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanUpPath(file: string, name: string, logger: Logger) {
|
||||
|
||||
@@ -4,10 +4,12 @@ import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { createStubCodeQL, getCodeQLForTesting } from "./codeql";
|
||||
import { EnvVar } from "./environment";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
createTestConfig,
|
||||
getRecordingLogger,
|
||||
getTestEnv,
|
||||
LoggedMessage,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
@@ -1002,3 +1004,124 @@ test.serial(
|
||||
t.is(messages.length, 0);
|
||||
},
|
||||
);
|
||||
|
||||
test("getRepositoryRootOrThrow - gets root from config", async (t) => {
|
||||
const expectedRoot = "/path/to/root";
|
||||
const repositoryRoot = workflow.getRepositoryRootOrThrow(
|
||||
{},
|
||||
"testJob",
|
||||
{},
|
||||
createTestConfig({ repositoryRoot: expectedRoot }),
|
||||
getTestEnv(),
|
||||
);
|
||||
|
||||
t.is(repositoryRoot, expectedRoot);
|
||||
});
|
||||
|
||||
test("getRepositoryRootOrThrow - gets root from workflow", async (t) => {
|
||||
const expectedRoot = "/path/to/root";
|
||||
const repositoryRoot = workflow.getRepositoryRootOrThrow(
|
||||
{
|
||||
jobs: {
|
||||
testJob: {
|
||||
steps: [
|
||||
{
|
||||
uses: "github/codeql-action/analyze",
|
||||
with: { checkout_path: expectedRoot },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"testJob",
|
||||
{},
|
||||
createTestConfig({}),
|
||||
getTestEnv(),
|
||||
);
|
||||
|
||||
t.is(repositoryRoot, expectedRoot);
|
||||
});
|
||||
|
||||
test("getRepositoryRootOrThrow - gets root from environment", async (t) => {
|
||||
const expectedRoot = "/path/to/root";
|
||||
const repositoryRoot = workflow.getRepositoryRootOrThrow(
|
||||
{
|
||||
jobs: { testJob: { steps: [{ uses: "github/codeql-action/analyze" }] } },
|
||||
},
|
||||
"testJob",
|
||||
{},
|
||||
createTestConfig({}),
|
||||
getTestEnv({ [ActionsEnvVars.GITHUB_WORKSPACE]: expectedRoot }),
|
||||
);
|
||||
|
||||
t.is(repositoryRoot, expectedRoot);
|
||||
});
|
||||
|
||||
test("getRepositoryRootOrThrow - throws if there's no matching job", async (t) => {
|
||||
t.throws(
|
||||
() =>
|
||||
workflow.getRepositoryRootOrThrow(
|
||||
{
|
||||
jobs: {
|
||||
otherJob: {
|
||||
steps: [
|
||||
{
|
||||
uses: "github/codeql-action/analyze",
|
||||
with: { checkout_path: "/some/path" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"testJob",
|
||||
{},
|
||||
createTestConfig({}),
|
||||
getTestEnv(),
|
||||
),
|
||||
{ message: /since the workflow has no job named testJob./ },
|
||||
);
|
||||
});
|
||||
|
||||
test("getRepositoryRootOrThrow - throws if there's no analyze step", async (t) => {
|
||||
t.throws(
|
||||
() =>
|
||||
workflow.getRepositoryRootOrThrow(
|
||||
{
|
||||
jobs: {
|
||||
testJob: { steps: [] },
|
||||
},
|
||||
},
|
||||
"testJob",
|
||||
{},
|
||||
createTestConfig({}),
|
||||
getTestEnv(),
|
||||
),
|
||||
{ message: /since the testJob job does not call/ },
|
||||
);
|
||||
});
|
||||
|
||||
test("getRepositoryRootOrThrow - throws if the env var is not set", async (t) => {
|
||||
t.throws(
|
||||
() =>
|
||||
workflow.getRepositoryRootOrThrow(
|
||||
{
|
||||
jobs: {
|
||||
testJob: {
|
||||
steps: [
|
||||
{
|
||||
uses: "github/codeql-action/analyze",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"testJob",
|
||||
{},
|
||||
createTestConfig({}),
|
||||
getTestEnv(),
|
||||
),
|
||||
{
|
||||
message: `${ActionsEnvVars.GITHUB_WORKSPACE} environment variable must be set`,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@ import * as yaml from "js-yaml";
|
||||
import { isDynamicWorkflow } from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { EnvVar } from "./environment";
|
||||
import type { Config } from "./config-utils";
|
||||
import { ActionsEnvVars, EnvVar, getEnv, ReadOnlyEnv } from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
getRequiredEnvParam,
|
||||
@@ -441,27 +442,45 @@ export function getUploadInputOrThrow(
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a best effort attempt to retrieve the checkout_path input for the
|
||||
* particular job, given a set of matrix variables.
|
||||
* Makes a best effort attempt to determine the root path of the repository that the analysis
|
||||
* relates to. We need that to make paths in SARIF files relative.
|
||||
*
|
||||
* - If available, we take the `repositoryRoot` from the `config`.
|
||||
* - If it isn't, we fall back to trying to extract a `checkout_path` input from the `workflow`.
|
||||
* - Finally, we fall back to the value of `GITHUB_WORKSPACE`.
|
||||
*
|
||||
* Typically you'll want to wrap this function in a try/catch block and handle the error.
|
||||
*
|
||||
* @returns the checkout_path input
|
||||
* @throws an error if the checkout_path input could not be determined
|
||||
* @param workflow The workflow specification of the currently running workflow.
|
||||
* @param jobName The name of the job that is currently running.
|
||||
* @param matrixVars The matrix variables, if any.
|
||||
* @param config The CodeQL Action configuration state.
|
||||
* @param env The environment variables.
|
||||
*
|
||||
* @returns The repository root path, or its best approximation.
|
||||
* @throws `Error` if the repository root could not be determined.
|
||||
*/
|
||||
export function getCheckoutPathInputOrThrow(
|
||||
export function getRepositoryRootOrThrow(
|
||||
workflow: Workflow,
|
||||
jobName: string,
|
||||
matrixVars: { [key: string]: string } | undefined,
|
||||
config: Config,
|
||||
env: ReadOnlyEnv = getEnv(),
|
||||
): string {
|
||||
return (
|
||||
// If the CodeQL Action already has a persisted repository root, then we can just use that.
|
||||
config.repositoryRoot ??
|
||||
// Otherwise, try to retrieve it from a `checkout_path` input in the workflow specification.
|
||||
getInputOrThrow(
|
||||
workflow,
|
||||
jobName,
|
||||
getAnalyzeActionName(),
|
||||
"checkout_path",
|
||||
matrixVars,
|
||||
) || getRequiredEnvParam("GITHUB_WORKSPACE") // if unspecified, checkout_path defaults to ${{ github.workspace }}
|
||||
) ??
|
||||
// Finally, if all of the above fail, just use the value of `GITHUB_WORKSPACE` since that
|
||||
// is what is used by default.
|
||||
env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user