mirror of
https://github.com/github/codeql-action.git
synced 2025-12-27 17:50:07 +08:00
288 lines
8.6 KiB
TypeScript
288 lines
8.6 KiB
TypeScript
import * as fs from "fs";
|
|
import * as path from "path";
|
|
|
|
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
|
import * as io from "@actions/io";
|
|
import * as yaml from "js-yaml";
|
|
|
|
import { getOptionalInput, isSelfHostedRunner } from "./actions-util";
|
|
import { GitHubApiDetails } from "./api-client";
|
|
import { CodeQL, setupCodeQL } from "./codeql";
|
|
import * as configUtils from "./config-utils";
|
|
import { CodeQLDefaultVersionInfo } from "./feature-flags";
|
|
import { KnownLanguage, Language } from "./languages";
|
|
import { Logger, withGroupAsync } from "./logging";
|
|
import { ToolsSource } from "./setup-codeql";
|
|
import { ZstdAvailability } from "./tar";
|
|
import { ToolsDownloadStatusReport } from "./tools-download";
|
|
import * as util from "./util";
|
|
|
|
export async function initCodeQL(
|
|
toolsInput: string | undefined,
|
|
apiDetails: GitHubApiDetails,
|
|
tempDir: string,
|
|
variant: util.GitHubVariant,
|
|
defaultCliVersion: CodeQLDefaultVersionInfo,
|
|
logger: Logger,
|
|
): Promise<{
|
|
codeql: CodeQL;
|
|
toolsDownloadStatusReport?: ToolsDownloadStatusReport;
|
|
toolsSource: ToolsSource;
|
|
toolsVersion: string;
|
|
zstdAvailability: ZstdAvailability;
|
|
}> {
|
|
logger.startGroup("Setup CodeQL tools");
|
|
const {
|
|
codeql,
|
|
toolsDownloadStatusReport,
|
|
toolsSource,
|
|
toolsVersion,
|
|
zstdAvailability,
|
|
} = await setupCodeQL(
|
|
toolsInput,
|
|
apiDetails,
|
|
tempDir,
|
|
variant,
|
|
defaultCliVersion,
|
|
logger,
|
|
true,
|
|
);
|
|
await codeql.printVersion();
|
|
logger.endGroup();
|
|
return {
|
|
codeql,
|
|
toolsDownloadStatusReport,
|
|
toolsSource,
|
|
toolsVersion,
|
|
zstdAvailability,
|
|
};
|
|
}
|
|
|
|
export async function initConfig(
|
|
inputs: configUtils.InitConfigInputs,
|
|
): Promise<configUtils.Config> {
|
|
return await withGroupAsync("Load language configuration", async () => {
|
|
return await configUtils.initConfig(inputs);
|
|
});
|
|
}
|
|
|
|
export async function runDatabaseInitCluster(
|
|
databaseInitEnvironment: Record<string, string | undefined>,
|
|
codeql: CodeQL,
|
|
config: configUtils.Config,
|
|
sourceRoot: string,
|
|
processName: string | undefined,
|
|
qlconfigFile: string | undefined,
|
|
logger: Logger,
|
|
): Promise<void> {
|
|
fs.mkdirSync(config.dbLocation, { recursive: true });
|
|
await configUtils.wrapEnvironment(
|
|
databaseInitEnvironment,
|
|
async () =>
|
|
await codeql.databaseInitCluster(
|
|
config,
|
|
sourceRoot,
|
|
processName,
|
|
qlconfigFile,
|
|
logger,
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check whether all query packs are compatible with the overlay analysis
|
|
* support in the CodeQL CLI. If the check fails, this function will log a
|
|
* warning and returns false.
|
|
*
|
|
* @param codeql A CodeQL instance.
|
|
* @param logger A logger.
|
|
* @returns `true` if all query packs are compatible with overlay analysis,
|
|
* `false` otherwise.
|
|
*/
|
|
export async function checkPacksForOverlayCompatibility(
|
|
codeql: CodeQL,
|
|
config: configUtils.Config,
|
|
logger: Logger,
|
|
): Promise<boolean> {
|
|
const codeQlOverlayVersion = (await codeql.getVersion()).overlayVersion;
|
|
if (codeQlOverlayVersion === undefined) {
|
|
logger.warning("The CodeQL CLI does not support overlay analysis.");
|
|
return false;
|
|
}
|
|
|
|
for (const language of config.languages) {
|
|
const suitePath = util.getGeneratedSuitePath(config, language);
|
|
const packDirs = await codeql.resolveQueriesStartingPacks([suitePath]);
|
|
if (
|
|
packDirs.some(
|
|
(packDir) =>
|
|
!checkPackForOverlayCompatibility(
|
|
packDir,
|
|
codeQlOverlayVersion,
|
|
logger,
|
|
),
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Interface for `qlpack.yml` file contents. */
|
|
interface QlPack {
|
|
buildMetadata?: string;
|
|
}
|
|
|
|
/**
|
|
* Check a single pack for its overlay compatibility. If the check fails, this
|
|
* function will log a warning and returns false.
|
|
*
|
|
* @param packDir Path to the directory containing the pack.
|
|
* @param codeQlOverlayVersion The overlay version of the CodeQL CLI.
|
|
* @param logger A logger.
|
|
* @returns `true` if the pack is compatible with overlay analysis, `false`
|
|
* otherwise.
|
|
*/
|
|
function checkPackForOverlayCompatibility(
|
|
packDir: string,
|
|
codeQlOverlayVersion: number,
|
|
logger: Logger,
|
|
): boolean {
|
|
try {
|
|
let qlpackPath = path.join(packDir, "qlpack.yml");
|
|
if (!fs.existsSync(qlpackPath)) {
|
|
qlpackPath = path.join(packDir, "codeql-pack.yml");
|
|
}
|
|
const qlpackContents = yaml.load(
|
|
fs.readFileSync(qlpackPath, "utf8"),
|
|
) as QlPack;
|
|
if (!qlpackContents.buildMetadata) {
|
|
// This is a source-only pack, and overlay compatibility checks apply only
|
|
// to precompiled packs.
|
|
return true;
|
|
}
|
|
|
|
const packInfoPath = path.join(packDir, ".packinfo");
|
|
if (!fs.existsSync(packInfoPath)) {
|
|
logger.warning(
|
|
`The query pack at ${packDir} does not have a .packinfo file, ` +
|
|
"so it cannot support overlay analysis. Recompiling the query pack " +
|
|
"with the latest CodeQL CLI should solve this problem.",
|
|
);
|
|
return false;
|
|
}
|
|
|
|
const packInfoFileContents = JSON.parse(
|
|
fs.readFileSync(packInfoPath, "utf8"),
|
|
);
|
|
const packOverlayVersion = packInfoFileContents.overlayVersion;
|
|
if (typeof packOverlayVersion !== "number") {
|
|
logger.warning(
|
|
`The .packinfo file for the query pack at ${packDir} ` +
|
|
"does not have the overlayVersion field, which indicates that " +
|
|
"the pack is not compatible with overlay analysis.",
|
|
);
|
|
return false;
|
|
}
|
|
|
|
if (packOverlayVersion !== codeQlOverlayVersion) {
|
|
logger.warning(
|
|
`The query pack at ${packDir} was compiled with ` +
|
|
`overlay version ${packOverlayVersion}, but the CodeQL CLI ` +
|
|
`supports overlay version ${codeQlOverlayVersion}. The ` +
|
|
"query pack needs to be recompiled to support overlay analysis.",
|
|
);
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
logger.warning(
|
|
`Error while checking pack at ${packDir} ` +
|
|
`for overlay compatibility: ${util.getErrorMessage(e)}`,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* If we are running python 3.12+ on windows, we need to switch to python 3.11.
|
|
* This check happens in a powershell script.
|
|
*/
|
|
export async function checkInstallPython311(
|
|
languages: Language[],
|
|
codeql: CodeQL,
|
|
) {
|
|
if (
|
|
languages.includes(KnownLanguage.python) &&
|
|
process.platform === "win32" &&
|
|
!(await codeql.getVersion()).features?.supportsPython312
|
|
) {
|
|
const script = path.resolve(
|
|
__dirname,
|
|
"../python-setup",
|
|
"check_python12.ps1",
|
|
);
|
|
await new toolrunner.ToolRunner(await io.which("powershell", true), [
|
|
script,
|
|
]).exec();
|
|
}
|
|
}
|
|
|
|
export function cleanupDatabaseClusterDirectory(
|
|
config: configUtils.Config,
|
|
logger: Logger,
|
|
options: { disableExistingDirectoryWarning?: boolean } = {},
|
|
// We can't stub the fs module in tests, so we allow the caller to override the rmSync function
|
|
// for testing.
|
|
rmSync = fs.rmSync,
|
|
): void {
|
|
if (
|
|
fs.existsSync(config.dbLocation) &&
|
|
(fs.statSync(config.dbLocation).isFile() ||
|
|
fs.readdirSync(config.dbLocation).length > 0)
|
|
) {
|
|
if (!options.disableExistingDirectoryWarning) {
|
|
logger.warning(
|
|
`The database cluster directory ${config.dbLocation} must be empty. Attempting to clean it up.`,
|
|
);
|
|
}
|
|
try {
|
|
rmSync(config.dbLocation, {
|
|
force: true,
|
|
maxRetries: 3,
|
|
recursive: true,
|
|
});
|
|
|
|
logger.info(
|
|
`Cleaned up database cluster directory ${config.dbLocation}.`,
|
|
);
|
|
} catch (e) {
|
|
const blurb = `The CodeQL Action requires an empty database cluster directory. ${
|
|
getOptionalInput("db-location")
|
|
? `This is currently configured to be ${config.dbLocation}. `
|
|
: `By default, this is located at ${config.dbLocation}. ` +
|
|
"You can customize it using the 'db-location' input to the init Action. "
|
|
}An attempt was made to clean up the directory, but this failed.`;
|
|
|
|
// Hosted runners are automatically cleaned up, so this error should not occur for hosted runners.
|
|
if (isSelfHostedRunner()) {
|
|
throw new util.ConfigurationError(
|
|
`${blurb} This can happen if another process is using the directory or the directory is owned by a different user. ` +
|
|
`Please clean up the directory manually and rerun the job. Details: ${util.getErrorMessage(
|
|
e,
|
|
)}`,
|
|
);
|
|
} else {
|
|
throw new Error(
|
|
`${blurb} This shouldn't typically happen on hosted runners. ` +
|
|
"If you are using an advanced setup, please check your workflow, otherwise we " +
|
|
`recommend rerunning the job. Details: ${util.getErrorMessage(e)}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|