summaryrefslogtreecommitdiffstats
path: root/BaseTools/Plugin/CodeQL/CodeQlBuildPlugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'BaseTools/Plugin/CodeQL/CodeQlBuildPlugin.py')
-rw-r--r--BaseTools/Plugin/CodeQL/CodeQlBuildPlugin.py169
1 files changed, 169 insertions, 0 deletions
diff --git a/BaseTools/Plugin/CodeQL/CodeQlBuildPlugin.py b/BaseTools/Plugin/CodeQL/CodeQlBuildPlugin.py
new file mode 100644
index 0000000000..2f6c928c21
--- /dev/null
+++ b/BaseTools/Plugin/CodeQL/CodeQlBuildPlugin.py
@@ -0,0 +1,169 @@
+# @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