diff --git a/lib/entry-points.js b/lib/entry-points.js index ef7547dda..7994d26d9 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -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 diff --git a/src/analyze-action.test.ts b/src/analyze-action.test.ts index 923908a64..ffba5a03d 100644 --- a/src/analyze-action.test.ts +++ b/src/analyze-action.test.ts @@ -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); diff --git a/src/analyze-action.ts b/src/analyze-action.ts index 67b025ead..7c117f75a 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -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( diff --git a/src/analyze.ts b/src/analyze.ts index 3580f80aa..306ccd434 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -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) { diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 29d72f3af..ec18bec45 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -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, diff --git a/src/config-utils.ts b/src/config-utils.ts index 288b4f02f..37337b68d 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -385,6 +385,7 @@ export async function initActionState( enableFileCoverageInformation, }: InitConfigInputs, userConfig: UserConfig, + repositoryRoot: string | undefined, ): Promise { 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, diff --git a/src/config/action-config.ts b/src/config/action-config.ts index de6882e77..2b69141d2 100644 --- a/src/config/action-config.ts +++ b/src/config/action-config.ts @@ -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. */ diff --git a/src/testing-utils.ts b/src/testing-utils.ts index f15cee2e7..b50d51c94 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -968,6 +968,7 @@ export function createTestConfig(overrides: Partial): Config { {}, { version: getActionVersion(), + repositoryRoot: undefined, analysisKinds: [AnalysisKind.CodeScanning], languages: [], buildMode: undefined,