mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-10-18 06:55:30 +08:00
detect_rust_target() 的 Cortex-M33 分支固定返回 thumbv8m.main-none-eabi, 没有使用同函数已计算的 hard_float,而 M4/M7 分支是判断的。 硬浮点 M33 板卡因此拿到软浮点 ABI 的 Rust 库,链接时报 "uses VFP register arguments" 并终止构建。 按 hard_float 选择 eabihf/eabi,与 M4/M7 分支保持一致。
374 lines
14 KiB
Python
374 lines
14 KiB
Python
import os
|
||
import subprocess
|
||
|
||
|
||
def _parse_cflags(cflags: str):
|
||
info = {
|
||
"march": None,
|
||
"mabi": None,
|
||
"rv_bits": None, # 32 or 64
|
||
"has_f": False,
|
||
"has_d": False,
|
||
}
|
||
|
||
if not cflags:
|
||
return info
|
||
|
||
parts = cflags.split()
|
||
for flag in parts:
|
||
if flag.startswith("-march="):
|
||
info["march"] = flag.split("=", 1)[1]
|
||
if "rv32" in info["march"]:
|
||
info["rv_bits"] = 32
|
||
elif "rv64" in info["march"]:
|
||
info["rv_bits"] = 64
|
||
# crude feature detection
|
||
m = info["march"]
|
||
if m:
|
||
info["has_f"] = ("f" in m)
|
||
info["has_d"] = ("d" in m)
|
||
elif flag.startswith("-mabi="):
|
||
info["mabi"] = flag.split("=", 1)[1]
|
||
if info["mabi"] in ("ilp32d", "ilp32f", "lp64d", "lp64f"):
|
||
# floating-point ABI implies FPU availability
|
||
info["has_f"] = True
|
||
info["has_d"] = info["mabi"].endswith("d")
|
||
|
||
return info
|
||
|
||
|
||
def detect_rust_target(has, rtconfig):
|
||
"""
|
||
Decide the Rust target triple based on RT-Thread Kconfig and rtconfig.*.
|
||
`has` is a callable: has("SYMBOL") -> bool
|
||
"""
|
||
# ARM Cortex-M
|
||
if has("ARCH_ARM"):
|
||
# FPU hints from flags/macros
|
||
cflags = getattr(rtconfig, "CFLAGS", "")
|
||
hard_float = "-mfloat-abi=hard" in cflags or has("ARCH_ARM_FPU") or has("ARCH_FPU_VFP")
|
||
|
||
if has("ARCH_ARM_CORTEX_M0") or has("ARCH_ARM_CORTEX_M0PLUS"):
|
||
return "thumbv6m-none-eabi"
|
||
if has("ARCH_ARM_CORTEX_M3"):
|
||
return "thumbv7m-none-eabi"
|
||
if has("ARCH_ARM_CORTEX_M4") or has("ARCH_ARM_CORTEX_M7"):
|
||
return "thumbv7em-none-eabihf" if hard_float else "thumbv7em-none-eabi"
|
||
if has("ARCH_ARM_CORTEX_M23"):
|
||
return "thumbv8m.base-none-eabi"
|
||
if has("ARCH_ARM_CORTEX_M33"):
|
||
# v8m.main
|
||
return "thumbv8m.main-none-eabihf" if hard_float else "thumbv8m.main-none-eabi"
|
||
if has("ARCH_ARM_CORTEX_A"):
|
||
return "armv7a-none-eabi"
|
||
|
||
# AArch64
|
||
if has("ARCH_AARCH64") or has("ARCH_ARMV8") or has("ARCH_ARM64"):
|
||
if has("ARCH_CPU_FLOAT_ABI_SOFT"):
|
||
return "aarch64-unknown-none-softfloat"
|
||
return "aarch64-unknown-none"
|
||
|
||
# RISC-V
|
||
if has("ARCH_RISCV32") or has("ARCH_RISCV64"):
|
||
cflags = getattr(rtconfig, "CFLAGS", "")
|
||
info = _parse_cflags(cflags)
|
||
|
||
# fallback to Kconfig hint if march not present
|
||
rv_bits = info["rv_bits"] or (32 if has("ARCH_RISCV32") else 64)
|
||
|
||
# ABI must carry f/d to actually use hard-float calling convention
|
||
abi = info["mabi"] or ""
|
||
abi_has_fp = abi.endswith("f") or abi.endswith("d")
|
||
|
||
if rv_bits == 32:
|
||
# Only pick *f* target when ABI uses hard-float; otherwise use soft-float even if core has F/D
|
||
return "riscv32imafc-unknown-none-elf" if abi_has_fp else "riscv32imac-unknown-none-elf"
|
||
else:
|
||
# rv64: prefer gc (includes fd) only when ABI uses hard-float
|
||
return "riscv64gc-unknown-none-elf" if abi_has_fp else "riscv64imac-unknown-none-elf"
|
||
|
||
# Fallback by ARCH string or CFLAGS
|
||
arch = getattr(rtconfig, "ARCH", None)
|
||
if arch:
|
||
arch_l = arch.lower()
|
||
if "aarch64" in arch_l:
|
||
return "aarch64-unknown-none"
|
||
if "arm" == arch_l or "armv7" in arch_l:
|
||
return "armv7a-none-eabi"
|
||
if "riscv32" in arch_l:
|
||
return "riscv32imac-unknown-none-elf"
|
||
if "riscv64" in arch_l:
|
||
return "riscv64imac-unknown-none-elf"
|
||
if "risc-v" in arch_l:
|
||
info = _parse_cflags(getattr(rtconfig, "CFLAGS", ""))
|
||
abi = info["mabi"] or ""
|
||
abi_has_fp = abi.endswith("f") or abi.endswith("d")
|
||
if info["rv_bits"] == 32:
|
||
return "riscv32imafc-unknown-none-elf" if abi_has_fp else "riscv32imac-unknown-none-elf"
|
||
if info["rv_bits"] == 64:
|
||
return "riscv64gc-unknown-none-elf" if abi_has_fp else "riscv64imac-unknown-none-elf"
|
||
# Many BSPs use "risc-v" token; assume 64-bit for virt64 when CFLAGS do not specify width
|
||
return "riscv64imac-unknown-none-elf"
|
||
|
||
# Parse CFLAGS for hints
|
||
cflags = getattr(rtconfig, "CFLAGS", "")
|
||
if "-mcpu=cortex-m3" in cflags:
|
||
return "thumbv7m-none-eabi"
|
||
if "-mcpu=cortex-m4" in cflags or "-mcpu=cortex-m7" in cflags:
|
||
if "-mfpu=" in cflags and "-mfloat-abi=hard" in cflags:
|
||
return "thumbv7em-none-eabihf"
|
||
return "thumbv7em-none-eabi"
|
||
if "-march=rv32" in cflags:
|
||
info = _parse_cflags(cflags)
|
||
abi = info["mabi"] or ""
|
||
abi_has_fp = abi.endswith("f") or abi.endswith("d")
|
||
return "riscv32imafc-unknown-none-elf" if abi_has_fp else "riscv32imac-unknown-none-elf"
|
||
if "-march=rv64" in cflags:
|
||
info = _parse_cflags(cflags)
|
||
abi = info["mabi"] or ""
|
||
abi_has_fp = abi.endswith("f") or abi.endswith("d")
|
||
if abi_has_fp:
|
||
return "riscv64gc-unknown-none-elf"
|
||
return "riscv64imac-unknown-none-elf"
|
||
|
||
return None
|
||
|
||
|
||
def uses_short_wchar_abi(rtconfig, target: str):
|
||
if not isinstance(target, str) or not target:
|
||
return False
|
||
if not (target.startswith("thumb") or target.startswith("arm")):
|
||
return False
|
||
|
||
cflags = getattr(rtconfig, "CFLAGS", "")
|
||
return "-fshort-wchar" in cflags.split()
|
||
|
||
|
||
def make_cargo_build_std_args(rtconfig, target: str):
|
||
if uses_short_wchar_abi(rtconfig, target):
|
||
return ["-Z", "build-std=core,alloc"]
|
||
return []
|
||
|
||
|
||
def make_rustflags(rtconfig, target: str):
|
||
if not isinstance(target, str) or not target:
|
||
arch = getattr(rtconfig, "ARCH", None)
|
||
cflags = getattr(rtconfig, "CFLAGS", "")
|
||
raise ValueError(
|
||
"Unsupported Rust target: unable to detect a Rust target "
|
||
f"for ARCH={arch!r}, CFLAGS={cflags!r}"
|
||
)
|
||
|
||
rustflags = [
|
||
"-C", "opt-level=z",
|
||
"-C", "panic=abort",
|
||
"-C", "relocation-model=static",
|
||
]
|
||
|
||
if "riscv" in target:
|
||
rustflags += [
|
||
"-C", "code-model=medium",
|
||
"-C", "link-dead-code",
|
||
]
|
||
# propagate march/mabi for consistency (use link-arg for staticlib builds – harmless)
|
||
cflags = getattr(rtconfig, "CFLAGS", "")
|
||
for flag in cflags.split():
|
||
if flag.startswith("-march=") or flag.startswith("-mabi="):
|
||
rustflags += ["-C", f"link-arg={flag}"]
|
||
|
||
if "thumb" in target or "aarch64" in target:
|
||
rustflags += ["-C", "link-arg=-nostartfiles"]
|
||
|
||
if uses_short_wchar_abi(rtconfig, target):
|
||
rustflags += ["-Zllvm_module_flag=wchar_size:u32:2:error"]
|
||
|
||
return " ".join(rustflags)
|
||
|
||
|
||
def collect_features(has):
|
||
cargo_toml_path = os.path.abspath(
|
||
os.path.join(os.path.dirname(__file__), "..", "core", "Cargo.toml")
|
||
)
|
||
if not os.path.isfile(cargo_toml_path):
|
||
raise RuntimeError(f"Rust core Cargo.toml not found: {cargo_toml_path}")
|
||
|
||
try:
|
||
import toml
|
||
except ImportError as e:
|
||
raise RuntimeError("Missing toml module required to parse Rust core Cargo.toml") from e
|
||
|
||
try:
|
||
with open(cargo_toml_path, "r") as f:
|
||
cargo_data = toml.load(f)
|
||
except OSError as e:
|
||
raise RuntimeError(f"Failed to read Rust core Cargo.toml {cargo_toml_path}: {e}") from e
|
||
except Exception as e:
|
||
raise RuntimeError(f"Failed to parse Rust core Cargo.toml {cargo_toml_path}: {e}") from e
|
||
|
||
declared_features = cargo_data.get("features")
|
||
if not isinstance(declared_features, dict):
|
||
raise RuntimeError("Invalid Rust core Cargo.toml: [features] must be a table")
|
||
|
||
package = cargo_data.get("package")
|
||
metadata = package.get("metadata") if isinstance(package, dict) else None
|
||
rt_thread = metadata.get("rt-thread") if isinstance(metadata, dict) else None
|
||
feature_mappings = rt_thread.get("features") if isinstance(rt_thread, dict) else None
|
||
if not isinstance(feature_mappings, dict):
|
||
raise RuntimeError(
|
||
"Invalid Rust core feature metadata: package.metadata.rt-thread.features must be a table"
|
||
)
|
||
|
||
for feature in feature_mappings:
|
||
if feature not in declared_features:
|
||
raise RuntimeError(
|
||
f"Rust core feature metadata references undeclared Cargo feature: {feature}"
|
||
)
|
||
|
||
feats = []
|
||
for feature in declared_features:
|
||
if feature == "default" or feature not in feature_mappings:
|
||
continue
|
||
|
||
mapping = feature_mappings[feature]
|
||
if not isinstance(mapping, dict):
|
||
raise RuntimeError(f"Invalid Rust core feature metadata for '{feature}': expected a table")
|
||
if set(mapping) != {"all"}:
|
||
raise RuntimeError(
|
||
f"Invalid Rust core feature metadata for '{feature}': only 'all' is supported"
|
||
)
|
||
|
||
symbols = mapping["all"]
|
||
if not isinstance(symbols, list):
|
||
raise RuntimeError(f"Invalid Rust core feature metadata for '{feature}': 'all' must be a list")
|
||
if not symbols:
|
||
raise RuntimeError(f"Invalid Rust core feature metadata for '{feature}': 'all' must not be empty")
|
||
if any(not isinstance(symbol, str) or not symbol for symbol in symbols):
|
||
raise RuntimeError(
|
||
f"Invalid Rust core feature metadata for '{feature}': 'all' must contain only non-empty strings"
|
||
)
|
||
|
||
if all(has(symbol) for symbol in symbols):
|
||
feats.append(feature)
|
||
return feats
|
||
|
||
|
||
def verify_rust_toolchain():
|
||
try:
|
||
r1 = subprocess.run(["rustc", "--version"], capture_output=True, text=True)
|
||
r2 = subprocess.run(["cargo", "--version"], capture_output=True, text=True)
|
||
return r1.returncode == 0 and r2.returncode == 0
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
def parse_installed_rust_targets(output):
|
||
return {line.strip() for line in output.splitlines() if line.strip()}
|
||
|
||
|
||
def get_staticlib_artifact_name(rust_dir):
|
||
cargo_toml_path = os.path.join(rust_dir, "Cargo.toml")
|
||
lib_name = "rt_rust"
|
||
|
||
try:
|
||
import toml
|
||
with open(cargo_toml_path, "r") as f:
|
||
cargo_data = toml.load(f)
|
||
|
||
package_name = cargo_data.get("package", {}).get("name")
|
||
lib_name = cargo_data.get("lib", {}).get("name") or package_name or lib_name
|
||
except Exception as e:
|
||
print(f"Warning: Failed to parse Rust static library metadata from {cargo_toml_path}: {e}")
|
||
|
||
if not isinstance(lib_name, str) or not lib_name:
|
||
lib_name = "rt_rust"
|
||
|
||
lib_name = lib_name.replace("-", "_")
|
||
return f"lib{lib_name}.a"
|
||
|
||
|
||
def get_staticlib_link_name(rust_dir):
|
||
cargo_toml_path = os.path.join(rust_dir, "Cargo.toml")
|
||
lib_name = "rt_rust"
|
||
|
||
try:
|
||
import toml
|
||
with open(cargo_toml_path, "r") as f:
|
||
cargo_data = toml.load(f)
|
||
|
||
package_name = cargo_data.get("package", {}).get("name")
|
||
lib_name = cargo_data.get("lib", {}).get("name") or package_name or lib_name
|
||
except Exception as e:
|
||
print(f"Warning: Failed to parse Rust static library metadata from {cargo_toml_path}: {e}")
|
||
|
||
if not isinstance(lib_name, str) or not lib_name:
|
||
lib_name = "rt_rust"
|
||
|
||
return lib_name.replace("-", "_")
|
||
|
||
|
||
def ensure_rust_target_installed(target: str):
|
||
if not isinstance(target, str) or not target:
|
||
print("Invalid Rust target: expected a non-empty string")
|
||
return False
|
||
|
||
try:
|
||
result = subprocess.run(["rustup", "target", "list", "--installed"], capture_output=True, text=True)
|
||
installed_targets = parse_installed_rust_targets(result.stdout)
|
||
if result.returncode == 0 and target in installed_targets:
|
||
return True
|
||
print(f"Rust target '{target}' is not installed.")
|
||
print(f"Please install it with: rustup target add {target}")
|
||
except Exception:
|
||
print("Warning: Failed to check rustup target list (rustup missing?)")
|
||
return False
|
||
|
||
|
||
def cargo_build_staticlib(rust_dir: str, target: str, features, debug: bool, rustflags: str = None, build_root: str = None, cargo_extra_args=None):
|
||
if build_root is None:
|
||
build_root = os.path.join((os.path.abspath(os.path.join(rust_dir, os.pardir, os.pardir))), "build", "rust")
|
||
else:
|
||
build_root = os.path.abspath(os.fspath(build_root))
|
||
target_dir = os.path.join(build_root, "target")
|
||
os.makedirs(build_root, exist_ok=True)
|
||
|
||
env = os.environ.copy()
|
||
if rustflags:
|
||
prev = env.get("RUSTFLAGS", "").strip()
|
||
env["RUSTFLAGS"] = (prev + " " + rustflags).strip() if prev else rustflags
|
||
env["CARGO_TARGET_DIR"] = target_dir
|
||
|
||
cmd = ["cargo", "build", "--target", target, "--manifest-path", os.path.join(rust_dir, "Cargo.toml")]
|
||
if not debug:
|
||
cmd.insert(2, "--release")
|
||
if cargo_extra_args:
|
||
cmd[2:2] = cargo_extra_args
|
||
if features:
|
||
cmd += ["--no-default-features", "--features", ",".join(features)]
|
||
|
||
print("Building Rust component (cargo)…")
|
||
try:
|
||
res = subprocess.run(cmd, cwd=rust_dir, env=env, capture_output=True, text=True)
|
||
except FileNotFoundError:
|
||
print("Error: cargo executable not found. Please install Rust/Cargo and ensure it is in PATH.")
|
||
return None
|
||
if res.returncode != 0:
|
||
print("Warning: Rust build failed")
|
||
if res.stderr:
|
||
print(res.stderr)
|
||
return None
|
||
|
||
mode = "debug" if debug else "release"
|
||
artifact_name = get_staticlib_artifact_name(rust_dir)
|
||
lib_path = os.path.join(target_dir, target, mode, artifact_name)
|
||
if os.path.isfile(lib_path) and os.path.getsize(lib_path) > 0:
|
||
print("Rust component built successfully")
|
||
return lib_path
|
||
print(f"Warning: Rust static library artifact not found, is not a file, or is empty: {lib_path}")
|
||
return None
|
||
|
||
|
||
def clean_rust_build(bsp_root: str, artifact_type: str = "rust"):
|
||
"""Return the build directory path for SCons Clean operation"""
|
||
build_dir = os.path.join(bsp_root, "build", artifact_type)
|
||
return build_dir
|