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
 |