blob: 59e42f2d2e67fc7ffb19cb4c253d81ab086c7bc8 [file] [log] [blame]
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::{env, fs, str};
/// Static library that will be built
const LIB_NAME: &str = "musl_math_prefixed";
/// Files that have more than one symbol. Map of file names to the symbols defined in that file.
const MULTIPLE_SYMBOLS: &[(&str, &[&str])] = &[
(
"__invtrigl",
&["__invtrigl", "__invtrigl_R", "__pio2_hi", "__pio2_lo"],
),
("__polevll", &["__polevll", "__p1evll"]),
("erf", &["erf", "erfc"]),
("erff", &["erff", "erfcf"]),
("erfl", &["erfl", "erfcl"]),
("exp10", &["exp10", "pow10"]),
("exp10f", &["exp10f", "pow10f"]),
("exp10l", &["exp10l", "pow10l"]),
("exp2f_data", &["exp2f_data", "__exp2f_data"]),
("exp_data", &["exp_data", "__exp_data"]),
("j0", &["j0", "y0"]),
("j0f", &["j0f", "y0f"]),
("j1", &["j1", "y1"]),
("j1f", &["j1f", "y1f"]),
("jn", &["jn", "yn"]),
("jnf", &["jnf", "ynf"]),
("lgamma", &["lgamma", "__lgamma_r"]),
("remainder", &["remainder", "drem"]),
("remainderf", &["remainderf", "dremf"]),
("lgammaf", &["lgammaf", "lgammaf_r", "__lgammaf_r"]),
("lgammal", &["lgammal", "lgammal_r", "__lgammal_r"]),
("log2_data", &["log2_data", "__log2_data"]),
("log2f_data", &["log2f_data", "__log2f_data"]),
("log_data", &["log_data", "__log_data"]),
("logf_data", &["logf_data", "__logf_data"]),
("pow_data", &["pow_data", "__pow_log_data"]),
("powf_data", &["powf_data", "__powf_log2_data"]),
("signgam", &["signgam", "__signgam"]),
("sqrt_data", &["sqrt_data", "__rsqrt_tab"]),
];
fn main() {
let cfg = Config::from_env();
if cfg.target_env == "msvc"
|| cfg.target_family == "wasm"
|| cfg.target_features.iter().any(|f| f == "thumb-mode")
{
println!(
"cargo::warning=Musl doesn't compile with the current \
target {}; skipping build",
&cfg.target_string
);
return;
}
build_musl_math(&cfg);
}
#[allow(dead_code)]
#[derive(Debug)]
struct Config {
manifest_dir: PathBuf,
out_dir: PathBuf,
musl_dir: PathBuf,
musl_arch: String,
target_arch: String,
target_env: String,
target_family: String,
target_os: String,
target_string: String,
target_vendor: String,
target_features: Vec<String>,
}
impl Config {
fn from_env() -> Self {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let target_features = env::var("CARGO_CFG_TARGET_FEATURE")
.map(|feats| feats.split(',').map(ToOwned::to_owned).collect())
.unwrap_or_default();
let musl_dir = manifest_dir.join("musl");
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let musl_arch = if target_arch == "x86" {
"i386".to_owned()
} else {
target_arch.clone()
};
println!(
"cargo::rerun-if-changed={}/c_patches",
manifest_dir.display()
);
println!("cargo::rerun-if-changed={}", musl_dir.display());
Self {
manifest_dir,
out_dir: PathBuf::from(env::var("OUT_DIR").unwrap()),
musl_dir,
musl_arch,
target_arch,
target_env: env::var("CARGO_CFG_TARGET_ENV").unwrap(),
target_family: env::var("CARGO_CFG_TARGET_FAMILY").unwrap(),
target_os: env::var("CARGO_CFG_TARGET_OS").unwrap(),
target_string: env::var("TARGET").unwrap(),
target_vendor: env::var("CARGO_CFG_TARGET_VENDOR").unwrap(),
target_features,
}
}
}
/// Build musl math symbols to a static library
fn build_musl_math(cfg: &Config) {
let musl_dir = &cfg.musl_dir;
let math = musl_dir.join("src/math");
let arch_dir = musl_dir.join("arch").join(&cfg.musl_arch);
assert!(
math.exists(),
"musl source not found. You may need to run `./ci/update-musl.sh`."
);
let source_map = find_math_source(&math, cfg);
let out_path = cfg.out_dir.join(format!("lib{LIB_NAME}.a"));
// Run configuration steps. Usually done as part of the musl `Makefile`.
let obj_include = cfg.out_dir.join("musl_obj/include");
fs::create_dir_all(&obj_include).unwrap();
fs::create_dir_all(obj_include.join("bits")).unwrap();
let sed_stat = Command::new("sed")
.arg("-f")
.arg(musl_dir.join("tools/mkalltypes.sed"))
.arg(arch_dir.join("bits/alltypes.h.in"))
.arg(musl_dir.join("include/alltypes.h.in"))
.stderr(Stdio::inherit())
.output()
.unwrap();
assert!(
sed_stat.status.success(),
"sed command failed: {:?}",
sed_stat.status
);
fs::write(obj_include.join("bits/alltypes.h"), sed_stat.stdout).unwrap();
let mut cbuild = cc::Build::new();
cbuild
.extra_warnings(false)
.warnings(false)
.flag_if_supported("-Wno-bitwise-op-parentheses")
.flag_if_supported("-Wno-literal-range")
.flag_if_supported("-Wno-parentheses")
.flag_if_supported("-Wno-shift-count-overflow")
.flag_if_supported("-Wno-shift-op-parentheses")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-std=c99")
.flag_if_supported("-ffreestanding")
.flag_if_supported("-nostdinc")
.define("_ALL_SOURCE", "1")
.define(
"ROOT_INCLUDE_FEATURES",
Some(musl_dir.join("include/features.h").to_str().unwrap()),
)
// Our overrides are in this directory
.include(cfg.manifest_dir.join("c_patches"))
.include(musl_dir.join("arch").join(&cfg.musl_arch))
.include(musl_dir.join("arch/generic"))
.include(musl_dir.join("src/include"))
.include(musl_dir.join("src/internal"))
.include(obj_include)
.include(musl_dir.join("include"))
.file(cfg.manifest_dir.join("c_patches/alias.c"));
for (sym_name, src_file) in source_map {
// Build the source file
cbuild.file(src_file);
// Trickery! Redefine the symbol names to have the prefix `musl_`, which allows us to
// differentiate these symbols from whatever we provide.
if let Some((_names, syms)) = MULTIPLE_SYMBOLS
.iter()
.find(|(name, _syms)| *name == sym_name)
{
// Handle the occasional file that defines multiple symbols
for sym in *syms {
cbuild.define(sym, Some(format!("musl_{sym}").as_str()));
}
} else {
// If the file doesn't define multiple symbols, the file name will be the symbol
cbuild.define(&sym_name, Some(format!("musl_{sym_name}").as_str()));
}
}
if cfg!(windows) {
// On Windows we don't have a good way to check symbols, so skip that step.
cbuild.compile(LIB_NAME);
return;
}
let objfiles = cbuild.compile_intermediates();
// We create the archive ourselves with relocations rather than letting `cc` do it so we can
// encourage it to resolve symbols now. This should help avoid accidentally linking the wrong
// thing.
let stat = cbuild
.get_compiler()
.to_command()
.arg("-r")
.arg("-o")
.arg(&out_path)
.args(objfiles)
.status()
.unwrap();
assert!(stat.success());
println!("cargo::rustc-link-lib={LIB_NAME}");
println!("cargo::rustc-link-search=native={}", cfg.out_dir.display());
validate_archive_symbols(&out_path);
}
/// Build a map of `name -> path`. `name` is typically the symbol name, but this doesn't account
/// for files that provide multiple symbols.
fn find_math_source(math_root: &Path, cfg: &Config) -> BTreeMap<String, PathBuf> {
let mut map = BTreeMap::new();
let mut arch_dir = None;
// Locate all files and directories
for item in fs::read_dir(math_root).unwrap() {
let path = item.unwrap().path();
let meta = fs::metadata(&path).unwrap();
if meta.is_dir() {
// Make note of the arch-specific directory if it exists
if path.file_name().unwrap() == cfg.target_arch.as_str() {
arch_dir = Some(path);
}
continue;
}
// Skip non-source files
if path.extension().is_some_and(|ext| ext == "h") {
continue;
}
let sym_name = path.file_stem().unwrap();
map.insert(sym_name.to_str().unwrap().to_owned(), path.to_owned());
}
// If arch-specific versions are available, build those instead.
if let Some(arch_dir) = arch_dir {
for item in fs::read_dir(arch_dir).unwrap() {
let path = item.unwrap().path();
let sym_name = path.file_stem().unwrap();
if path.extension().unwrap() == "s" {
// FIXME: we never build assembly versions since we have no good way to
// rename the symbol (our options are probably preprocessor or objcopy).
continue;
}
map.insert(sym_name.to_str().unwrap().to_owned(), path);
}
}
map
}
/// Make sure we don't have something like a loose unprefixed `_cos` called somewhere, which could
/// wind up linking to system libraries rather than the built musl library.
fn validate_archive_symbols(out_path: &Path) {
const ALLOWED_UNDEF_PFX: &[&str] = &[
// PIC and arch-specific
".TOC",
"_GLOBAL_OFFSET_TABLE_",
"__x86.get_pc_thunk",
// gcc/compiler-rt/compiler-builtins symbols
"__add",
"__aeabi_",
"__div",
"__eq",
"__extend",
"__fix",
"__float",
"__gcc_",
"__ge",
"__gt",
"__le",
"__lshr",
"__lt",
"__mul",
"__ne",
"__stack_chk_fail",
"__stack_chk_guard",
"__sub",
"__trunc",
"__undef",
// string routines
"__bzero",
"bzero",
// FPENV interfaces
"feclearexcept",
"fegetround",
"feraiseexcept",
"fesetround",
"fetestexcept",
];
// List global undefined symbols
let out = Command::new("nm")
.arg("-guj")
.arg(out_path)
.stderr(Stdio::inherit())
.output()
.unwrap();
let undef = str::from_utf8(&out.stdout).unwrap();
let mut undef = undef.lines().collect::<Vec<_>>();
undef.retain(|sym| {
// Account for file formats that add a leading `_`
!ALLOWED_UNDEF_PFX
.iter()
.any(|pfx| sym.starts_with(pfx) || sym[1..].starts_with(pfx))
});
assert!(
undef.is_empty(),
"found disallowed undefined symbols: {undef:#?}"
);
// Find any symbols that are missing the `_musl_` prefix`
let out = Command::new("nm")
.arg("-gUj")
.arg(out_path)
.stderr(Stdio::inherit())
.output()
.unwrap();
let defined = str::from_utf8(&out.stdout).unwrap();
let mut defined = defined.lines().collect::<Vec<_>>();
defined.retain(|sym| {
!(sym.starts_with("_musl_")
|| sym.starts_with("musl_")
|| sym.starts_with("__x86.get_pc_thunk"))
});
assert!(defined.is_empty(), "found unprefixed symbols: {defined:#?}");
}