Repo File Sync: synced file(s) with microsoft/mu_devops #3594
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This workflow runs CodeQL against the repository. | |
# | |
# Results are uploaded to GitHub Code Scanning. | |
# | |
# Note: Important: This file only works with "CI" builds. "Platform" builds are | |
# supported with the codeql-platform.yml file. | |
# | |
# Note: This workflow only supports Windows as CodeQL CLI has confirmed issues running | |
# against edk2-style codebases on Linux (only tested on Ubuntu). Therefore, this | |
# workflow is written only for Windows but could easily be adapted to run on Linux | |
# in the future if needed (e.g. swap out "windows" with agent OS var value, etc.). | |
# | |
# For details about the Linux issue see: https://github.com/github/codeql-action/issues/1338 | |
# | |
# NOTE: This file is automatically synchronized from Mu DevOps. Update the original file there | |
# instead of the file in this repo. | |
# | |
# - Mu DevOps Repo: https://github.com/microsoft/mu_devops | |
# - File Sync Settings: https://github.com/microsoft/mu_devops/blob/main/.sync/Files.yml | |
# | |
# Copyright (c) Microsoft Corporation. | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
name: "CodeQL" | |
on: | |
push: | |
branches: | |
- main | |
- release/* | |
- dev/* | |
pull_request: | |
branches: | |
- main | |
- release/* | |
- dev/* | |
paths-ignore: | |
- '!**.c' | |
- '!**.h' | |
jobs: | |
gather_packages: | |
name: Gather Repo Packages | |
runs-on: ubuntu-latest | |
outputs: | |
packages: ${{ steps.generate_matrix.outputs.packages }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Install Python | |
uses: actions/setup-python@v5 | |
with: | |
python-version: '3.12' | |
- name: Generate Package Matrix | |
id: generate_matrix | |
shell: python | |
run: | | |
import os | |
import json | |
packages = [d for d in os.listdir() if d.strip().lower().endswith('pkg')] | |
# Ensure the package can actually be built | |
for package in packages: | |
if not any(file.endswith('.dsc') for file in os.listdir(package)): | |
packages.remove(package) | |
packages.sort() | |
with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: | |
print(f'packages={json.dumps(packages)}', file=fh) | |
analyze: | |
name: Analyze | |
runs-on: windows-2022 | |
needs: | |
- gather_packages | |
permissions: | |
actions: read | |
contents: read | |
security-events: write | |
strategy: | |
fail-fast: false | |
matrix: | |
package: ${{ fromJson(needs.gather_packages.outputs.packages) }} | |
include: | |
- archs: IA32,X64 | |
- tool_chain_tag: VS2022 | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Install Python | |
uses: actions/setup-python@v5 | |
with: | |
python-version: '3.12' | |
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 CI Settings file (usually in .pytool/CISettings.py) | |
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: Get Cargo Tool Details | |
id: get_cargo_tool_details | |
shell: python | |
run: | | |
import os | |
import requests | |
import sys | |
import time | |
def get_response_with_retries(url, retries=5, wait_time=10): | |
for attempt in range(retries): | |
response = requests.get(url) | |
if response.status_code == 200: | |
return response | |
print(f"::warning title=GitHub API Access Error!::Attempt {attempt + 1} failed. Retrying in {wait_time} seconds...") | |
time.sleep(wait_time) | |
return response | |
GITHUB_REPO = "sagiegurari/cargo-make" | |
api_url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/tags/0.37.9" | |
response = get_response_with_retries(api_url) | |
if response.status_code == 200: | |
build_release_id = response.json()["id"] | |
else: | |
print("::error title=GitHub Release Error!::Failed to get cargo-make release ID!") | |
sys.exit(1) | |
api_url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/{build_release_id}" | |
response = get_response_with_retries(api_url) | |
if response.status_code == 200: | |
latest_cargo_make_version = response.json()["tag_name"] | |
else: | |
print("::error title=GitHub Release Error!::Failed to get cargo-make!") | |
sys.exit(1) | |
cache_key = f'cargo-make-{latest_cargo_make_version}' | |
with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: | |
print(f'cargo_bin_path={os.path.join(os.environ["USERPROFILE"], ".cargo", "bin")}', file=fh) | |
print(f'cargo_make_cache_key={cache_key}', file=fh) | |
print(f'cargo_make_version={latest_cargo_make_version}', file=fh) | |
- name: Attempt to Load cargo-make From Cache | |
id: cargo_make_cache | |
uses: actions/cache@v4 | |
with: | |
path: ${{ steps.get_cargo_tool_details.outputs.cargo_bin_path }} | |
key: ${{ steps.get_cargo_tool_details.outputs.cargo_make_cache_key }} | |
- name: Download cargo-make | |
if: steps.cargo_make_cache.outputs.cache-hit != 'true' | |
uses: robinraju/[email protected] | |
with: | |
repository: 'sagiegurari/cargo-make' | |
tag: '${{ steps.get_cargo_tool_details.outputs.cargo_make_version }}' | |
fileName: 'cargo-make-v${{ steps.get_cargo_tool_details.outputs.cargo_make_version }}-x86_64-pc-windows-msvc.zip' | |
out-file-path: 'cargo-make-download' | |
token: ${{ secrets.GITHUB_TOKEN }} | |
- name: Extract cargo-make | |
if: steps.cargo_make_cache.outputs.cache-hit != 'true' | |
env: | |
CARGO_MAKE_VERSION: ${{ steps.get_cargo_tool_details.outputs.cargo_make_version }} | |
DEST_DIR: ${{steps.get_cargo_tool_details.outputs.cargo_bin_path }} | |
shell: python | |
run: | | |
import os | |
import shutil | |
import zipfile | |
from pathlib import Path | |
DOWNLOAD_DIR = Path(os.environ["GITHUB_WORKSPACE"], "cargo-make-download") | |
ZIP_FILE_NAME = f"cargo-make-v{os.environ['CARGO_MAKE_VERSION']}-x86_64-pc-windows-msvc.zip" | |
ZIP_FILE_PATH = Path(DOWNLOAD_DIR, ZIP_FILE_NAME) | |
EXTRACT_DIR = Path(DOWNLOAD_DIR, "cargo-make-contents") | |
with zipfile.ZipFile(ZIP_FILE_PATH, 'r') as zip_ref: | |
zip_ref.extractall(EXTRACT_DIR) | |
for extracted_file in EXTRACT_DIR.iterdir(): | |
if extracted_file.name == "cargo-make.exe": | |
shutil.copy2(extracted_file, os.environ["DEST_DIR"]) | |
break | |
- name: Rust Prep | |
run: rustup component add rust-src | |
- name: Setup | |
if: steps.get_ci_file_operations.outputs.setup_supported == 'true' | |
run: stuart_setup -c .pytool/CISettings.py -t DEBUG -a ${{ matrix.archs }} TOOL_CHAIN_TAG=${{ matrix.tool_chain_tag }} | |
- name: Upload Setup Log As An Artifact | |
uses: actions/upload-artifact@v4 | |
if: (success() || failure()) && steps.get_ci_file_operations.outputs.setup_supported == 'true' | |
with: | |
name: ${{ matrix.package }}-Setup-Log | |
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.archs }} TOOL_CHAIN_TAG=${{ matrix.tool_chain_tag }} | |
- name: Upload CI Setup Log As An Artifact | |
uses: actions/upload-artifact@v4 | |
if: (success() || failure()) && steps.get_ci_file_operations.outputs.ci_setup_supported == 'true' | |
with: | |
name: ${{ matrix.package }}-CI-Setup-Log | |
path: | | |
**/CISETUP.txt | |
retention-days: 7 | |
if-no-files-found: ignore | |
- name: Update | |
run: stuart_update -c .pytool/CISettings.py -t DEBUG -a ${{ matrix.archs }} TOOL_CHAIN_TAG=${{ matrix.tool_chain_tag }} | |
- name: Upload Update Log As An Artifact | |
uses: actions/upload-artifact@v4 | |
if: success() || failure() | |
with: | |
name: ${{ matrix.package }}-Update-Log | |
path: | | |
**/UPDATE_LOG.txt | |
retention-days: 7 | |
if-no-files-found: ignore | |
- 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. | |
# | |
# Prior to Mu Basecore 202311, the CodeQL plugin was located in .pytool. After it | |
# is located in BaseTools. First check BaseTools, but consider .pytool as a backup | |
# for backward compatibility. The .pytool backup can be removed when no longer needed | |
# for supported branches. | |
# | |
plugin_dir = list(Path(os.environ['GITHUB_WORKSPACE']).rglob('BaseTools/Plugin/CodeQL')) | |
if not plugin_dir: | |
plugin_dir = list(Path(os.environ['GITHUB_WORKSPACE']).rglob('.pytool/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 Mu Basecore 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@v4 | |
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.archs }} TOOL_CHAIN_TAG=${{ matrix.tool_chain_tag }} --codeql | |
- name: Find pytool Plugin Directory | |
id: find_pytool_dir | |
shell: python | |
run: | | |
import os | |
import sys | |
from pathlib import Path | |
# Find the plugin directory that contains the Compiler plugin | |
plugin_dir = list(Path(os.environ['GITHUB_WORKSPACE']).rglob('.pytool/Plugin/CompilerPlugin')) | |
# This should only be found once | |
if len(plugin_dir) == 1: | |
# If the directory is found get the parent Plugin directory | |
plugin_dir = str(plugin_dir[0].parent) | |
with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: | |
print(f'pytool_plugin_dir={plugin_dir}', file=fh) | |
else: | |
print("::error title=Workspace Error!::Failed to find Mu Basecore .pytool/Plugin directory!") | |
sys.exit(1) | |
- name: Remove CI Plugins Irrelevant to CodeQL | |
shell: python | |
env: | |
PYTOOL_PLUGIN_DIR: ${{ steps.find_pytool_dir.outputs.pytool_plugin_dir }} | |
run: | | |
import os | |
import shutil | |
from pathlib import Path | |
# Only these two plugins are needed for CodeQL. | |
# | |
# CodeQL build time is reduced by removing other plugins that are not needed for the CodeQL | |
# build in the .pytool directory. The CompilerPlugin is required to compile code for CodeQL | |
# to extract results from and the CodeQL plugin is necessary to to analyze the results and | |
# build the CodeQL database from them. The CodeQL plugin should be in BaseTools moving forward | |
# but still might be in .pytool in older branches so it is kept here as an exception. | |
# | |
plugins_to_keep = ['CodeQL', 'CompilerPlugin'] | |
plugin_dir = Path(os.environ['PYTOOL_PLUGIN_DIR']).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: | |
RUST_ENV_CHECK_TOOL_EXCLUSIONS: "cargo fmt, cargo tarpaulin" | |
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.archs }} TOOL_CHAIN_TAG=${{ matrix.tool_chain_tag }} --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@v4 | |
if: success() || failure() | |
with: | |
name: ${{ matrix.package }}-Build-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: | |
print(f'sarif_file_path={sarif_path}', file=fh) | |
- name: Upload CodeQL Results (SARIF) As An Artifact | |
uses: actions/upload-artifact@v4 | |
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@v3 | |
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 }} | |