use anyhow::bail;
use super::*;
pub(crate) fn env_truthy(env: &HashMap<String, String>, key: &str) -> bool {
env.get(key).is_some_and(|value| {
matches!(
value.trim().to_ascii_lowercase().as_str(),
"y" | "yes" | "1" | "true" | "on"
)
})
}
pub(crate) fn toolchain_rustflags(env: &HashMap<String, String>) -> Vec<String> {
let mut flags = Vec::new();
let dwarf = env_truthy(env, "DWARF");
let backtrace = env_truthy(env, "BACKTRACE") || dwarf;
if dwarf {
flags.push("-Cdebuginfo=2".to_string());
flags.push("-Cstrip=none".to_string());
}
if backtrace {
flags.push("-Cforce-frame-pointers=yes".to_string());
}
flags
}
pub(super) fn features_enable_stack_protector(features: &[String]) -> bool {
features.iter().any(|feature| {
matches!(
feature.as_str(),
"stack-protector" | "ax-std/stack-protector" | "starry-kernel/stack-protector"
)
})
}
pub(crate) fn toolchain_rustflags_for_features(
env: &HashMap<String, String>,
features: &[String],
) -> Vec<String> {
let mut flags = toolchain_rustflags(env);
if features_enable_stack_protector(features) {
flags.push("-Zstack-protector=strong".to_string());
}
flags
}
pub(crate) fn append_encoded_rustflags(cargo: &mut Cargo, flags: &[&str]) {
const KEY: &str = "CARGO_ENCODED_RUSTFLAGS";
let encoded = flags.join("\x1f");
if encoded.is_empty() {
return;
}
if !cargo.env.contains_key(KEY) && append_inline_target_rustflags(cargo, flags) {
return;
}
let value = cargo.env.entry(KEY.to_string()).or_default();
if encoded_rustflags_contains_sequence(value, &encoded) {
return;
}
if !value.is_empty() {
value.push('\x1f');
}
value.push_str(&encoded);
}
fn append_inline_target_rustflags(cargo: &mut Cargo, flags: &[&str]) -> bool {
let target_key = Path::new(&cargo.target)
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or(&cargo.target);
let rustflags_key = format!("target.{target_key}.rustflags");
for index in 1..cargo.args.len() {
if cargo.args[index - 1] != "--config" {
continue;
}
let Some((key, value)) = cargo.args[index].split_once('=') else {
continue;
};
if key != rustflags_key {
continue;
}
let Ok(table) = toml::from_str::<toml::Table>(&format!("rustflags = {value}")) else {
continue;
};
let Some(mut rustflags) = table
.get("rustflags")
.and_then(toml::Value::as_array)
.and_then(|rustflags| {
rustflags
.iter()
.map(|flag| flag.as_str().map(ToOwned::to_owned))
.collect::<Option<Vec<_>>>()
})
else {
continue;
};
if !rustflags
.windows(flags.len())
.any(|window| window.iter().map(String::as_str).eq(flags.iter().copied()))
{
rustflags.extend(flags.iter().map(|flag| (*flag).to_string()));
}
let rustflags =
toml::Value::Array(rustflags.into_iter().map(toml::Value::String).collect());
cargo.args[index] = format!("{rustflags_key}={rustflags}");
return true;
}
false
}
fn encoded_rustflags_contains_sequence(value: &str, encoded: &str) -> bool {
let needle: Vec<_> = encoded.split('\x1f').collect();
if needle.is_empty() {
return true;
}
value
.split('\x1f')
.collect::<Vec<_>>()
.windows(needle.len())
.any(|window| window == needle.as_slice())
}
pub(crate) fn build_info_enables_backtrace(info: &BuildInfo) -> bool {
let dwarf = env_truthy(&info.env, "DWARF");
env_truthy(&info.env, "BACKTRACE") || dwarf
}
pub(crate) fn build_info_enables_backtrace_path(path: &Path) -> bool {
load_build_info::<BuildInfo>(path)
.ok()
.is_some_and(|info| build_info_enables_backtrace(&info))
}
pub(super) const TARGET_JSON_ROOT: &str = "scripts/targets";
pub(super) const PIE_TARGET_DIR: &str = "pie";
pub(crate) const ARCEOS_LINKER_SCRIPT: &str = "linker.x";
pub(super) const STD_TARGET_DIR: &str = "std";
pub(super) const AXSTD_STD_PACKAGE: &str = "ax-std";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BareKernelLinkMode {
Default,
Pie,
}
impl BareKernelLinkMode {
fn rustflags(self, target: &str) -> Vec<String> {
match self {
Self::Default => Vec::new(),
Self::Pie => {
let mut flags = vec![
"-Crelocation-model=pic".to_string(),
"-Clink-args=-pie".to_string(),
];
if target.starts_with("riscv64") {
flags.push("-Clink-args=--no-relax".to_string());
}
flags.extend([
"-Clink-args=--gc-sections".to_string(),
"-Clink-args=-znorelro".to_string(),
"-Clink-args=-znostart-stop-gc".to_string(),
"-Clink-args=-Tlinker.x".to_string(),
"-Clink-args=-u _head".to_string(),
]);
flags
}
}
}
}
#[derive(Debug, Clone, JsonSchema, Deserialize, Serialize, PartialEq)]
pub struct BuildInfo {
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub env: HashMap<String, String>,
pub features: Vec<String>,
pub log: LogLevel,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_cpu_num: Option<usize>,
}
impl BuildInfo {
pub fn with_features<T: AsRef<str>>(mut self, features: impl AsRef<[T]>) -> Self {
let features = features
.as_ref()
.iter()
.map(|feature| feature.as_ref().to_string())
.collect();
self.features = features;
self
}
pub(crate) fn prepare_log_env(&mut self) {
self.env
.insert("AX_LOG".into(), format!("{:?}", self.log).to_lowercase());
}
pub(crate) fn prepare_max_cpu_num_env(&mut self) -> anyhow::Result<()> {
if let Some(max_cpu_num) = self.validated_max_cpu_num()? {
self.env.insert("SMP".into(), max_cpu_num.to_string());
}
Ok(())
}
pub(crate) fn into_base_cargo_config(
self,
package: String,
target: String,
args: Vec<String>,
) -> Cargo {
self.into_base_cargo_config_with_to_bin(package, target, args, false)
}
pub(crate) fn into_base_cargo_config_with_to_bin(
self,
package: String,
target: String,
args: Vec<String>,
to_bin: bool,
) -> Cargo {
Cargo {
env: self.env,
target,
package,
features: self.features,
log: Some(self.log),
extra_config: None,
profile: None,
disable_someboot_build_config: true,
args,
pre_build_cmds: vec![],
post_build_cmds: vec![],
to_bin,
bin: None,
test: None,
}
}
pub(crate) fn into_base_cargo_config_with_log(
mut self,
package: String,
target: String,
args: Vec<String>,
) -> Cargo {
self.prepare_log_env();
self.prepare_max_cpu_num_env()
.expect("max_cpu_num validation should run before cargo config generation");
self.into_base_cargo_config(package, target, args)
}
pub(crate) fn into_prepared_base_cargo_config_with_metadata(
mut self,
package: &str,
target: &str,
metadata: &Metadata,
) -> anyhow::Result<Cargo> {
self.validated_max_cpu_num()?;
self.validate_features()?;
self.resolve_std_features();
if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) {
self.features.push("smp".to_string());
self.resolve_std_features();
}
let std_target = std_build_target_for(target)?;
let fake_lib_dir = std_fake_lib_dir(&std_target.target_name)?;
let wrapper = std_linker_wrapper_path(&std_target.target_name, &fake_lib_dir)?;
let mut cargo = self.into_base_cargo_config_with_log(
package.to_string(),
std_target.target.clone(),
std_target.cargo_args,
);
cargo.env.extend(std_target.env);
cargo
.env
.insert("AX_TARGET".to_string(), target.to_string());
let app_features = package_feature_names(package, metadata)?;
let axstd_features = package_feature_names(AXSTD_STD_PACKAGE, metadata)?;
pass_std_build_nested_features(&mut cargo.features, &app_features, &axstd_features);
cargo.pre_build_cmds.push(
std_fake_lib_prebuild_script_path(&std_target.target_name, &fake_lib_dir, &cargo.env)?
.display()
.to_string(),
);
let rustflags = toolchain_rustflags_for_features(&cargo.env, &cargo.features);
cargo.extra_config = Some(
std_cargo_config_path(&std_target.target_name, &wrapper, &rustflags)?
.display()
.to_string(),
);
Ok(cargo)
}
pub(crate) fn into_prepared_std_cargo_config_with_metadata(
self,
package: &str,
target: &str,
metadata: &Metadata,
) -> anyhow::Result<Cargo> {
self.into_prepared_base_cargo_config_with_metadata(package, target, metadata)
}
pub(crate) fn into_prepared_no_std_cargo_config_with_metadata(
mut self,
package: &str,
target: &str,
metadata: &Metadata,
link_mode: BareKernelLinkMode,
) -> anyhow::Result<Cargo> {
self.validated_max_cpu_num()?;
self.validate_features()?;
self.reject_freestanding_std_compat()?;
self.enable_package_smp_feature(package, metadata)?;
let mut rustflags = toolchain_rustflags_for_features(&self.env, &self.features);
rustflags.extend(link_mode.rustflags(target));
let args = Self::build_cargo_args(target, &rustflags);
let mut cargo =
self.into_base_cargo_config_with_log(package.to_string(), target.to_string(), args);
cargo.to_bin = bare_target_requires_bin(target);
Ok(cargo)
}
fn reject_freestanding_std_compat(&self) -> anyhow::Result<()> {
if let Some(feature) = self
.features
.iter()
.find(|feature| feature.rsplit('/').next() == Some("std-compat"))
{
bail!("freestanding no_std build cannot enable `{feature}`");
}
Ok(())
}
fn enable_package_smp_feature(
&mut self,
package: &str,
metadata: &Metadata,
) -> anyhow::Result<()> {
if !self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) {
return Ok(());
}
if package_feature_names(package, metadata)?
.iter()
.any(|feature| feature == "smp")
{
self.features.push("smp".to_string());
self.features.sort();
self.features.dedup();
}
Ok(())
}
pub(super) fn resolve_std_features(&mut self) {
self.features = self
.features
.iter()
.map(|feature| normalize_std_feature(feature))
.collect();
self.features.sort();
self.features.dedup();
}
pub(crate) fn resolve_c_app_features(&mut self) -> anyhow::Result<()> {
self.validate_features()?;
if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) {
self.features.push("ax-std/smp".to_string());
}
self.features.sort();
self.features.dedup();
Ok(())
}
pub(crate) fn validate_features(&self) -> anyhow::Result<()> {
let selects_mode = |mode: &str| {
self.features
.iter()
.any(|feature| feature.rsplit('/').next() == Some(mode))
};
if selects_mode("uspace") && selects_mode("tls") {
bail!(
"features `uspace` and `tls` select incompatible CPU-local register ownership \
modes"
);
}
for feature in &self.features {
self.validate_feature(feature)?;
}
Ok(())
}
pub(crate) fn validate_feature(&self, feature: &str) -> anyhow::Result<()> {
if feature == "axstd" || feature.starts_with("axstd/") {
bail!(
"feature `{feature}` uses the removed `axstd` alias; use the declared Cargo \
feature name instead"
);
}
if is_removed_dynamic_platform_feature(feature) {
bail!(
"feature `{feature}` is no longer supported; dynamic platform selection is \
automatic, remove the feature from the selected configuration"
);
}
Ok(())
}
pub(crate) fn validated_max_cpu_num(&self) -> anyhow::Result<Option<usize>> {
match self.max_cpu_num {
Some(0) => bail!("max_cpu_num must be greater than 0"),
Some(max_cpu_num) => Ok(Some(max_cpu_num)),
None => Ok(None),
}
}
pub(crate) fn build_cargo_args(target: &str, extra_rustflags: &[String]) -> Vec<String> {
let mut args = vec!["-Z".to_string(), "build-std=core,alloc".to_string()];
let target_key = Path::new(target)
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or(target);
let mut rustflags = extra_rustflags.to_vec();
if target_key.starts_with("loongarch64-") {
rustflags.push("-Ctarget-feature=-ual".to_string());
}
if !rustflags.is_empty() {
args.push("--config".to_string());
let rustflags_toml =
toml::Value::Array(rustflags.into_iter().map(toml::Value::String).collect())
.to_string();
args.push(format!("target.{target_key}.rustflags={rustflags_toml}"));
}
args
}
}
fn bare_target_requires_bin(target: &str) -> bool {
target.starts_with("aarch64-") || target.starts_with("riscv64")
}
impl Default for BuildInfo {
fn default() -> Self {
Self {
env: HashMap::new(),
log: LogLevel::Warn,
features: Vec::new(),
max_cpu_num: None,
}
}
}