Merge pull request #1242 from github/henrymercer/go-more-backwards-compat

Go extraction reconciliation: Ensure backwards compatibility for multi-language builds
This commit is contained in:
Henry Mercer 2022-09-16 11:05:41 +01:00 committed by GitHub
commit 34aa5a554b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 188 additions and 59 deletions

View file

@ -133,7 +133,7 @@ function doesGoExtractionOutputExist(config: Config): boolean {
* steps.
*
* - We detect whether an autobuild step is present by checking the
* `CODEQL_ACTION_DID_AUTOBUILD_GOLANG` environment variable, which is set
* `util.DID_AUTOBUILD_GO_ENV_VAR_NAME` environment variable, which is set
* when the autobuilder is invoked.
* - We approximate whether manual build steps are present by looking at
* whether any extraction output already exists for Go.
@ -152,7 +152,7 @@ async function runAutobuildIfLegacyGoWorkflow(
);
return;
}
if (process.env["CODEQL_ACTION_DID_AUTOBUILD_GOLANG"] === "true") {
if (process.env[util.DID_AUTOBUILD_GO_ENV_VAR_NAME] === "true") {
// This log line is info level while Go extraction reconciliation is in beta.
// We will make it debug level once Go extraction reconciliation is GA.
logger.info("Won't run Go autobuild since it has already been run.");

View file

@ -9,13 +9,14 @@ import {
StatusReportBase,
} from "./actions-util";
import { getApiDetails, getGitHubVersionActionsOnly } from "./api-client";
import { determineAutobuildLanguage, runAutobuild } from "./autobuild";
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import * as configUtils from "./config-utils";
import { GitHubFeatureFlags } from "./feature-flags";
import { Language } from "./languages";
import { getActionsLogger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import {
DID_AUTOBUILD_GO_ENV_VAR_NAME,
checkActionVersion,
checkGitHubVersionInRange,
getRequiredEnvParam,
@ -61,7 +62,8 @@ async function run() {
const startedAt = new Date();
const logger = getActionsLogger();
await checkActionVersion(pkg.version);
let language: Language | undefined = undefined;
let currentLanguage: Language | undefined = undefined;
let languages: Language[] | undefined = undefined;
try {
if (
!(await sendStatusReport(
@ -88,8 +90,8 @@ async function run() {
);
}
language = await determineAutobuildLanguage(config, featureFlags, logger);
if (language !== undefined) {
languages = await determineAutobuildLanguages(config, featureFlags, logger);
if (languages !== undefined) {
const workingDirectory = getOptionalInput("working-directory");
if (workingDirectory) {
logger.info(
@ -97,9 +99,12 @@ async function run() {
);
process.chdir(workingDirectory);
}
await runAutobuild(language, config, logger);
if (language === Language.go) {
core.exportVariable("CODEQL_ACTION_DID_AUTOBUILD_GOLANG", "true");
for (const language of languages) {
currentLanguage = language;
await runAutobuild(language, config, logger);
if (language === Language.go) {
core.exportVariable(DID_AUTOBUILD_GO_ENV_VAR_NAME, "true");
}
}
}
} catch (error) {
@ -111,14 +116,14 @@ async function run() {
console.log(error);
await sendCompletedStatusReport(
startedAt,
language ? [language] : [],
language,
languages ?? [],
currentLanguage,
error instanceof Error ? error : new Error(String(error))
);
return;
}
await sendCompletedStatusReport(startedAt, language ? [language] : []);
await sendCompletedStatusReport(startedAt, languages ?? []);
}
async function runWrapper() {

View file

@ -5,11 +5,11 @@ import { Language, isTracedLanguage } from "./languages";
import { Logger } from "./logging";
import * as util from "./util";
export async function determineAutobuildLanguage(
export async function determineAutobuildLanguages(
config: configUtils.Config,
featureFlags: FeatureFlags,
logger: Logger
): Promise<Language | undefined> {
): Promise<Language[] | undefined> {
const isGoExtractionReconciliationEnabled =
await util.isGoExtractionReconciliationEnabled(featureFlags);
// Attempt to find a language to autobuild
@ -19,26 +19,81 @@ export async function determineAutobuildLanguage(
const autobuildLanguages = config.languages.filter((l) =>
isTracedLanguage(l, isGoExtractionReconciliationEnabled, logger)
);
const language = autobuildLanguages[0];
if (!language) {
if (!autobuildLanguages) {
logger.info(
"None of the languages in this project require extra build steps"
);
return undefined;
}
logger.debug(`Detected dominant traced language: ${language}`);
/**
* Additionally autobuild Go in the autobuild Action to ensure backwards
* compatibility for users performing a multi-language build within a single
* job.
*
* For example, consider a user with the following workflow file:
*
* ```yml
* - uses: github/codeql-action/init@v2
* with:
* languages: go, java
* - uses: github/codeql-action/autobuild@v2
* - uses: github/codeql-action/analyze@v2
* ```
*
* - With Go extraction disabled, we will run the Java autobuilder in the
* autobuild Action, ensuring we extract both Java and Go code.
* - With Go extraction enabled, taking the previous behavior we'd run the Go
* autobuilder, since Go is first on the list of languages. We wouldn't run
* the Java autobuilder at all and so we'd only extract Go code.
*
* We therefore introduce a special case here such that we'll autobuild Go
* in addition to the primary non-Go traced language in the autobuild Action.
*
* This special case behavior should be removed as part of the next major
* version of the CodeQL Action.
*/
const autobuildLanguagesWithoutGo = autobuildLanguages.filter(
(l) => l !== Language.go
);
if (autobuildLanguages.length > 1) {
const languages: Language[] = [];
// First run the autobuilder for the first non-Go traced language, if one
// exists.
if (autobuildLanguagesWithoutGo[0] !== undefined) {
languages.push(autobuildLanguagesWithoutGo[0]);
}
// If Go is requested, run the Go autobuilder last to ensure it doesn't
// interfere with the other autobuilder.
if (autobuildLanguages.length !== autobuildLanguagesWithoutGo.length) {
languages.push(Language.go);
}
logger.debug(`Will autobuild ${languages.join(" and ")}.`);
// In general the autobuilders for other traced languages may conflict with
// each other. Therefore if a user has requested more than one non-Go traced
// language, we ask for manual build steps.
// Matrixing the build would also work, but that would change the SARIF
// categories, potentially leading to a "stale tips" situation where alerts
// that should be fixed remain on a repo since they are linked to SARIF
// categories that are no longer updated.
if (autobuildLanguagesWithoutGo.length > 1) {
logger.warning(
`We will only automatically build ${language} code. If you wish to scan ${autobuildLanguages
`We will only automatically build ${languages.join(
" and "
)} code. If you wish to scan ${autobuildLanguagesWithoutGo
.slice(1)
.join(" and ")}, you must replace this call with custom build steps.`
.join(
" and "
)}, you must replace the autobuild step of your workflow with custom build steps. ` +
"For more information, see " +
"https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language"
);
}
return language;
return languages;
}
export async function runAutobuild(

View file

@ -5,7 +5,7 @@ import { Command } from "commander";
import del from "del";
import { runFinalize, runQueries } from "./analyze";
import { determineAutobuildLanguage, runAutobuild } from "./autobuild";
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import { CodeQL, CODEQL_VERSION_NEW_TRACING, getCodeQL } from "./codeql";
import { Config, getConfig } from "./config-utils";
import { createFeatureFlags } from "./feature-flags";
@ -362,9 +362,9 @@ program
}
await enrichEnvironment(Mode.runner, await getCodeQL(config.codeQLCmd));
importTracerEnvironment(config);
let language: Language | undefined = undefined;
let languages: Language[] | undefined = undefined;
if (cmd.language !== undefined) {
language = parseLanguage(cmd.language);
const language = parseLanguage(cmd.language);
if (language === undefined || !config.languages.includes(language)) {
throw new Error(
`"${cmd.language}" is not a recognised language. ` +
@ -373,15 +373,18 @@ program
)}.`
);
}
languages = [language];
} else {
language = await determineAutobuildLanguage(
languages = await determineAutobuildLanguages(
config,
createFeatureFlags([]),
logger
);
}
if (language !== undefined) {
await runAutobuild(language, config, logger);
if (languages !== undefined) {
for (const language of languages) {
await runAutobuild(language, config, logger);
}
}
} catch (e) {
logger.error("Autobuild failed");

View file

@ -45,6 +45,13 @@ export const DEFAULT_DEBUG_ARTIFACT_NAME = "debug-artifacts";
*/
export const DEFAULT_DEBUG_DATABASE_NAME = "db";
/**
* Environment variable that is set to "true" when the CodeQL Action has invoked
* the Go autobuilder.
*/
export const DID_AUTOBUILD_GO_ENV_VAR_NAME =
"CODEQL_ACTION_DID_AUTOBUILD_GOLANG";
export interface SarifFile {
version?: string | null;
runs: Array<{