mirror of
https://github.com/github/codeql-action.git
synced 2025-12-27 01:30:10 +08:00
Merge pull request #1358 from github/henrymercer/require-cli-2.6.3
Bump minimum CodeQL bundle version to 2.6.3
This commit is contained in:
@@ -3,7 +3,6 @@ import * as path from "path";
|
||||
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import * as yaml from "js-yaml";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import {
|
||||
convertPackToQuerySuiteEntry,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
} from "./analyze";
|
||||
import { setCodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import * as count from "./count-loc";
|
||||
import { Language } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { setupTests, setupActionsVars, createFeatures } from "./testing-utils";
|
||||
@@ -25,12 +23,6 @@ setupTests(test);
|
||||
// and correct case of builtin or custom. Also checks the correct search
|
||||
// paths are set in the database analyze invocation.
|
||||
test("status report fields and search path setting", async (t) => {
|
||||
const mockLinesOfCode = Object.values(Language).reduce((obj, lang, i) => {
|
||||
// use a different line count for each language
|
||||
obj[lang] = i + 1;
|
||||
return obj;
|
||||
}, {});
|
||||
sinon.stub(count, "countLoc").resolves(mockLinesOfCode);
|
||||
let searchPathsUsed: Array<string | undefined> = [];
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -96,6 +88,7 @@ test("status report fields and search path setting", async (t) => {
|
||||
);
|
||||
return "";
|
||||
},
|
||||
databasePrintBaseline: async () => "",
|
||||
});
|
||||
|
||||
searchPathsUsed = [];
|
||||
@@ -202,35 +195,9 @@ test("status report fields and search path setting", async (t) => {
|
||||
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
|
||||
}
|
||||
|
||||
verifyLineCounts(tmpDir);
|
||||
verifyQuerySuites(tmpDir);
|
||||
});
|
||||
|
||||
function verifyLineCounts(tmpDir: string) {
|
||||
// eslint-disable-next-line github/array-foreach
|
||||
Object.keys(Language).forEach((lang, i) => {
|
||||
verifyLineCountForFile(path.join(tmpDir, `${lang}.sarif`), i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function verifyLineCountForFile(filePath: string, lineCount: number) {
|
||||
const sarif = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
t.deepEqual(sarif.runs[0].properties.metricResults, [
|
||||
{
|
||||
rule: {
|
||||
index: 0,
|
||||
toolComponent: {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
value: 123,
|
||||
baseline: lineCount,
|
||||
},
|
||||
]);
|
||||
// when the rule doesn't exist, it should not be added
|
||||
t.deepEqual(sarif.runs[1].properties.metricResults, []);
|
||||
}
|
||||
|
||||
function verifyQuerySuites(tmpDir: string) {
|
||||
const qlsContent = [
|
||||
{
|
||||
|
||||
@@ -8,14 +8,8 @@ import * as yaml from "js-yaml";
|
||||
|
||||
import { DatabaseCreationTimings } from "./actions-util";
|
||||
import * as analysisPaths from "./analysis-paths";
|
||||
import {
|
||||
CodeQL,
|
||||
CODEQL_VERSION_COUNTS_LINES,
|
||||
CODEQL_VERSION_NEW_TRACING,
|
||||
getCodeQL,
|
||||
} from "./codeql";
|
||||
import { CodeQL, CODEQL_VERSION_NEW_TRACING, getCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { countLoc } from "./count-loc";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import { isScannedLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
@@ -218,26 +212,6 @@ export async function runQueries(
|
||||
): Promise<QueriesStatusReport> {
|
||||
const statusReport: QueriesStatusReport = {};
|
||||
|
||||
let locPromise: Promise<Partial<Record<Language, number>>> = Promise.resolve(
|
||||
{}
|
||||
);
|
||||
const cliCanCountBaseline = await cliCanCountLoC();
|
||||
const countLocDebugMode =
|
||||
process.env["INTERNAL_CODEQL_ACTION_DEBUG_LOC"] || config.debugMode;
|
||||
if (!cliCanCountBaseline || countLocDebugMode) {
|
||||
// count the number of lines in the background
|
||||
locPromise = countLoc(
|
||||
path.resolve(),
|
||||
// config.paths specifies external directories. the current
|
||||
// directory is included in the analysis by default. Replicate
|
||||
// that here.
|
||||
config.paths,
|
||||
config.pathsIgnore,
|
||||
config.languages,
|
||||
logger
|
||||
);
|
||||
}
|
||||
|
||||
const codeql = await getCodeQL(config.codeQLCmd);
|
||||
|
||||
await util.logCodeScanningConfigInCli(codeql, featureEnablement, logger);
|
||||
@@ -255,13 +229,13 @@ export async function runQueries(
|
||||
|
||||
if (!hasBuiltinQueries && !hasCustomQueries && !hasPackWithCustomQueries) {
|
||||
throw new Error(
|
||||
`Unable to analyse ${language} as no queries were selected for this language`
|
||||
`Unable to analyze ${language} as no queries were selected for this language`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (await util.useCodeScanningConfigInCli(codeql, featureEnablement)) {
|
||||
// If we are using the codescanning config in the CLI,
|
||||
// If we are using the code scanning config in the CLI,
|
||||
// much of the work needed to generate the query suites
|
||||
// is done in the CLI. We just need to make a single
|
||||
// call to run all the queries for each language and
|
||||
@@ -346,20 +320,12 @@ export async function runQueries(
|
||||
sarifFile,
|
||||
config.debugMode
|
||||
);
|
||||
if (!cliCanCountBaseline) {
|
||||
await injectLinesOfCode(sarifFile, language, locPromise);
|
||||
}
|
||||
statusReport[`interpret_results_${language}_duration_ms`] =
|
||||
new Date().getTime() - startTimeInterpretResults;
|
||||
logger.endGroup();
|
||||
logger.info(analysisSummary);
|
||||
}
|
||||
if (!cliCanCountBaseline || countLocDebugMode) {
|
||||
printLinesOfCodeSummary(logger, language, await locPromise);
|
||||
}
|
||||
if (cliCanCountBaseline) {
|
||||
logger.info(await runPrintLinesOfCode(language));
|
||||
}
|
||||
logger.info(await runPrintLinesOfCode(language));
|
||||
} catch (e) {
|
||||
logger.info(String(e));
|
||||
if (e instanceof Error) {
|
||||
@@ -394,13 +360,6 @@ export async function runQueries(
|
||||
);
|
||||
}
|
||||
|
||||
async function cliCanCountLoC() {
|
||||
return await util.codeQlVersionAbove(
|
||||
await getCodeQL(config.codeQLCmd),
|
||||
CODEQL_VERSION_COUNTS_LINES
|
||||
);
|
||||
}
|
||||
|
||||
async function runPrintLinesOfCode(language: Language): Promise<string> {
|
||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||
return await codeql.databasePrintBaseline(databasePath);
|
||||
@@ -548,50 +507,6 @@ export async function runCleanup(
|
||||
logger.endGroup();
|
||||
}
|
||||
|
||||
async function injectLinesOfCode(
|
||||
sarifFile: string,
|
||||
language: Language,
|
||||
locPromise: Promise<Partial<Record<Language, number>>>
|
||||
) {
|
||||
const lineCounts = await locPromise;
|
||||
if (language in lineCounts) {
|
||||
const sarif = JSON.parse(fs.readFileSync(sarifFile, "utf8"));
|
||||
|
||||
if (Array.isArray(sarif.runs)) {
|
||||
for (const run of sarif.runs) {
|
||||
run.properties = run.properties || {};
|
||||
run.properties.metricResults = run.properties.metricResults || [];
|
||||
for (const metric of run.properties.metricResults) {
|
||||
// Baseline is inserted when matching rule has tag lines-of-code
|
||||
if (metric.rule && metric.rule.toolComponent) {
|
||||
const matchingRule =
|
||||
run.tool.extensions[metric.rule.toolComponent.index].rules[
|
||||
metric.rule.index
|
||||
];
|
||||
if (matchingRule.properties.tags?.includes("lines-of-code")) {
|
||||
metric.baseline = lineCounts[language];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(sarifFile, JSON.stringify(sarif));
|
||||
}
|
||||
}
|
||||
|
||||
function printLinesOfCodeSummary(
|
||||
logger: Logger,
|
||||
language: Language,
|
||||
lineCounts: Partial<Record<Language, number>>
|
||||
) {
|
||||
if (language in lineCounts) {
|
||||
logger.info(
|
||||
`Counted a baseline of ${lineCounts[language]} lines of code for ${language}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// exported for testing
|
||||
export function validateQueryFilters(queryFilters?: configUtils.QueryFilter[]) {
|
||||
if (!queryFilters) {
|
||||
|
||||
@@ -232,26 +232,19 @@ export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||
|
||||
/**
|
||||
* The oldest version of CodeQL that the Action will run with. This should be
|
||||
* at least three minor versions behind the current version. The version flags
|
||||
* below can be used to conditionally enable certain features on versions newer
|
||||
* than this. Please record the reason we cannot support an older version.
|
||||
* at least three minor versions behind the current version and must include the
|
||||
* CLI versions shipped with each supported version of GHES.
|
||||
*
|
||||
* Reason: First version containing fix for the "We still have not reached
|
||||
* idleness" deadlock.
|
||||
* The version flags below can be used to conditionally enable certain features
|
||||
* on versions newer than this.
|
||||
*/
|
||||
const CODEQL_MINIMUM_VERSION = "2.4.5";
|
||||
const CODEQL_MINIMUM_VERSION = "2.6.3";
|
||||
|
||||
/**
|
||||
* Versions of CodeQL that version-flag certain functionality in the Action.
|
||||
* For convenience, please keep these in descending order. Once a version
|
||||
* flag is older than the oldest supported version above, it may be removed.
|
||||
*/
|
||||
const CODEQL_VERSION_RAM_FINALIZE = "2.5.8";
|
||||
const CODEQL_VERSION_DIAGNOSTICS = "2.5.6";
|
||||
const CODEQL_VERSION_METRICS = "2.5.5";
|
||||
const CODEQL_VERSION_GROUP_RULES = "2.5.5";
|
||||
const CODEQL_VERSION_SARIF_GROUP = "2.5.3";
|
||||
export const CODEQL_VERSION_COUNTS_LINES = "2.6.2";
|
||||
const CODEQL_VERSION_CUSTOM_QUERY_HELP = "2.7.1";
|
||||
const CODEQL_VERSION_LUA_TRACER_CONFIG = "2.10.0";
|
||||
export const CODEQL_VERSION_CONFIG_FILES = "2.10.1";
|
||||
@@ -828,7 +821,7 @@ async function getCodeQLForCmd(
|
||||
}
|
||||
}
|
||||
|
||||
const configLocation = await generateCodescanningConfig(
|
||||
const configLocation = await generateCodeScanningConfig(
|
||||
codeql,
|
||||
config,
|
||||
featureEnablement
|
||||
@@ -947,11 +940,10 @@ async function getCodeQLForCmd(
|
||||
"finalize",
|
||||
"--finalize-dataset",
|
||||
threadsFlag,
|
||||
memoryFlag,
|
||||
...getExtraOptionsFromEnv(["database", "finalize"]),
|
||||
databasePath,
|
||||
];
|
||||
if (await util.codeQlVersionAbove(this, CODEQL_VERSION_RAM_FINALIZE))
|
||||
args.push(memoryFlag);
|
||||
await toolrunnerErrorCatcher(cmd, args, errorMatchers);
|
||||
},
|
||||
async resolveLanguages() {
|
||||
@@ -1054,20 +1046,14 @@ async function getCodeQLForCmd(
|
||||
verbosityFlag,
|
||||
`--output=${sarifFile}`,
|
||||
addSnippetsFlag,
|
||||
"--print-diagnostics-summary",
|
||||
"--print-metrics-summary",
|
||||
"--sarif-group-rules-by-pack",
|
||||
...getExtraOptionsFromEnv(["database", "interpret-results"]),
|
||||
];
|
||||
if (await util.codeQlVersionAbove(this, CODEQL_VERSION_DIAGNOSTICS))
|
||||
codeqlArgs.push("--print-diagnostics-summary");
|
||||
if (await util.codeQlVersionAbove(this, CODEQL_VERSION_METRICS))
|
||||
codeqlArgs.push("--print-metrics-summary");
|
||||
if (await util.codeQlVersionAbove(this, CODEQL_VERSION_GROUP_RULES))
|
||||
codeqlArgs.push("--sarif-group-rules-by-pack");
|
||||
if (await util.codeQlVersionAbove(this, CODEQL_VERSION_CUSTOM_QUERY_HELP))
|
||||
codeqlArgs.push("--sarif-add-query-help");
|
||||
if (
|
||||
automationDetailsId !== undefined &&
|
||||
(await util.codeQlVersionAbove(this, CODEQL_VERSION_SARIF_GROUP))
|
||||
) {
|
||||
if (automationDetailsId !== undefined) {
|
||||
codeqlArgs.push("--sarif-category", automationDetailsId);
|
||||
}
|
||||
if (
|
||||
@@ -1176,8 +1162,8 @@ async function getCodeQLForCmd(
|
||||
await new toolrunner.ToolRunner(cmd, args).exec();
|
||||
},
|
||||
};
|
||||
// To ensure that status reports include the CodeQL CLI version whereever
|
||||
// possbile, we want to call getVersion(), which populates the version value
|
||||
// To ensure that status reports include the CodeQL CLI version wherever
|
||||
// possible, we want to call getVersion(), which populates the version value
|
||||
// used by status reporting, at the earliest opportunity. But invoking
|
||||
// getVersion() directly here breaks tests that only pretend to create a
|
||||
// CodeQL object. So instead we rely on the assumption that all non-test
|
||||
@@ -1293,7 +1279,7 @@ async function runTool(cmd: string, args: string[] = []) {
|
||||
* @param config The configuration to use.
|
||||
* @returns the path to the generated user configuration file.
|
||||
*/
|
||||
async function generateCodescanningConfig(
|
||||
async function generateCodeScanningConfig(
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
featureEnablement: FeatureEnablement
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import * as path from "path";
|
||||
|
||||
import test from "ava";
|
||||
|
||||
import { countLoc } from "./count-loc";
|
||||
import { Language } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("ensure lines of code works for cpp and js", async (t) => {
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
[],
|
||||
[],
|
||||
[Language.cpp, Language.javascript],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {
|
||||
cpp: 6,
|
||||
javascript: 9,
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure lines of code works for csharp", async (t) => {
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
[],
|
||||
[],
|
||||
[Language.csharp],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {
|
||||
csharp: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure lines of code can handle undefined language", async (t) => {
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
[],
|
||||
[],
|
||||
[Language.javascript, Language.python, "hucairz" as Language],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {
|
||||
javascript: 9,
|
||||
python: 5,
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure lines of code can handle empty languages", async (t) => {
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {});
|
||||
});
|
||||
|
||||
test("ensure lines of code can handle includes", async (t) => {
|
||||
// note that "**" is always included. The includes are for extra
|
||||
// directories outside the normal structure.
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
["../../src/testdata"],
|
||||
[],
|
||||
[Language.javascript],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {
|
||||
javascript: 12,
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure lines of code can handle empty includes", async (t) => {
|
||||
// note that "**" is always included. The includes are for extra
|
||||
// directories outside the normal structure.
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
["idontexist"],
|
||||
[],
|
||||
[Language.javascript],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {
|
||||
// should get no results
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure lines of code can handle exclude", async (t) => {
|
||||
const results = await countLoc(
|
||||
path.join(__dirname, "../tests/multi-language-repo"),
|
||||
[],
|
||||
["**/*.py"],
|
||||
[Language.javascript, Language.python],
|
||||
getRunnerLogger(true)
|
||||
);
|
||||
|
||||
t.deepEqual(results, {
|
||||
javascript: 9,
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
import { LocDir } from "github-linguist";
|
||||
|
||||
import { Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
|
||||
// Map from linguist language names to language prefixes used in the action and codeql
|
||||
const linguistToMetrics: Record<string, Language> = {
|
||||
c: Language.cpp,
|
||||
"c++": Language.cpp,
|
||||
"c#": Language.csharp,
|
||||
go: Language.go,
|
||||
java: Language.java,
|
||||
javascript: Language.javascript,
|
||||
python: Language.python,
|
||||
ruby: Language.ruby,
|
||||
swift: Language.swift,
|
||||
typescript: Language.javascript,
|
||||
};
|
||||
|
||||
const nameToLinguist = Object.entries(linguistToMetrics).reduce(
|
||||
(obj, [key, name]) => {
|
||||
if (!obj[name]) {
|
||||
obj[name] = [];
|
||||
}
|
||||
obj[name].push(key);
|
||||
return obj;
|
||||
},
|
||||
{} as Record<Language, string[]>
|
||||
);
|
||||
|
||||
/**
|
||||
* Count the lines of code of the specified language using the include
|
||||
* and exclude glob paths.
|
||||
*
|
||||
* @param cwd the root directory to start the count from
|
||||
* @param include glob patterns to include in the search for relevant files
|
||||
* @param exclude glob patterns to exclude in the search for relevant files
|
||||
* @param dbLanguages list of languages to include in the results
|
||||
* @param logger object to log results
|
||||
*/
|
||||
export async function countLoc(
|
||||
cwd: string,
|
||||
include: string[],
|
||||
exclude: string[],
|
||||
dbLanguages: Language[],
|
||||
logger: Logger
|
||||
): Promise<Partial<Record<Language, number>>> {
|
||||
const result = await new LocDir({
|
||||
cwd,
|
||||
include: Array.isArray(include) && include.length > 0 ? include : ["**"],
|
||||
exclude,
|
||||
analysisLanguages: dbLanguages.flatMap((lang) => nameToLinguist[lang]),
|
||||
}).loadInfo();
|
||||
|
||||
// The analysis counts LoC in all languages. We need to
|
||||
// extract the languages we care about. Also, note that
|
||||
// the analysis uses slightly different names for language.
|
||||
const lineCounts = Object.entries(result.languages).reduce(
|
||||
(obj, [language, { code }]) => {
|
||||
const metricsLanguage = linguistToMetrics[language];
|
||||
if (metricsLanguage && dbLanguages.includes(metricsLanguage)) {
|
||||
obj[metricsLanguage] = code + (obj[metricsLanguage] || 0);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
{} as Record<Language, number>
|
||||
);
|
||||
|
||||
if (Object.keys(lineCounts).length) {
|
||||
logger.debug("Lines of code count:");
|
||||
for (const [language, count] of Object.entries(lineCounts)) {
|
||||
logger.debug(` ${language}: ${count}`);
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
"Could not determine the baseline lines of code count in this repository. " +
|
||||
"Because of this, it will not be possible to compare the lines " +
|
||||
"of code analyzed by code scanning with the baseline. This will not affect " +
|
||||
"the results produced by code scanning. If you have any questions, you can " +
|
||||
"raise an issue at https://github.com/github/codeql-action/issues. Please " +
|
||||
"include a link to the repository if public, or otherwise information about " +
|
||||
"the code scanning workflow you are using."
|
||||
);
|
||||
}
|
||||
|
||||
return lineCounts;
|
||||
}
|
||||
Reference in New Issue
Block a user