# @file CodeQlBuildPlugin.py | |
# | |
# A build plugin that produces CodeQL results for the present build. | |
# | |
# Copyright (c) Microsoft Corporation. All rights reserved. | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
## | |
import glob | |
import logging | |
import os | |
import stat | |
from common import codeql_plugin | |
from pathlib import Path | |
from edk2toolext import edk2_logging | |
from edk2toolext.environment.plugintypes.uefi_build_plugin import \ | |
IUefiBuildPlugin | |
from edk2toolext.environment.uefi_build import UefiBuilder | |
from edk2toollib.uefi.edk2.path_utilities import Edk2Path | |
from edk2toollib.utility_functions import GetHostInfo, RemoveTree | |
class CodeQlBuildPlugin(IUefiBuildPlugin): | |
def do_pre_build(self, builder: UefiBuilder) -> int: | |
"""CodeQL pre-build functionality. | |
Args: | |
builder (UefiBuilder): A UEFI builder object for this build. | |
Returns: | |
int: The plugin return code. Zero indicates the plugin ran | |
successfully. A non-zero value indicates an unexpected error | |
occurred during plugin execution. | |
""" | |
if not builder.SkipBuild: | |
self.builder = builder | |
self.package = builder.edk2path.GetContainingPackage( | |
builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath( | |
builder.env.GetValue("ACTIVE_PLATFORM") | |
) | |
) | |
self.target = builder.env.GetValue("TARGET") | |
self.build_output_dir = builder.env.GetValue("BUILD_OUTPUT_BASE") | |
self.codeql_db_path = codeql_plugin.get_codeql_db_path( | |
builder.ws, self.package, self.target) | |
edk2_logging.log_progress(f"{self.package} will be built for CodeQL") | |
edk2_logging.log_progress(f" CodeQL database will be written to " | |
f"{self.codeql_db_path}") | |
self.codeql_path = codeql_plugin.get_codeql_cli_path() | |
if not self.codeql_path: | |
logging.critical("CodeQL build enabled but CodeQL CLI application " | |
"not found.") | |
return -1 | |
# CodeQL can only generate a database on clean build | |
# | |
# Note: builder.CleanTree() cannot be used here as some platforms | |
# have build steps that run before this plugin that store | |
# files in the build output directory. | |
# | |
# CodeQL does not care about with those files or many others such | |
# as the FV directory, build logs, etc. so instead focus on | |
# removing only the directories with compilation/linker output | |
# for the architectures being built (that need clean runs for | |
# CodeQL to work). | |
targets = self.builder.env.GetValue("TARGET_ARCH").split(" ") | |
for target in targets: | |
directory_to_delete = Path(self.build_output_dir, target) | |
if directory_to_delete.is_dir(): | |
logging.debug(f"Removing {str(directory_to_delete)} to have a " | |
f"clean build for CodeQL.") | |
RemoveTree(str(directory_to_delete)) | |
# CodeQL CLI does not handle spaces passed in CLI commands well | |
# (perhaps at all) as discussed here: | |
# 1. https://github.com/github/codeql-cli-binaries/issues/73 | |
# 2. https://github.com/github/codeql/issues/4910 | |
# | |
# Since it's unclear how quotes are handled and may change in the | |
# future, this code is going to use the workaround to place the | |
# command in an executable file that is instead passed to CodeQL. | |
self.codeql_cmd_path = Path(self.build_output_dir, "codeql_build_command") | |
build_params = self._get_build_params() | |
codeql_build_cmd = "" | |
if GetHostInfo().os == "Windows": | |
self.codeql_cmd_path = self.codeql_cmd_path.parent / ( | |
self.codeql_cmd_path.name + '.bat') | |
elif GetHostInfo().os == "Linux": | |
self.codeql_cmd_path = self.codeql_cmd_path.parent / ( | |
self.codeql_cmd_path.name + '.sh') | |
codeql_build_cmd += f"#!/bin/bash{os.linesep * 2}" | |
codeql_build_cmd += "build " + build_params | |
self.codeql_cmd_path.parent.mkdir(exist_ok=True, parents=True) | |
self.codeql_cmd_path.write_text(encoding='utf8', data=codeql_build_cmd) | |
if GetHostInfo().os == "Linux": | |
os.chmod(self.codeql_cmd_path, | |
os.stat(self.codeql_cmd_path).st_mode | stat.S_IEXEC) | |
for f in glob.glob(os.path.join( | |
os.path.dirname(self.codeql_path), '**/*'), recursive=True): | |
os.chmod(f, os.stat(f).st_mode | stat.S_IEXEC) | |
codeql_params = (f'database create {self.codeql_db_path} ' | |
f'--language=cpp ' | |
f'--source-root={builder.ws} ' | |
f'--command={self.codeql_cmd_path}') | |
# Set environment variables so the CodeQL build command is picked up | |
# as the active build command. | |
# | |
# Note: Requires recent changes in edk2-pytool-extensions (0.20.0) | |
# to support reading these variables. | |
builder.env.SetValue( | |
"EDK_BUILD_CMD", self.codeql_path, "Set in CodeQL Build Plugin") | |
builder.env.SetValue( | |
"EDK_BUILD_PARAMS", codeql_params, "Set in CodeQL Build Plugin") | |
return 0 | |
def _get_build_params(self) -> str: | |
"""Returns the build command parameters for this build. | |
Based on the well-defined `build` command-line parameters. | |
Returns: | |
str: A string representing the parameters for the build command. | |
""" | |
build_params = f"-p {self.builder.env.GetValue('ACTIVE_PLATFORM')}" | |
build_params += f" -b {self.target}" | |
build_params += f" -t {self.builder.env.GetValue('TOOL_CHAIN_TAG')}" | |
max_threads = self.builder.env.GetValue('MAX_CONCURRENT_THREAD_NUMBER') | |
if max_threads is not None: | |
build_params += f" -n {max_threads}" | |
rt = self.builder.env.GetValue("TARGET_ARCH").split(" ") | |
for t in rt: | |
build_params += " -a " + t | |
if (self.builder.env.GetValue("BUILDREPORTING") == "TRUE"): | |
build_params += (" -y " + | |
self.builder.env.GetValue("BUILDREPORT_FILE")) | |
rt = self.builder.env.GetValue("BUILDREPORT_TYPES").split(" ") | |
for t in rt: | |
build_params += " -Y " + t | |
# add special processing to handle building a single module | |
mod = self.builder.env.GetValue("BUILDMODULE") | |
if (mod is not None and len(mod.strip()) > 0): | |
build_params += " -m " + mod | |
edk2_logging.log_progress("Single Module Build: " + mod) | |
build_vars = self.builder.env.GetAllBuildKeyValues(self.target) | |
for key, value in build_vars.items(): | |
build_params += " -D " + key + "=" + value | |
return build_params |