Persist repository root from init action in CodeQL Action state

This commit is contained in:
Michael B. Gale
2026-09-16 12:49:02 +01:00
parent 99fe83948e
commit 5dab0ccf51
8 changed files with 89 additions and 22 deletions

43
lib/entry-points.js generated
View File

@@ -150444,7 +150444,7 @@ async function initActionState({
analysisKinds,
logger,
enableFileCoverageInformation
}, userConfig) {
}, userConfig, repositoryRoot) {
const languages = await getLanguages(
codeql,
languagesInput,
@@ -150480,6 +150480,7 @@ async function initActionState({
);
return {
version: getActionVersion(),
repositoryRoot,
analysisKinds,
languages,
buildMode,
@@ -150630,7 +150631,7 @@ async function checkRunnerResources(codeql, features, diskUsage, ramInput, logge
}
return new Success(void 0);
}
async function checkOverlayEnablement(codeql, features, languages, sourceRoot, buildMode, ramInput, codeScanningConfig, repositoryProperties, gitVersion, logger) {
async function checkOverlayEnablement(codeql, features, languages, repositoryRoot, sourceRoot, buildMode, ramInput, codeScanningConfig, repositoryProperties, gitVersion, logger) {
const modeEnv = process.env.CODEQL_OVERLAY_DATABASE_MODE;
if (modeEnv === "overlay" /* Overlay */ || modeEnv === "overlay-base" /* OverlayBase */ || modeEnv === "none" /* None */) {
logger.info(
@@ -150645,6 +150646,7 @@ async function checkOverlayEnablement(codeql, features, languages, sourceRoot, b
true,
codeql,
languages,
repositoryRoot,
sourceRoot,
buildMode,
gitVersion,
@@ -150717,13 +150719,14 @@ async function checkOverlayEnablement(codeql, features, languages, sourceRoot, b
false,
codeql,
languages,
repositoryRoot,
sourceRoot,
buildMode,
gitVersion,
logger
);
}
async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDatabaseCaching, overlayModeSetExplicitly, codeql, languages, sourceRoot, buildMode, gitVersion, logger) {
async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDatabaseCaching, overlayModeSetExplicitly, codeql, languages, repositoryRoot, sourceRoot, buildMode, gitVersion, logger) {
if (buildMode !== "none" /* None */ && (await Promise.all(
languages.map(
async (l) => l !== "go" /* go */ && // Workaround to allow overlay analysis for Go with any build
@@ -150744,14 +150747,13 @@ async function validateOverlayDatabaseMode(overlayDatabaseMode, useOverlayDataba
);
return new Failure("incompatible-codeql" /* IncompatibleCodeQl */);
}
const gitRoot = await getGitRoot(sourceRoot);
if (gitRoot === void 0) {
if (repositoryRoot === void 0) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because the source root "${sourceRoot}" is not inside a git repository. Falling back to creating a normal full database instead.`
);
return new Failure("no-git-root" /* NoGitRoot */);
}
if (hasSubmodules(gitRoot)) {
if (hasSubmodules(repositoryRoot)) {
if (gitVersion === void 0) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because the repository has submodules and the Git version could not be determined. Falling back to creating a normal full database instead.`
@@ -150886,8 +150888,9 @@ async function determineUserConfig(action, tempDir, inputs) {
async function initConfig(actionState, inputs) {
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 (config.analysisKinds.length === 1 && isCodeQualityEnabled(config)) {
if (hasQueryCustomisation(config.computedConfig)) {
throw new ConfigurationError(
@@ -150940,6 +150943,7 @@ async function initConfig(actionState, inputs) {
inputs.codeql,
inputs.features,
config.languages,
repositoryRoot,
inputs.sourceRoot,
config.buildMode,
inputs.ramInput,
@@ -153801,8 +153805,27 @@ var CodeQLAnalysisError = class extends Error {
message;
error;
};
function determineCheckoutPath(action) {
return action.actions.getRequiredInput("checkout_path");
async function determineCheckoutPath(action) {
const checkoutPathInput = action.actions.getRequiredInput("checkout_path");
const repositoryRoot = await getGitRoot(checkoutPathInput);
if (repositoryRoot === void 0) {
action.logger.warning(
[
`The directory at '${checkoutPathInput}' 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 !== path16.resolve(checkoutPathInput)) {
action.logger.warning(
[
`The directory at '${checkoutPathInput}' 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(" ")
);
}
return checkoutPathInput;
}
async function setupPythonExtractor(logger) {
const codeqlPython = process.env["CODEQL_PYTHON"];
@@ -156615,7 +156638,7 @@ async function run(action) {
getOptionalInput("ram") || process.env["CODEQL_RAM"],
logger
);
const checkoutPath = determineCheckoutPath(action);
const checkoutPath = await determineCheckoutPath(action);
const diffRangePackDir = await setupDiffInformedQueryRun(
logger,
checkoutPath

View File

@@ -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);

View File

@@ -310,7 +310,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
logger,
);
const checkoutPath = determineCheckoutPath(action);
const checkoutPath = await determineCheckoutPath(action);
// Setup diff informed analysis if needed (based on whether init created the file)
const diffRangePackDir = await setupDiffInformedQueryRun(

View File

@@ -22,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";
@@ -93,8 +94,33 @@ export interface QueriesStatusReport
*
* @param action The action state.
*/
export function determineCheckoutPath(action: ActionState<["Actions"]>) {
return action.actions.getRequiredInput("checkout_path");
export async function determineCheckoutPath(
action: ActionState<["Logger", "Actions"]>,
) {
const checkoutPathInput = action.actions.getRequiredInput("checkout_path");
// Try to obtain the root path of the repository and validate that it matches the input.
const repositoryRoot = await getGitRoot(checkoutPathInput);
if (repositoryRoot === undefined) {
action.logger.warning(
[
`The directory at '${checkoutPathInput}' 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 !== path.resolve(checkoutPathInput)) {
action.logger.warning(
[
`The directory at '${checkoutPathInput}' 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(" "),
);
}
return checkoutPathInput;
}
async function setupPythonExtractor(logger: Logger) {

View File

@@ -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,

View File

@@ -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,
@@ -718,6 +720,7 @@ export async function checkOverlayEnablement(
codeql: CodeQL,
features: FeatureEnablement,
languages: Language[],
repositoryRoot: string | undefined,
sourceRoot: string,
buildMode: BuildMode | undefined,
ramInput: string | undefined,
@@ -747,6 +750,7 @@ export async function checkOverlayEnablement(
true,
codeql,
languages,
repositoryRoot,
sourceRoot,
buildMode,
gitVersion,
@@ -836,6 +840,7 @@ export async function checkOverlayEnablement(
false,
codeql,
languages,
repositoryRoot,
sourceRoot,
buildMode,
gitVersion,
@@ -855,6 +860,7 @@ async function validateOverlayDatabaseMode(
overlayModeSetExplicitly: boolean,
codeql: CodeQL,
languages: Language[],
repositoryRoot: string | undefined,
sourceRoot: string,
buildMode: BuildMode | undefined,
gitVersion: GitVersionInfo | undefined,
@@ -890,8 +896,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 +904,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 +1165,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 +1251,7 @@ export async function initConfig(
inputs.codeql,
inputs.features,
config.languages,
repositoryRoot,
inputs.sourceRoot,
config.buildMode,
inputs.ramInput,

View File

@@ -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.
*/

View File

@@ -968,6 +968,7 @@ export function createTestConfig(overrides: Partial<Config>): Config {
{},
{
version: getActionVersion(),
repositoryRoot: undefined,
analysisKinds: [AnalysisKind.CodeScanning],
languages: [],
buildMode: undefined,