diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..72ece9dcb4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,338 @@ +# This workflow runs CodeQL against the repository. +# +# Results are uploaded to GitHub Code Scanning. +# +# Due to a known issue with the CodeQL extractor when building the edk2 +# codebase on Linux systems, only Windows agents are used for build with +# the VS toolchain. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent + +name: "CodeQL" + +on: + push: + branches: + - master + pull_request: + branches: + - master + paths-ignore: + - '!**.c' + - '!**.h' + +jobs: + analyze: + name: Analyze + runs-on: windows-2019 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + include: + - Package: "ArmPkg" + ArchList: "IA32,X64" + - Package: "CryptoPkg" + ArchList: "IA32" + - Package: "CryptoPkg" + ArchList: "X64" + - Package: "DynamicTablesPkg" + ArchList: "IA32,X64" + - Package: "FatPkg" + ArchList: "IA32,X64" + - Package: "FmpDevicePkg" + ArchList: "IA32,X64" + - Package: "IntelFsp2Pkg" + ArchList: "IA32,X64" + - Package: "IntelFsp2WrapperPkg" + ArchList: "IA32,X64" + - Package: "MdeModulePkg" + ArchList: "IA32" + - Package: "MdeModulePkg" + ArchList: "X64" + - Package: "MdePkg" + ArchList: "IA32,X64" + - Package: "PcAtChipsetPkg" + ArchList: "IA32,X64" + - Package: "PrmPkg" + ArchList: "IA32,X64" + - Package: "SecurityPkg" + ArchList: "IA32,X64" + - Package: "ShellPkg" + ArchList: "IA32,X64" + - Package: "SourceLevelDebugPkg" + ArchList: "IA32,X64" + - Package: "StandaloneMmPkg" + ArchList: "IA32,X64" + - Package: "UefiCpuPkg" + ArchList: "IA32,X64" + - Package: "UnitTestFrameworkPkg" + ArchList: "IA32,X64" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'pip' + cache-dependency-path: 'pip-requirements.txt' + + - name: Use Git Long Paths on Windows + if: runner.os == 'Windows' + shell: pwsh + run: | + git config --system core.longpaths true + + - name: Install/Upgrade pip Modules + run: pip install -r pip-requirements.txt --upgrade requests + + - name: Determine CI Settings File Supported Operations + id: get_ci_file_operations + shell: python + run: | + import importlib + import os + import sys + from pathlib import Path + from edk2toolext.invocables.edk2_ci_setup import CiSetupSettingsManager + from edk2toolext.invocables.edk2_setup import SetupSettingsManager + + # Find the repo CI Settings file + ci_settings_file = list(Path(os.environ['GITHUB_WORKSPACE']).rglob('.pytool/CISettings.py')) + + # Note: At this point, submodules have not been pulled, only one CI Settings file should exist + if len(ci_settings_file) != 1 or not ci_settings_file[0].is_file(): + print("::error title=Workspace Error!::Failed to find CI Settings file!") + sys.exit(1) + + ci_settings_file = ci_settings_file[0] + + # Try Finding the Settings class in the file + module_name = 'ci_settings' + + spec = importlib.util.spec_from_file_location(module_name, ci_settings_file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + try: + settings = getattr(module, 'Settings') + except AttributeError: + print("::error title=Workspace Error!::Failed to find Settings class in CI Settings file!") + sys.exit(1) + + # Determine Which Operations Are Supported by the Settings Class + ci_setup_supported = issubclass(settings, CiSetupSettingsManager) + setup_supported = issubclass(settings, SetupSettingsManager) + + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'ci_setup_supported={str(ci_setup_supported).lower()}', file=fh) + print(f'setup_supported={str(setup_supported).lower()}', file=fh) + + - name: Setup + if: steps.get_ci_file_operations.outputs.setup_supported == 'true' + run: stuart_setup -c .pytool/CISettings.py -t DEBUG -a ${{ matrix.ArchList }} TOOL_CHAIN_TAG=VS2019 + + - name: Upload Setup Log As An Artifact + uses: actions/upload-artifact@v3 + if: (success() || failure()) && steps.get_ci_file_operations.outputs.setup_supported == 'true' + with: + name: ${{ matrix.Package }}-Logs + path: | + **/SETUPLOG.txt + retention-days: 7 + if-no-files-found: ignore + + - name: CI Setup + if: steps.get_ci_file_operations.outputs.ci_setup_supported == 'true' + run: stuart_ci_setup -c .pytool/CISettings.py -t DEBUG -a ${{ matrix.ArchList }} TOOL_CHAIN_TAG=VS2019 + + - name: Upload CI Setup Log As An Artifact + uses: actions/upload-artifact@v3 + if: (success() || failure()) && steps.get_ci_file_operations.outputs.ci_setup_supported == 'true' + with: + name: ${{ matrix.Package }}-Logs + path: | + **/CISETUP.txt + retention-days: 7 + if-no-files-found: ignore + + - name: Update + run: stuart_update -c .pytool/CISettings.py -t DEBUG -a ${{ matrix.ArchList }} TOOL_CHAIN_TAG=VS2019 + + - name: Upload Update Log As An Artifact + uses: actions/upload-artifact@v3 + if: success() || failure() + with: + name: ${{ matrix.Package }}-Logs + path: | + **/UPDATE_LOG.txt + retention-days: 7 + if-no-files-found: ignore + + - name: Build Tools From Source + run: python BaseTools/Edk2ToolsBuild.py -t VS2019 + + - name: Find CodeQL Plugin Directory + id: find_dir + shell: python + run: | + import os + import sys + from pathlib import Path + + # Find the plugin directory that contains the CodeQL plugin + plugin_dir = list(Path(os.environ['GITHUB_WORKSPACE']).rglob('BaseTools/Plugin/CodeQL')) + + # This should only be found once + if len(plugin_dir) == 1: + plugin_dir = str(plugin_dir[0]) + + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'codeql_plugin_dir={plugin_dir}', file=fh) + else: + print("::error title=Workspace Error!::Failed to find CodeQL plugin directory!") + sys.exit(1) + + - name: Get CodeQL CLI Cache Data + id: cache_key_gen + env: + CODEQL_PLUGIN_DIR: ${{ steps.find_dir.outputs.codeql_plugin_dir }} + shell: python + run: | + import os + import yaml + + codeql_cli_ext_dep_name = 'codeqlcli_windows_ext_dep' + codeql_plugin_file = os.path.join(os.environ['CODEQL_PLUGIN_DIR'], codeql_cli_ext_dep_name + '.yaml') + + with open (codeql_plugin_file) as pf: + codeql_cli_ext_dep = yaml.safe_load(pf) + + cache_key_name = codeql_cli_ext_dep['name'] + cache_key_version = codeql_cli_ext_dep['version'] + cache_key = f'{cache_key_name}-{cache_key_version}' + + codeql_plugin_cli_ext_dep_dir = os.path.join(os.environ['CODEQL_PLUGIN_DIR'], codeql_cli_ext_dep['name'].strip() + '_extdep') + + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'codeql_cli_cache_key={cache_key}', file=fh) + print(f'codeql_cli_ext_dep_dir={codeql_plugin_cli_ext_dep_dir}', file=fh) + + - name: Attempt to Load CodeQL CLI From Cache + id: codeqlcli_cache + uses: actions/cache@v3 + with: + path: ${{ steps.cache_key_gen.outputs.codeql_cli_ext_dep_dir }} + key: ${{ steps.cache_key_gen.outputs.codeql_cli_cache_key }} + + - name: Download CodeQL CLI + if: steps.codeqlcli_cache.outputs.cache-hit != 'true' + run: stuart_update -c .pytool/CISettings.py -t DEBUG -a ${{ matrix.ArchList }} TOOL_CHAIN_TAG=VS2019 --codeql + + - name: Remove CI Plugins Irrelevant to CodeQL + shell: python + env: + CODEQL_PLUGIN_DIR: ${{ steps.find_dir.outputs.codeql_plugin_dir }} + run: | + import os + import shutil + from pathlib import Path + + # Only these two plugins are needed for CodeQL + plugins_to_keep = ['CompilerPlugin'] + + plugin_dir = Path('.pytool/Plugin').absolute() + if plugin_dir.is_dir(): + for dir in plugin_dir.iterdir(): + if str(dir.stem) not in plugins_to_keep: + shutil.rmtree(str(dir.absolute()), ignore_errors=True) + + - name: CI Build + env: + STUART_CODEQL_PATH: ${{ steps.cache_key_gen.outputs.codeql_cli_ext_dep_dir }} + run: stuart_ci_build -c .pytool/CISettings.py -t DEBUG -p ${{ matrix.Package }} -a ${{ matrix.ArchList }} TOOL_CHAIN_TAG=VS2019 --codeql + + - name: Build Cleanup + id: build_cleanup + shell: python + run: | + import os + import shutil + from pathlib import Path + + dirs_to_delete = ['ia32', 'x64', 'arm', 'aarch64'] + + def delete_dirs(path: Path): + if path.exists() and path.is_dir(): + if path.name.lower() in dirs_to_delete: + print(f'Removed {str(path)}') + shutil.rmtree(path) + return + + for child_dir in path.iterdir(): + delete_dirs(child_dir) + + build_path = Path(os.environ['GITHUB_WORKSPACE'], 'Build') + delete_dirs(build_path) + + - name: Upload Build Logs As An Artifact + uses: actions/upload-artifact@v3 + if: success() || failure() + with: + name: ${{ matrix.Package }}-Logs + path: | + **/BUILD_REPORT.TXT + **/OVERRIDELOG.TXT + **/BUILDLOG_*.md + **/BUILDLOG_*.txt + **/CI_*.md + **/CI_*.txt + retention-days: 7 + if-no-files-found: ignore + + - name: Prepare Env Data for CodeQL Upload + id: env_data + env: + PACKAGE_NAME: ${{ matrix.Package }} + shell: python + run: | + import os + + package = os.environ['PACKAGE_NAME'].strip().lower() + directory_name = 'codeql-analysis-' + package + '-debug' + file_name = 'codeql-db-' + package + '-debug-0.sarif' + sarif_path = os.path.join('Build', directory_name, file_name) + + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + if os.path.isfile(sarif_path): + print(f'upload_sarif_file=true', file=fh) + print(f'sarif_file_path={sarif_path}', file=fh) + else: + print(f'upload_sarif_file=false', file=fh) + + - name: Upload CodeQL Results (SARIF) As An Artifact + uses: actions/upload-artifact@v3 + if: steps.env_data.outputs.upload_sarif_file == 'true' + with: + name: ${{ matrix.Package }}-CodeQL-SARIF + path: ${{ steps.env_data.outputs.sarif_file_path }} + retention-days: 14 + if-no-files-found: warn + + - name: Upload CodeQL Results (SARIF) To GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v2 + if: steps.env_data.outputs.upload_sarif_file == 'true' + with: + # Path to SARIF file relative to the root of the repository. + sarif_file: ${{ steps.env_data.outputs.sarif_file_path }} + # Optional category for the results. Used to differentiate multiple results for one commit. + # Each package is a separate category. + category: ${{ matrix.Package }}