Adds a CodeQL plugin that supports CodeQL in the build system. 1. CodeQlBuildPlugin - Generates a CodeQL database for a given build. 2. CodeQlAnalyzePlugin - Analyzes a CodeQL database and interprets results. 3. External dependencies - Assist with downloading the CodeQL CLI and making it available to the CodeQL plugins. 4. CodeQlQueries.qls - A C/C++ CodeQL query set run against the code. 5. Readme.md - A comprehensive readme file to help: - Platform integrators understand how to configure the plugin - Developers understand how to modify the plugin - Users understand how to use the plugin Read Readme.md for additional details. Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Rebecca Cran <rebecca@bsdio.com> Cc: Sean Brogan <sean.brogan@microsoft.com> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com> Reviewed-by: Yuwei Chen <yuwei.chen@intel.com> Reviewed-by: Sean Brogan <sean.brogan@microsoft.com> Acked-by: Laszlo Ersek <lersek@redhat.com> Acked-by: Michael D Kinney <michael.d.kinney@intel.com>
		
			
				
	
	
		
			170 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # @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
 |