use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use cargo::core::compiler::{unit_graph::UnitDep, unit_graph::UnitGraph, Executor, Unit};
use cargo::core::profiles::Profiles;
use cargo::core::{FeatureValue, Package, PackageId, Target, TargetKind, Workspace};
use cargo::ops::{self, CompileFilter, CompileOptions, FilterRule, LibRule};
use cargo::util::command_prelude::{ArgMatches, ArgMatchesExt, ProfileChecking, UserIntent};
use cargo::util::interning::InternedString;
use cargo::util::BuildLogger;
use cargo::{CliResult, GlobalContext};
use anyhow::Context as _;
use cargo_platform::Platform;
use cargo_util::paths::{copy, create_dir_all, open, read, read_bytes, write};
use implib::def::ModuleDef;
use implib::{Flavor, ImportLibrary, MachineType};
use semver::Version;
use crate::build_targets::BuildTargets;
use crate::install::InstallPaths;
use crate::pkg_config_gen::PkgConfig;
use crate::target;
fn build_include_file(
ws: &Workspace,
name: &str,
version: Option<&Version>,
root_output: &Path,
root_path: &Path,
) -> anyhow::Result<()> {
ws.gctx()
.shell()
.status("Building", "header file using cbindgen")?;
let mut header_name = PathBuf::from(name);
header_name.set_extension("h");
let include_path = root_output.join(header_name);
let crate_path = root_path;
let mut config = cbindgen::Config::from_root_or_default(crate_path);
if let Some(version) = version {
let warning = config.autogen_warning.unwrap_or_default();
let version_info = format!(
"\n#define {0}_MAJOR {1}\n#define {0}_MINOR {2}\n#define {0}_PATCH {3}\n",
name.to_uppercase().replace('-', "_"),
version.major,
version.minor,
version.patch
);
config.autogen_warning = Some(warning + &version_info);
}
cbindgen::Builder::new()
.with_crate(crate_path)
.with_config(config)
.generate()
.unwrap()
.write_to_file(include_path);
Ok(())
}
fn copy_prebuilt_include_file(
ws: &Workspace,
build_targets: &BuildTargets,
root_output: &Path,
) -> anyhow::Result<()> {
let mut shell = ws.gctx().shell();
shell.status("Populating", "uninstalled header directory")?;
for (from, to) in build_targets.extra.include.iter() {
let to = root_output.join("include").join(to);
create_dir_all(to.parent().unwrap())?;
copy(from, to)?;
}
Ok(())
}
fn build_pc_file(name: &str, root_output: &Path, pc: &PkgConfig) -> anyhow::Result<()> {
let pc_path = root_output.join(format!("{name}.pc"));
let buf = pc.render();
write(pc_path, buf)
}
fn build_pc_files(
ws: &Workspace,
filename: &str,
root_output: &Path,
pc: &PkgConfig,
) -> anyhow::Result<()> {
ws.gctx().shell().status("Building", "pkg-config files")?;
build_pc_file(filename, root_output, pc)?;
let pc_uninstalled = pc.uninstalled(root_output);
build_pc_file(
&format!("{filename}-uninstalled"),
root_output,
&pc_uninstalled,
)
}
fn patch_target(
pkg: &mut Package,
library_types: LibraryTypes,
capi_config: &CApiConfig,
) -> anyhow::Result<()> {
use cargo::core::compiler::CrateType;
let manifest = pkg.manifest_mut();
let targets = manifest.targets_mut();
let mut kinds = Vec::with_capacity(3);
if library_types.rlib {
kinds.push(CrateType::Rlib);
}
if library_types.staticlib {
kinds.push(CrateType::Staticlib);
}
if library_types.cdylib {
kinds.push(CrateType::Cdylib);
}
for target in targets.iter_mut().filter(|t| t.is_lib()) {
target.set_kind(TargetKind::Lib(kinds.to_vec()));
target.set_name(&capi_config.library.name);
}
Ok(())
}
fn build_def_file(
ws: &Workspace,
name: &str,
target: &target::Target,
targetdir: &Path,
) -> anyhow::Result<()> {
if target.os == "windows" && target.env == "msvc" {
ws.gctx().shell().status("Building", ".def file")?;
let dll_path = targetdir.join(format!("{}.dll", name.replace('-', "_")));
let dll_content = std::fs::read(&dll_path)?;
let dll_file = object::File::parse(&*dll_content)?;
let def_file = cargo_util::paths::create(targetdir.join(format!("{name}.def")))?;
write_def_file(dll_file, def_file)?;
}
Ok(())
}
fn write_def_file<W: std::io::Write>(dll_file: object::File, mut def_file: W) -> anyhow::Result<W> {
use object::read::Object;
writeln!(def_file, "EXPORTS")?;
for export in dll_file.exports()? {
def_file.write_all(export.name())?;
def_file.write_all(b"\n")?;
}
Ok(def_file)
}
fn build_implib_file(
ws: &Workspace,
build_targets: &BuildTargets,
name: &str,
target: &target::Target,
targetdir: &Path,
) -> anyhow::Result<()> {
if target.os == "windows" || target.os == "cygwin" {
ws.gctx().shell().status("Building", "implib")?;
let def_path = targetdir.join(format!("{name}.def"));
let def_contents = cargo_util::paths::read(&def_path)?;
let flavor = match target.env.as_str() {
"msvc" => Flavor::Msvc,
_ => Flavor::Gnu,
};
let machine_type = match target.arch.as_str() {
"x86_64" => MachineType::AMD64,
"x86" => MachineType::I386,
"aarch64" => MachineType::ARM64,
_ => {
return Err(anyhow::anyhow!(
"Windows support for {} is not implemented yet.",
target.arch
))
}
};
let lib_name = build_targets
.shared_output_file_name()
.unwrap()
.into_string()
.unwrap();
let implib_path = build_targets.impl_lib.as_ref().unwrap();
let implib_file = cargo_util::paths::create(implib_path)?;
write_implib(implib_file, lib_name, machine_type, flavor, &def_contents)?;
}
Ok(())
}
fn write_implib<W: std::io::Write + std::io::Seek>(
mut w: W,
lib_name: String,
machine_type: MachineType,
flavor: Flavor,
def_contents: &str,
) -> anyhow::Result<W> {
let mut module_def = ModuleDef::parse(def_contents, machine_type)?;
module_def.import_name = lib_name;
let import_library = ImportLibrary::from_def(module_def, machine_type, flavor);
import_library.write_to(&mut w)?;
Ok(w)
}
#[derive(Debug)]
struct FingerPrint {
id: PackageId,
root_output: PathBuf,
build_targets: BuildTargets,
install_paths: InstallPaths,
static_libs: String,
hasher: DefaultHasher,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct Cache {
hash: String,
static_libs: String,
}
impl FingerPrint {
fn new(
id: &PackageId,
root_output: &Path,
build_targets: &BuildTargets,
install_paths: &InstallPaths,
capi_config: &CApiConfig,
) -> Self {
let mut hasher = DefaultHasher::new();
capi_config.hash(&mut hasher);
Self {
id: id.to_owned(),
root_output: root_output.to_owned(),
build_targets: build_targets.clone(),
install_paths: install_paths.clone(),
static_libs: String::new(),
hasher,
}
}
fn hash(&self) -> anyhow::Result<Option<String>> {
let mut hasher = self.hasher.clone();
self.install_paths.hash(&mut hasher);
let mut paths: Vec<&PathBuf> = Vec::new();
if let Some(include) = &self.build_targets.include {
paths.push(include);
}
paths.extend(&self.build_targets.static_lib);
paths.extend(&self.build_targets.shared_lib);
for path in paths.iter() {
if let Ok(buf) = read_bytes(path) {
hasher.write(&buf);
} else {
return Ok(None);
};
}
let hash = hasher.finish();
Ok(Some(hash.to_string()))
}
fn path(&self) -> PathBuf {
self.root_output
.join(format!("cargo-c-{}.cache", self.id.name()))
}
fn load_previous(&self) -> anyhow::Result<Cache> {
let mut f = open(self.path())?;
let mut cache_str = String::new();
f.read_to_string(&mut cache_str)?;
let cache = toml::from_str(&cache_str)?;
Ok(cache)
}
fn is_valid(&self) -> bool {
match (self.load_previous(), self.hash()) {
(Ok(prev), Ok(Some(current))) => prev.hash == current,
_ => false,
}
}
fn store(&self) -> anyhow::Result<()> {
if let Some(hash) = self.hash()? {
let cache = Cache {
hash,
static_libs: self.static_libs.to_owned(),
};
let buf = toml::to_string(&cache)?;
write(self.path(), buf)?;
}
Ok(())
}
}
#[derive(Debug, Hash)]
pub struct CApiConfig {
pub header: HeaderCApiConfig,
pub pkg_config: PkgConfigCApiConfig,
pub library: LibraryCApiConfig,
pub install: InstallCApiConfig,
}
#[derive(Debug, Hash)]
pub struct HeaderCApiConfig {
pub name: String,
pub subdirectory: String,
pub generation: bool,
pub enabled: bool,
pub emit_version_constants: bool,
}
#[derive(Debug, Hash)]
pub struct PkgConfigCApiConfig {
pub name: String,
pub filename: String,
pub description: String,
pub version: String,
pub requires: Option<String>,
pub requires_private: Option<String>,
pub strip_include_path_components: usize,
}
#[derive(Debug, Hash)]
pub enum VersionSuffix {
Major,
MajorMinor,
MajorMinorPatch,
}
#[derive(Debug, Hash)]
pub struct LibraryCApiConfig {
pub name: String,
pub version: Version,
pub install_subdir: Option<String>,
pub versioning: bool,
pub version_suffix_components: Option<VersionSuffix>,
pub import_library: bool,
pub rustflags: Vec<String>,
}
impl LibraryCApiConfig {
pub fn sover(&self) -> String {
let major = self.version.major;
let minor = self.version.minor;
let patch = self.version.patch;
match self.version_suffix_components {
None => match (major, minor, patch) {
(0, 0, patch) => format!("0.0.{patch}"),
(0, minor, _) => format!("0.{minor}"),
(major, _, _) => format!("{major}"),
},
Some(VersionSuffix::Major) => format!("{major}"),
Some(VersionSuffix::MajorMinor) => format!("{major}.{minor}"),
Some(VersionSuffix::MajorMinorPatch) => format!("{major}.{minor}.{patch}"),
}
}
}
#[derive(Debug, Default, Hash)]
pub struct InstallCApiConfig {
pub include: Vec<InstallTarget>,
pub data: Vec<InstallTarget>,
}
#[derive(Debug, Hash)]
pub enum InstallTarget {
Asset(InstallTargetPaths),
Generated(InstallTargetPaths),
}
#[derive(Clone, Debug, Hash)]
pub struct InstallTargetPaths {
pub from: String,
pub to: String,
}
impl InstallTargetPaths {
pub fn from_value(value: &toml::value::Value, default_to: &str) -> anyhow::Result<Self> {
let from = value
.get("from")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("a from field is required"))?;
let to = value
.get("to")
.and_then(|v| v.as_str())
.unwrap_or(default_to);
Ok(InstallTargetPaths {
from: from.to_string(),
to: to.to_string(),
})
}
pub fn install_paths(
&self,
root: &Path,
) -> anyhow::Result<impl Iterator<Item = (PathBuf, PathBuf)>> {
let pattern = root.join(&self.from);
let base_pattern = if self.from.contains("/**") {
pattern
.iter()
.take_while(|&c| c != std::ffi::OsStr::new("**"))
.collect()
} else {
pattern.parent().unwrap().to_path_buf()
};
let pattern = pattern.to_str().unwrap();
let to = PathBuf::from(&self.to);
let g = glob::glob(pattern)?.filter_map(move |p| {
if let Ok(p) = p {
if p.is_file() {
let from = p;
let to = to.join(from.strip_prefix(&base_pattern).unwrap());
Some((from, to))
} else {
None
}
} else {
None
}
});
Ok(g)
}
}
fn load_manifest_capi_config(
pkg: &Package,
rustc_target: &target::Target,
) -> anyhow::Result<CApiConfig> {
let name = &pkg
.manifest()
.targets()
.iter()
.find(|t| t.is_lib())
.unwrap()
.crate_name();
let root_path = pkg.root().to_path_buf();
let manifest_str = read(&root_path.join("Cargo.toml"))?;
let toml = manifest_str.parse::<toml::Table>()?;
let capi = toml
.get("package")
.and_then(|v| v.get("metadata"))
.and_then(|v| v.get("capi"));
if let Some(min_version) = capi
.as_ref()
.and_then(|capi| capi.get("min_version"))
.and_then(|v| v.as_str())
{
let min_version = Version::parse(min_version)?;
let version = Version::parse(env!("CARGO_PKG_VERSION"))?;
if min_version > version {
anyhow::bail!(
"Minimum required cargo-c version is {} but using cargo-c version {}",
min_version,
version
);
}
}
let header = capi.and_then(|v| v.get("header"));
let subdirectory = header
.as_ref()
.and_then(|h| h.get("subdirectory"))
.map(|v| {
if let Ok(b) = v.clone().try_into::<bool>() {
Ok(if b {
String::from(name)
} else {
String::from("")
})
} else {
v.clone().try_into::<String>()
}
})
.unwrap_or_else(|| Ok(String::from(name)))?;
let header = if let Some(capi) = capi {
HeaderCApiConfig {
name: header
.as_ref()
.and_then(|h| h.get("name"))
.or_else(|| capi.get("header_name"))
.map(|v| v.clone().try_into())
.unwrap_or_else(|| Ok(String::from(name)))?,
subdirectory,
generation: header
.as_ref()
.and_then(|h| h.get("generation"))
.map(|v| v.clone().try_into())
.unwrap_or(Ok(true))?,
enabled: header
.as_ref()
.and_then(|h| h.get("enabled"))
.map(|v| v.clone().try_into())
.unwrap_or(Ok(true))?,
emit_version_constants: header
.as_ref()
.and_then(|h| h.get("emit_version_constants"))
.map(|v| v.clone().try_into())
.unwrap_or(Ok(true))?,
}
} else {
HeaderCApiConfig {
name: String::from(name),
subdirectory: String::from(name),
generation: true,
enabled: true,
emit_version_constants: true,
}
};
let pc = capi.and_then(|v| v.get("pkg_config"));
let mut pc_name = String::from(name);
let mut pc_filename = String::from(name);
let mut description = String::from(
pkg.manifest()
.metadata()
.description
.as_deref()
.unwrap_or(""),
);
let mut version = pkg.version().to_string();
let mut requires = None;
let mut requires_private = None;
let mut strip_include_path_components = 0;
if let Some(pc) = pc {
if let Some(override_name) = pc.get("name").and_then(|v| v.as_str()) {
pc_name = String::from(override_name);
}
if let Some(override_filename) = pc.get("filename").and_then(|v| v.as_str()) {
pc_filename = String::from(override_filename);
}
if let Some(override_description) = pc.get("description").and_then(|v| v.as_str()) {
description = String::from(override_description);
}
if let Some(override_version) = pc.get("version").and_then(|v| v.as_str()) {
version = String::from(override_version);
}
if let Some(req) = pc.get("requires").and_then(|v| v.as_str()) {
requires = Some(String::from(req));
}
if let Some(req) = pc.get("requires_private").and_then(|v| v.as_str()) {
requires_private = Some(String::from(req));
}
strip_include_path_components = pc
.get("strip_include_path_components")
.map(|v| v.clone().try_into())
.unwrap_or_else(|| Ok(0))?
}
let pkg_config = PkgConfigCApiConfig {
name: pc_name,
filename: pc_filename,
description,
version,
requires,
requires_private,
strip_include_path_components,
};
let library = capi.and_then(|v| v.get("library"));
let mut lib_name = String::from(name);
let mut version = pkg.version().clone();
let mut install_subdir = None;
let mut versioning = true;
let mut version_suffix_components = None;
let mut import_library = true;
let mut rustflags = Vec::new();
if let Some(library) = library {
if let Some(override_name) = library.get("name").and_then(|v| v.as_str()) {
lib_name = String::from(override_name);
}
if let Some(override_version) = library.get("version").and_then(|v| v.as_str()) {
version = Version::parse(override_version)?;
}
if let Some(subdir) = library.get("install_subdir").and_then(|v| v.as_str()) {
install_subdir = Some(String::from(subdir));
}
versioning = library
.get("versioning")
.and_then(|v| v.as_bool())
.unwrap_or(true);
if let Some(value) = library.get("version_suffix_components") {
let value = value.as_integer().with_context(|| {
format!("Value for `version_suffix_components` is not an integer: {value:?}")
})?;
version_suffix_components = Some(match value {
1 => VersionSuffix::Major,
2 => VersionSuffix::MajorMinor,
3 => VersionSuffix::MajorMinorPatch,
_ => anyhow::bail!("Out of range value for version suffix components: {value}"),
});
}
fn make_args(args: &str) -> impl Iterator<Item = String> + use<'_> {
args.split(' ')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
}
import_library = library
.get("import_library")
.and_then(|v| v.as_bool())
.unwrap_or(true);
if let Some(args) = library.get("rustflags").and_then(|v| v.as_str()) {
rustflags.extend(make_args(args));
}
if let Some(args) = library.get("target").and_then(|v| v.as_table()) {
let args = args
.iter()
.filter_map(|(p, v)| {
if Platform::from_str(p)
.ok()
.is_some_and(|p| p.matches(name, &rustc_target.cfg))
{
v.get("rustflags").and_then(|v| v.as_str())
} else {
None
}
})
.flat_map(make_args);
rustflags.extend(args)
}
}
if rustc_target.os == "android" {
versioning = false;
}
let library = LibraryCApiConfig {
name: lib_name,
version,
install_subdir,
versioning,
version_suffix_components,
import_library,
rustflags,
};
let default_assets_include = InstallTargetPaths {
from: "assets/capi/include/**/*".to_string(),
to: header.subdirectory.clone(),
};
let header_name = if header.name.ends_with(".h") {
format!("assets/{}", header.name)
} else {
format!("assets/{}.h", header.name)
};
let default_legacy_asset_include = InstallTargetPaths {
from: header_name,
to: header.subdirectory.clone(),
};
let default_generated_include = InstallTargetPaths {
from: "capi/include/**/*".to_string(),
to: header.subdirectory.clone(),
};
let mut include_targets = vec![
InstallTarget::Asset(default_assets_include),
InstallTarget::Asset(default_legacy_asset_include),
InstallTarget::Generated(default_generated_include),
];
let mut data_targets = Vec::new();
let mut data_subdirectory = name.clone();
fn custom_install_target_paths(
root: &toml::Value,
subdirectory: &str,
targets: &mut Vec<InstallTarget>,
) -> anyhow::Result<()> {
if let Some(assets) = root.get("asset").and_then(|v| v.as_array()) {
for asset in assets {
let target_paths = InstallTargetPaths::from_value(asset, subdirectory)?;
targets.push(InstallTarget::Asset(target_paths));
}
}
if let Some(generated) = root.get("generated").and_then(|v| v.as_array()) {
for gen in generated {
let target_paths = InstallTargetPaths::from_value(gen, subdirectory)?;
targets.push(InstallTarget::Generated(target_paths));
}
}
Ok(())
}
let install = capi.and_then(|v| v.get("install"));
if let Some(install) = install {
if let Some(includes) = install.get("include") {
custom_install_target_paths(includes, &header.subdirectory, &mut include_targets)?;
}
if let Some(data) = install.get("data") {
if let Some(subdir) = data.get("subdirectory").and_then(|v| v.as_str()) {
data_subdirectory = String::from(subdir);
}
custom_install_target_paths(data, &data_subdirectory, &mut data_targets)?;
}
}
let default_assets_data = InstallTargetPaths {
from: "assets/capi/share/**/*".to_string(),
to: data_subdirectory.clone(),
};
let default_generated_data = InstallTargetPaths {
from: "capi/share/**/*".to_string(),
to: data_subdirectory,
};
data_targets.extend([
InstallTarget::Asset(default_assets_data),
InstallTarget::Generated(default_generated_data),
]);
let install = InstallCApiConfig {
include: include_targets,
data: data_targets,
};
Ok(CApiConfig {
header,
pkg_config,
library,
install,
})
}
fn compile_options(
ws: &Workspace,
gctx: &GlobalContext,
args: &ArgMatches,
profile: InternedString,
compile_intent: UserIntent,
) -> anyhow::Result<CompileOptions> {
use cargo::core::compiler::CompileKind;
let mut compile_opts =
args.compile_options(gctx, compile_intent, Some(ws), ProfileChecking::Custom)?;
compile_opts.build_config.requested_profile = profile;
std::rc::Rc::get_mut(&mut compile_opts.cli_features.features)
.unwrap()
.insert(FeatureValue::new("capi".into()));
compile_opts.filter = CompileFilter::new(
LibRule::True,
FilterRule::none(),
FilterRule::none(),
FilterRule::none(),
FilterRule::none(),
);
compile_opts.build_config.unit_graph = false;
let rustc = gctx.load_global_rustc(Some(ws))?;
if compile_opts.build_config.requested_kinds[0].is_host() {
compile_opts.build_config.requested_kinds =
CompileKind::from_requested_targets(gctx, &[rustc.host.to_string()])?
}
Ok(compile_opts)
}
#[derive(Default)]
struct Exec {
ran: AtomicBool,
link_line: Mutex<HashMap<PackageId, String>>,
}
use cargo::CargoResult;
use cargo_util::ProcessBuilder;
impl Executor for Exec {
fn exec(
&self,
cmd: &ProcessBuilder,
id: PackageId,
_target: &Target,
_mode: CompileMode,
on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
) -> CargoResult<()> {
self.ran.store(true, Ordering::Relaxed);
cmd.exec_with_streaming(
on_stdout_line,
&mut |s| {
#[derive(serde::Deserialize, Debug)]
struct Message {
message: String,
level: String,
}
if let Ok(msg) = serde_json::from_str::<Message>(s) {
if msg.level == "note" {
if msg.message.starts_with("Link against the following native artifacts when linking against this static library") {
Ok(())
} else if let Some(link_line) = msg.message.strip_prefix("native-static-libs:") {
self.link_line.lock().unwrap().insert(id, link_line.to_string());
Ok(())
} else {
on_stderr_line(s)
}
} else {
on_stderr_line(s)
}
} else {
on_stderr_line(s)
}
},
false,
)
.map(drop)
}
}
use cargo::core::compiler::{unit_graph, CompileMode, UnitInterner};
use cargo::ops::create_bcx;
fn set_deps_args(
dep: &UnitDep,
graph: &UnitGraph,
extra_compiler_args: &mut HashMap<Unit, Vec<String>>,
global_args: &[String],
) {
if !dep.unit_for.is_for_host() {
for dep in graph[&dep.unit].iter() {
set_deps_args(dep, graph, extra_compiler_args, global_args);
}
extra_compiler_args
.entry(dep.unit.clone())
.or_insert_with(|| global_args.to_owned());
}
}
fn compile_with_exec(
ws: &Workspace<'_>,
options: &CompileOptions,
exec: &Arc<dyn Executor>,
rustc_target: &target::Target,
root_output: &Path,
args: &ArgMatches,
) -> CargoResult<HashMap<PackageId, PathBuf>> {
ws.emit_warnings()?;
let interner = UnitInterner::new();
let logger = BuildLogger::maybe_new(ws, &options.build_config)?;
let mut bcx = create_bcx(ws, options, &interner, logger.as_ref())?;
let unit_graph = &bcx.unit_graph;
let extra_compiler_args = &mut bcx.extra_compiler_args;
for unit in bcx.roots.iter() {
let pkg = &unit.pkg;
let capi_config = load_manifest_capi_config(pkg, rustc_target)?;
let name = &capi_config.library.name;
let install_paths = InstallPaths::new(name, rustc_target, args, &capi_config);
let install_path_lib = install_paths.lib_install_dir(&capi_config);
let pkg_rustflags = &capi_config.library.rustflags;
let mut leaf_args: Vec<String> = rustc_target
.shared_object_link_args(&capi_config, &install_path_lib, root_output)
.into_iter()
.flat_map(|l| ["-C".to_string(), format!("link-arg={l}")])
.collect();
leaf_args.extend(pkg_rustflags.clone());
leaf_args.push("--cfg".into());
leaf_args.push("cargo_c".into());
leaf_args.push("--print".into());
leaf_args.push("native-static-libs".into());
if args.flag("crt-static") {
leaf_args.push("-C".into());
leaf_args.push("target-feature=+crt-static".into());
}
extra_compiler_args.insert(unit.clone(), leaf_args.to_owned());
for dep in unit_graph[unit].iter() {
set_deps_args(dep, unit_graph, extra_compiler_args, pkg_rustflags);
}
}
if options.build_config.unit_graph {
unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.gctx())?;
return Ok(HashMap::new());
}
let cx = cargo::core::compiler::BuildRunner::new(&bcx)?;
let r = cx.compile(exec)?;
let out_dirs = r
.cdylibs
.iter()
.filter_map(|l| {
let id = l.unit.pkg.package_id();
l.script_metas.iter().flatten().find_map(|m| {
if let Some(env) = r.extra_env.get(m) {
env.iter().find_map(|e| {
if e.0 == "OUT_DIR" {
Some((id, PathBuf::from(&e.1)))
} else {
None
}
})
} else {
None
}
})
})
.collect();
Ok(out_dirs)
}
#[derive(Debug)]
pub struct CPackage {
pub version: Version,
pub root_path: PathBuf,
pub capi_config: CApiConfig,
pub build_targets: BuildTargets,
pub install_paths: InstallPaths,
finger_print: FingerPrint,
}
impl CPackage {
fn from_package(
pkg: &mut Package,
args: &ArgMatches,
library_types: LibraryTypes,
rustc_target: &target::Target,
root_output: &Path,
) -> anyhow::Result<CPackage> {
let id = pkg.package_id();
let version = pkg.version().clone();
let root_path = pkg.root().to_path_buf();
let capi_config = load_manifest_capi_config(pkg, rustc_target)?;
patch_target(pkg, library_types, &capi_config)?;
let name = &capi_config.library.name;
let install_paths = InstallPaths::new(name, rustc_target, args, &capi_config);
let build_targets = BuildTargets::new(
name,
rustc_target,
root_output,
library_types,
&capi_config,
args.get_flag("meson"),
)?;
let finger_print = FingerPrint::new(
&id,
root_output,
&build_targets,
&install_paths,
&capi_config,
);
Ok(CPackage {
version,
root_path,
capi_config,
build_targets,
install_paths,
finger_print,
})
}
}
fn deprecation_warnings(ws: &Workspace, args: &ArgMatches) -> anyhow::Result<()> {
if args.contains_id("dlltool") {
ws.gctx()
.shell()
.warn("The `dlltool` support is now builtin. The cli option is deprecated and will be removed in the future")?;
}
Ok(())
}
#[derive(Debug, Clone, Copy)]
pub struct LibraryTypes {
pub staticlib: bool,
pub cdylib: bool,
pub rlib: bool,
}
impl LibraryTypes {
fn from_target(target: &target::Target) -> Self {
Self {
staticlib: true,
cdylib: target.os != "none",
rlib: false,
}
}
fn from_args(target: &target::Target, args: &ArgMatches, want_rlib: bool) -> Self {
Self {
rlib: want_rlib,
..match args.get_many::<String>("library-type") {
Some(library_types) => Self::from_library_types(target, library_types),
None => Self::from_target(target),
}
}
}
fn from_library_types<S: AsRef<str>>(
target: &target::Target,
library_types: impl Iterator<Item = S>,
) -> Self {
let (mut staticlib, mut cdylib) = (false, false);
for library_type in library_types {
staticlib |= library_type.as_ref() == "staticlib";
cdylib |= library_type.as_ref() == "cdylib";
}
cdylib &= target.os != "none";
Self {
staticlib,
cdylib,
rlib: false,
}
}
const fn only_staticlib(self) -> bool {
self.staticlib && !self.cdylib
}
const fn only_cdylib(self) -> bool {
self.cdylib && !self.staticlib
}
}
fn static_libraries(link_line: &str, rustc_target: &target::Target) -> String {
link_line
.trim()
.split(' ')
.filter(|s| {
if rustc_target.env == "msvc" && s.starts_with("/defaultlib") {
return false;
}
!s.is_empty()
})
.map(|lib| {
if rustc_target.env == "msvc" && lib.ends_with(".lib") {
return format!("-l{}", lib.trim_end_matches(".lib"));
}
lib.trim().to_string()
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
pub fn cbuild(
ws: &mut Workspace,
config: &GlobalContext,
args: &ArgMatches,
default_profile: &str,
tests: bool,
) -> anyhow::Result<(Vec<CPackage>, CompileOptions)> {
deprecation_warnings(ws, args)?;
let (target, is_target_overridden) = match args.targets()?.as_slice() {
[] => (config.load_global_rustc(Some(ws))?.host.to_string(), false),
[target] => (target.to_string(), true),
[..] => anyhow::bail!("Multiple targets not supported yet"),
};
let rustc_target = target::Target::new(Some(&target), is_target_overridden)?;
let library_types = LibraryTypes::from_args(&rustc_target, args, tests);
let profile = args.get_profile_name(default_profile, ProfileChecking::Custom)?;
let profiles = Profiles::new(ws, profile)?;
let mut compile_opts = compile_options(ws, config, args, profile, UserIntent::Build)?;
let root_output = ws
.target_dir()
.as_path_unlocked()
.to_path_buf()
.join(PathBuf::from(target))
.join(profiles.get_dir_name());
let mut members = Vec::new();
let mut pristine = false;
let requested: Vec<_> = compile_opts
.spec
.get_packages(ws)?
.iter()
.map(|p| p.package_id())
.collect();
let capi_feature = InternedString::new("capi");
let is_relevant_package = |package: &Package| {
package.library().is_some()
&& package.summary().features().contains_key(&capi_feature)
&& requested.contains(&package.package_id())
};
for m in ws.members_mut().filter(|p| is_relevant_package(p)) {
let cpkg = CPackage::from_package(m, args, library_types, &rustc_target, &root_output)?;
pristine |= cpkg.finger_print.load_previous().is_err() || !cpkg.finger_print.is_valid();
members.push(cpkg);
}
compile_opts.build_config.force_rebuild |= pristine;
let exec = Arc::new(Exec::default());
let out_dirs = compile_with_exec(
ws,
&compile_opts,
&(exec.clone() as Arc<dyn Executor>),
&rustc_target,
&root_output,
args,
)?;
for cpkg in members.iter_mut() {
let out_dir = out_dirs.get(&cpkg.finger_print.id).map(|p| p.as_path());
cpkg.build_targets
.extra
.setup(&cpkg.capi_config, &cpkg.root_path, out_dir)?;
if cpkg.capi_config.header.generation {
let mut header_name = PathBuf::from(&cpkg.capi_config.header.name);
header_name.set_extension("h");
let from = root_output.join(&header_name);
let to = Path::new(&cpkg.capi_config.header.subdirectory).join(&header_name);
cpkg.build_targets.extra.include.push((from, to));
}
}
if pristine {
compile_opts.build_config.force_rebuild = false;
}
let new_build = exec.ran.load(Ordering::Relaxed);
for cpkg in members.iter_mut() {
if new_build {
let name = &cpkg.capi_config.library.name;
let (pkg_config_static_libs, static_libs) = if library_types.only_cdylib() {
(String::new(), String::new())
} else if let Some(libs) = exec.link_line.lock().unwrap().get(&cpkg.finger_print.id) {
(static_libraries(libs, &rustc_target), libs.to_string())
} else {
(String::new(), String::new())
};
let capi_config = &cpkg.capi_config;
let build_targets = &cpkg.build_targets;
let mut pc = PkgConfig::from_workspace(name, &cpkg.install_paths, args, capi_config);
if library_types.only_staticlib() {
pc.add_lib(&pkg_config_static_libs);
}
pc.add_lib_private(&pkg_config_static_libs);
build_pc_files(ws, &capi_config.pkg_config.filename, &root_output, &pc)?;
if !library_types.only_staticlib() && capi_config.library.import_library {
let lib_name = name;
build_def_file(ws, lib_name, &rustc_target, &root_output)?;
build_implib_file(ws, build_targets, lib_name, &rustc_target, &root_output)?;
}
if capi_config.header.enabled {
let header_name = &capi_config.header.name;
let emit_version_constants = capi_config.header.emit_version_constants;
if capi_config.header.generation {
build_include_file(
ws,
header_name,
emit_version_constants.then_some(&cpkg.version),
&root_output,
&cpkg.root_path,
)?;
}
copy_prebuilt_include_file(ws, build_targets, &root_output)?;
}
if name.contains('-') {
let from_build_targets = BuildTargets::new(
&name.replace('-', "_"),
&rustc_target,
&root_output,
library_types,
capi_config,
args.get_flag("meson"),
)?;
if let (Some(from_static_lib), Some(to_static_lib)) = (
from_build_targets.static_lib.as_ref(),
build_targets.static_lib.as_ref(),
) {
copy(from_static_lib, to_static_lib)?;
}
if let (Some(from_shared_lib), Some(to_shared_lib)) = (
from_build_targets.shared_lib.as_ref(),
build_targets.shared_lib.as_ref(),
) {
copy(from_shared_lib, to_shared_lib)?;
}
if let (Some(from_debug_info), Some(to_debug_info)) = (
from_build_targets.debug_info.as_ref(),
build_targets.debug_info.as_ref(),
) {
if from_debug_info.exists() {
create_dir_all(to_debug_info.parent().unwrap())?;
if from_debug_info.is_dir() {
let files =
from_debug_info.read_dir()?.collect::<Result<Vec<_>, _>>()?;
for f in files.iter() {
let src = f.path();
let file_name = src.strip_prefix(from_debug_info)?;
let dst = to_debug_info.join(file_name);
match std::fs::create_dir_all(
dst.parent().expect("Source path is not complete"),
) {
Ok(()) => Ok(()),
Err(v) => {
if v.kind() == ErrorKind::AlreadyExists {
Ok(())
} else {
Err(v)
}
}
}?;
copy(src, dst)?;
}
} else {
copy(from_debug_info, to_debug_info)?;
}
}
}
}
cpkg.finger_print.static_libs = static_libs;
cpkg.finger_print.store()?;
} else {
cpkg.finger_print.static_libs = cpkg.finger_print.load_previous()?.static_libs;
}
ws.gctx().shell().verbose(|s| {
let path = &format!("PKG_CONFIG_PATH=\"{}\"", root_output.display());
s.note(path)
})?;
}
Ok((members, compile_opts))
}
pub fn ctest(
ws: &Workspace,
args: &ArgMatches,
packages: &[CPackage],
mut compile_opts: CompileOptions,
) -> CliResult {
compile_opts.build_config.requested_profile =
args.get_profile_name("test", ProfileChecking::Custom)?;
compile_opts.build_config.intent = UserIntent::Test;
compile_opts.filter = ops::CompileFilter::new(
LibRule::Default, FilterRule::none(), FilterRule::All, FilterRule::none(), FilterRule::none(), );
compile_opts.target_rustc_args = None;
let ops = ops::TestOptions {
no_run: args.flag("no-run"),
no_fail_fast: args.flag("no-fail-fast"),
compile_opts,
};
let test_args = args.get_one::<String>("TESTNAME").into_iter();
let test_args = test_args.chain(args.get_many::<String>("args").unwrap_or_default());
let test_args = test_args.map(String::as_str).collect::<Vec<_>>();
use std::ffi::OsString;
let mut cflags = OsString::new();
for pkg in packages {
let static_lib_path = pkg.build_targets.static_lib.as_ref().unwrap();
let builddir = static_lib_path.parent().unwrap();
cflags.push("-I");
cflags.push(builddir);
cflags.push(" ");
cflags.push(static_lib_path);
cflags.push(" ");
cflags.push(&pkg.finger_print.static_libs);
}
std::env::set_var("INLINE_C_RS_CFLAGS", cflags);
ops::run_tests(ws, &ops, &test_args)
}
#[cfg(test)]
mod tests {
use super::*;
use semver::Version;
fn make_test_library_config(version: &str) -> LibraryCApiConfig {
LibraryCApiConfig {
name: "example".to_string(),
version: Version::parse(version).unwrap(),
install_subdir: None,
versioning: true,
version_suffix_components: None,
import_library: true,
rustflags: vec![],
}
}
#[test]
pub fn test_semver_zero_zero_zero() {
let library = make_test_library_config("0.0.0");
let sover = library.sover();
assert_eq!(sover, "0.0.0");
}
#[test]
pub fn test_semver_zero_one_zero() {
let library = make_test_library_config("0.1.0");
let sover = library.sover();
assert_eq!(sover, "0.1");
}
#[test]
pub fn test_semver_one_zero_zero() {
let library = make_test_library_config("1.0.0");
let sover = library.sover();
assert_eq!(sover, "1");
}
#[test]
pub fn text_one_fixed_zero_zero_zero() {
let mut library = make_test_library_config("0.0.0");
library.version_suffix_components = Some(VersionSuffix::Major);
let sover = library.sover();
assert_eq!(sover, "0");
}
#[test]
pub fn text_two_fixed_one_zero_zero() {
let mut library = make_test_library_config("1.0.0");
library.version_suffix_components = Some(VersionSuffix::MajorMinor);
let sover = library.sover();
assert_eq!(sover, "1.0");
}
#[test]
pub fn text_three_fixed_one_zero_zero() {
let mut library = make_test_library_config("1.0.0");
library.version_suffix_components = Some(VersionSuffix::MajorMinorPatch);
let sover = library.sover();
assert_eq!(sover, "1.0.0");
}
#[test]
pub fn test_lib_listing() {
let libs_osx = "-lSystem -lc -lm";
let libs_linux = "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc";
let libs_hurd = "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc";
let libs_msvc = "kernel32.lib advapi32.lib kernel32.lib ntdll.lib userenv.lib ws2_32.lib kernel32.lib ws2_32.lib kernel32.lib msvcrt.lib /defaultlib:msvcrt";
let libs_mingw = "-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32";
let target_osx = target::Target::new(Some("x86_64-apple-darwin"), false).unwrap();
let target_linux = target::Target::new(Some("x86_64-unknown-linux-gnu"), false).unwrap();
let target_hurd = target::Target::new(Some("x86_64-unknown-hurd-gnu"), false).unwrap();
let target_msvc = target::Target::new(Some("x86_64-pc-windows-msvc"), false).unwrap();
let target_mingw = target::Target::new(Some("x86_64-pc-windows-gnu"), false).unwrap();
assert_eq!(static_libraries(libs_osx, &target_osx), "-lSystem -lc -lm");
assert_eq!(
static_libraries(libs_linux, &target_linux),
"-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
);
assert_eq!(
static_libraries(libs_hurd, &target_hurd),
"-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
);
assert_eq!(
static_libraries(libs_msvc, &target_msvc),
"-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32 -lmsvcrt"
);
assert_eq!(
static_libraries(libs_mingw, &target_mingw),
"-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32"
);
}
}