-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BaseTools/Plugin: Add Rust Environment Check build plugin
Firmware developer's machines are often not setup for Rust. Ad more Rust code is proliferating across the repos, this plugin is used to provide early and direct feedback about the developer's environment so it can successfully build Rust code using the tools commonly used in the firmware build process. The plugin is run when the `rust-ci` scope is specified which is used by platforms to opt into Rust plugin support in Project Mu. The entire plugin takes ~1.4 sec to run on average so build time is not meaningfully impacted. Signed-off-by: Michael Kubacki <[email protected]>
- Loading branch information
Showing
2 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
173 changes: 173 additions & 0 deletions
173
BaseTools/Plugin/RustEnvironmentCheck/RustEnvironmentCheck.py
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
# @file RustEnvironmentCheck.py | ||
# Plugin to confirm Rust tools are present needed to compile Rust code during | ||
# firmare build. | ||
# | ||
# This provides faster, direct feedback to a developer about the changes they | ||
# may need to make to successfully build Rust code. Otherwise, the build will | ||
# fail much later during firmware code compilation when Rust tools are invoked | ||
# with messages that are ambiguous or difficult to find. | ||
# | ||
# Copyright (c) Microsoft Corporation. | ||
# | ||
# SPDX-License-Identifier: BSD-2-Clause-Patent | ||
## | ||
import logging | ||
import re | ||
from collections import namedtuple | ||
from edk2toolext.environment.plugintypes.uefi_build_plugin import IUefiBuildPlugin | ||
from edk2toolext.environment.uefi_build import UefiBuilder | ||
from edk2toollib.utility_functions import RunCmd | ||
from io import StringIO | ||
|
||
RustToolInfo = namedtuple("RustToolInfo", ["presence_cmd", "install_help"]) | ||
RustToolChainInfo = namedtuple("RustToolChainInfo", ["error", "toolchain"]) | ||
|
||
|
||
class RustEnvironmentCheck(IUefiBuildPlugin): | ||
"""Checks that the system environment is ready to build Rust code.""" | ||
|
||
def do_pre_build(self, _: UefiBuilder) -> int: | ||
"""Rust environment checks during pre-build. | ||
Args: | ||
builder (UefiBuilder): A UEFI builder object for this build. | ||
Returns: | ||
int: The number of environment issues found. Zero indicates no | ||
action is needed. | ||
""" | ||
def verify_cmd(name: str, params: str = "--version") -> bool: | ||
"""Indicates if a command can successfully be executed. | ||
Args: | ||
name (str): Tool name. | ||
params (str, optional): Tool params. Defaults to "--version". | ||
Returns: | ||
bool: True on success. False on failure to run the command. | ||
""" | ||
cmd_output = StringIO() | ||
ret = RunCmd(name, params, outstream=cmd_output, | ||
logging_level=logging.DEBUG) | ||
return ret == 0 | ||
|
||
def verify_workspace_rust_toolchain_is_installed() -> RustToolChainInfo: | ||
"""Verifies the rust toolchain used in the workspace is available. | ||
Note: This function does not use the toml library to parse the toml | ||
file since the file is very simple and its not desirable to add the | ||
toml module as a dependency. | ||
Returns: | ||
RustToolChainInfo: A tuple that indicates if the toolchain is | ||
available and any the toolchain version if found. | ||
""" | ||
WORKSPACE_TOOLCHAIN_FILE = "rust-toolchain.toml" | ||
|
||
toolchain_version = None | ||
try: | ||
with open(WORKSPACE_TOOLCHAIN_FILE, 'r') as toml_file: | ||
content = toml_file.read() | ||
match = re.search(r'channel\s*=\s*"([^"]+)"', content) | ||
if match: | ||
toolchain_version = match.group(1) | ||
except FileNotFoundError: | ||
# If a file is not found. Do not check any further. | ||
return RustToolChainInfo(error=False, toolchain=None) | ||
|
||
if not toolchain_version: | ||
# If the file is not in an expected format, let that be handled | ||
# elsewhere and do not look further. | ||
return RustToolChainInfo(error=False, toolchain=None) | ||
|
||
installed_toolchains = StringIO() | ||
ret = RunCmd("rustup", "toolchain list", | ||
outstream=installed_toolchains, | ||
logging_level=logging.DEBUG) | ||
|
||
# The ability to call "rustup" is checked separately. Here do not | ||
# continue if the command is not successful. | ||
if ret != 0: | ||
return RustToolChainInfo(error=False, toolchain=None) | ||
|
||
installed_toolchains = installed_toolchains.getvalue().splitlines() | ||
return RustToolChainInfo( | ||
error=not any(toolchain_version in toolchain | ||
for toolchain in installed_toolchains), | ||
toolchain=toolchain_version) | ||
|
||
generic_rust_install_instructions = \ | ||
"Visit https://rustup.rs/ to install Rust and cargo." | ||
|
||
tools = { | ||
"rustup": RustToolInfo( | ||
presence_cmd=("rustup",), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"rustc": RustToolInfo( | ||
presence_cmd=("rustc",), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"cargo": RustToolInfo( | ||
presence_cmd=("cargo",), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"cargo build": RustToolInfo( | ||
presence_cmd=("cargo", "build --help"), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"cargo check": RustToolInfo( | ||
presence_cmd=("cargo", "check --help"), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"cargo fmt": RustToolInfo( | ||
presence_cmd=("cargo", "fmt --help"), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"cargo test": RustToolInfo( | ||
presence_cmd=("cargo", "test --help"), | ||
install_help=generic_rust_install_instructions | ||
), | ||
"cargo make": RustToolInfo( | ||
presence_cmd=("cargo", "make --version"), | ||
install_help="Read installation instructions at " | ||
"https://github.com/sagiegurari/cargo-make#installation " | ||
"to install Cargo make." | ||
), | ||
"cargo tarpaulin": RustToolInfo( | ||
presence_cmd=("cargo", "tarpaulin --version"), | ||
install_help="View the installation instructions at " | ||
"https://crates.io/crates/cargo-tarpaulin to install Cargo " | ||
"tarpaulin. A tool used for Rust code coverage." | ||
), | ||
} | ||
|
||
errors = 0 | ||
for tool_name, tool_info in tools.items(): | ||
if not verify_cmd(*tool_info.presence_cmd): | ||
logging.error( | ||
f"Rust Environment Failure: {tool_name} is not installed " | ||
"or not on the system path.\n\n" | ||
f"Instructions:\n{tool_info.install_help}\n\n" | ||
f"Ensure \"{' '.join(tool_info.presence_cmd)}\" can " | ||
"successfully be run from a terminal before trying again.") | ||
errors += 1 | ||
|
||
rust_toolchain_info = verify_workspace_rust_toolchain_is_installed() | ||
if rust_toolchain_info.error: | ||
# The "rustc -Vv" command could be run in the script with the | ||
# output given to the user. This is approach is also meant to show | ||
# the user how to use the tools since getting the target triple is | ||
# important. | ||
logging.error( | ||
f"This workspace requires the {rust_toolchain_info.toolchain} " | ||
"toolchain.\n\n" | ||
"Run \"rustc -Vv\" and use the \"host\" value to install the " | ||
"toolchain needed:\n" | ||
f" \"rustup toolchain install {rust_toolchain_info.toolchain}-" | ||
"<host>\"\n\n" | ||
" \"rustup component add rust-src " | ||
f"{rust_toolchain_info.toolchain}-<host>\"") | ||
errors += 1 | ||
|
||
return errors |
16 changes: 16 additions & 0 deletions
16
BaseTools/Plugin/RustEnvironmentCheck/RustEnvironmentCheck_plug_in.yaml
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
## @file | ||
# Build plugin used to check that the users environment is ready to build with the Rust tools | ||
# commonly used. This provides direct feedback early in the build process rather than ambiguous | ||
# hard to find messages later during code compilation. | ||
# | ||
# This plugin requires the "rust-ci" scope to be set which is used in the plugins to determine | ||
# when a workspace is building code that includes Rust. | ||
# | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# SPDX-License-Identifier: BSD-2-Clause-Patent | ||
## | ||
{ | ||
"scope": "rust-ci", | ||
"name": "Rust Environment Check Pre-Build Plugin", | ||
"module": "RustEnvironmentCheck" | ||
} |