1 | # @file CodeQlBuildPlugin.py
|
---|
2 | #
|
---|
3 | # A build plugin that produces CodeQL results for the present build.
|
---|
4 | #
|
---|
5 | # Copyright (c) Microsoft Corporation. All rights reserved.
|
---|
6 | # SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
7 | ##
|
---|
8 |
|
---|
9 | import glob
|
---|
10 | import logging
|
---|
11 | import os
|
---|
12 | import stat
|
---|
13 | from common import codeql_plugin
|
---|
14 | from pathlib import Path
|
---|
15 |
|
---|
16 | from edk2toolext import edk2_logging
|
---|
17 | from edk2toolext.environment.plugintypes.uefi_build_plugin import \
|
---|
18 | IUefiBuildPlugin
|
---|
19 | from edk2toolext.environment.uefi_build import UefiBuilder
|
---|
20 | from edk2toollib.uefi.edk2.path_utilities import Edk2Path
|
---|
21 | from edk2toollib.utility_functions import GetHostInfo, RemoveTree
|
---|
22 |
|
---|
23 |
|
---|
24 | class CodeQlBuildPlugin(IUefiBuildPlugin):
|
---|
25 |
|
---|
26 | def do_pre_build(self, builder: UefiBuilder) -> int:
|
---|
27 | """CodeQL pre-build functionality.
|
---|
28 |
|
---|
29 | Args:
|
---|
30 | builder (UefiBuilder): A UEFI builder object for this build.
|
---|
31 |
|
---|
32 | Returns:
|
---|
33 | int: The plugin return code. Zero indicates the plugin ran
|
---|
34 | successfully. A non-zero value indicates an unexpected error
|
---|
35 | occurred during plugin execution.
|
---|
36 | """
|
---|
37 |
|
---|
38 | if not builder.SkipBuild:
|
---|
39 | self.builder = builder
|
---|
40 | self.package = builder.edk2path.GetContainingPackage(
|
---|
41 | builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
|
---|
42 | builder.env.GetValue("ACTIVE_PLATFORM")
|
---|
43 | )
|
---|
44 | )
|
---|
45 |
|
---|
46 | self.target = builder.env.GetValue("TARGET")
|
---|
47 |
|
---|
48 | self.build_output_dir = builder.env.GetValue("BUILD_OUTPUT_BASE")
|
---|
49 |
|
---|
50 | self.codeql_db_path = codeql_plugin.get_codeql_db_path(
|
---|
51 | builder.ws, self.package, self.target)
|
---|
52 |
|
---|
53 | edk2_logging.log_progress(f"{self.package} will be built for CodeQL")
|
---|
54 | edk2_logging.log_progress(f" CodeQL database will be written to "
|
---|
55 | f"{self.codeql_db_path}")
|
---|
56 |
|
---|
57 | self.codeql_path = codeql_plugin.get_codeql_cli_path()
|
---|
58 | if not self.codeql_path:
|
---|
59 | logging.critical("CodeQL build enabled but CodeQL CLI application "
|
---|
60 | "not found.")
|
---|
61 | return -1
|
---|
62 |
|
---|
63 | # CodeQL can only generate a database on clean build
|
---|
64 | #
|
---|
65 | # Note: builder.CleanTree() cannot be used here as some platforms
|
---|
66 | # have build steps that run before this plugin that store
|
---|
67 | # files in the build output directory.
|
---|
68 | #
|
---|
69 | # CodeQL does not care about with those files or many others such
|
---|
70 | # as the FV directory, build logs, etc. so instead focus on
|
---|
71 | # removing only the directories with compilation/linker output
|
---|
72 | # for the architectures being built (that need clean runs for
|
---|
73 | # CodeQL to work).
|
---|
74 | targets = self.builder.env.GetValue("TARGET_ARCH").split(" ")
|
---|
75 | for target in targets:
|
---|
76 | directory_to_delete = Path(self.build_output_dir, target)
|
---|
77 |
|
---|
78 | if directory_to_delete.is_dir():
|
---|
79 | logging.debug(f"Removing {str(directory_to_delete)} to have a "
|
---|
80 | f"clean build for CodeQL.")
|
---|
81 | RemoveTree(str(directory_to_delete))
|
---|
82 |
|
---|
83 | # CodeQL CLI does not handle spaces passed in CLI commands well
|
---|
84 | # (perhaps at all) as discussed here:
|
---|
85 | # 1. https://github.com/github/codeql-cli-binaries/issues/73
|
---|
86 | # 2. https://github.com/github/codeql/issues/4910
|
---|
87 | #
|
---|
88 | # Since it's unclear how quotes are handled and may change in the
|
---|
89 | # future, this code is going to use the workaround to place the
|
---|
90 | # command in an executable file that is instead passed to CodeQL.
|
---|
91 | self.codeql_cmd_path = Path(self.build_output_dir, "codeql_build_command")
|
---|
92 |
|
---|
93 | build_params = self._get_build_params()
|
---|
94 |
|
---|
95 | codeql_build_cmd = ""
|
---|
96 | if GetHostInfo().os == "Windows":
|
---|
97 | self.codeql_cmd_path = self.codeql_cmd_path.parent / (
|
---|
98 | self.codeql_cmd_path.name + '.bat')
|
---|
99 | elif GetHostInfo().os == "Linux":
|
---|
100 | self.codeql_cmd_path = self.codeql_cmd_path.parent / (
|
---|
101 | self.codeql_cmd_path.name + '.sh')
|
---|
102 | codeql_build_cmd += f"#!/bin/bash{os.linesep * 2}"
|
---|
103 | codeql_build_cmd += "build " + build_params
|
---|
104 |
|
---|
105 | self.codeql_cmd_path.parent.mkdir(exist_ok=True, parents=True)
|
---|
106 | self.codeql_cmd_path.write_text(encoding='utf8', data=codeql_build_cmd)
|
---|
107 |
|
---|
108 | if GetHostInfo().os == "Linux":
|
---|
109 | os.chmod(self.codeql_cmd_path,
|
---|
110 | os.stat(self.codeql_cmd_path).st_mode | stat.S_IEXEC)
|
---|
111 | for f in glob.glob(os.path.join(
|
---|
112 | os.path.dirname(self.codeql_path), '**/*'), recursive=True):
|
---|
113 | os.chmod(f, os.stat(f).st_mode | stat.S_IEXEC)
|
---|
114 |
|
---|
115 | codeql_params = (f'database create {self.codeql_db_path} '
|
---|
116 | f'--language=cpp '
|
---|
117 | f'--source-root={builder.ws} '
|
---|
118 | f'--command={self.codeql_cmd_path}')
|
---|
119 |
|
---|
120 | # Set environment variables so the CodeQL build command is picked up
|
---|
121 | # as the active build command.
|
---|
122 | #
|
---|
123 | # Note: Requires recent changes in edk2-pytool-extensions (0.20.0)
|
---|
124 | # to support reading these variables.
|
---|
125 | builder.env.SetValue(
|
---|
126 | "EDK_BUILD_CMD", self.codeql_path, "Set in CodeQL Build Plugin")
|
---|
127 | builder.env.SetValue(
|
---|
128 | "EDK_BUILD_PARAMS", codeql_params, "Set in CodeQL Build Plugin")
|
---|
129 |
|
---|
130 | return 0
|
---|
131 |
|
---|
132 | def _get_build_params(self) -> str:
|
---|
133 | """Returns the build command parameters for this build.
|
---|
134 |
|
---|
135 | Based on the well-defined `build` command-line parameters.
|
---|
136 |
|
---|
137 | Returns:
|
---|
138 | str: A string representing the parameters for the build command.
|
---|
139 | """
|
---|
140 | build_params = f"-p {self.builder.env.GetValue('ACTIVE_PLATFORM')}"
|
---|
141 | build_params += f" -b {self.target}"
|
---|
142 | build_params += f" -t {self.builder.env.GetValue('TOOL_CHAIN_TAG')}"
|
---|
143 |
|
---|
144 | max_threads = self.builder.env.GetValue('MAX_CONCURRENT_THREAD_NUMBER')
|
---|
145 | if max_threads is not None:
|
---|
146 | build_params += f" -n {max_threads}"
|
---|
147 |
|
---|
148 | rt = self.builder.env.GetValue("TARGET_ARCH").split(" ")
|
---|
149 | for t in rt:
|
---|
150 | build_params += " -a " + t
|
---|
151 |
|
---|
152 | if (self.builder.env.GetValue("BUILDREPORTING") == "TRUE"):
|
---|
153 | build_params += (" -y " +
|
---|
154 | self.builder.env.GetValue("BUILDREPORT_FILE"))
|
---|
155 | rt = self.builder.env.GetValue("BUILDREPORT_TYPES").split(" ")
|
---|
156 | for t in rt:
|
---|
157 | build_params += " -Y " + t
|
---|
158 |
|
---|
159 | # add special processing to handle building a single module
|
---|
160 | mod = self.builder.env.GetValue("BUILDMODULE")
|
---|
161 | if (mod is not None and len(mod.strip()) > 0):
|
---|
162 | build_params += " -m " + mod
|
---|
163 | edk2_logging.log_progress("Single Module Build: " + mod)
|
---|
164 |
|
---|
165 | build_vars = self.builder.env.GetAllBuildKeyValues(self.target)
|
---|
166 | for key, value in build_vars.items():
|
---|
167 | build_params += " -D " + key + "=" + value
|
---|
168 |
|
---|
169 | return build_params
|
---|