1use std::collections::HashMap;
2use std::hash::{DefaultHasher, Hash, Hasher};
3use std::io::{ErrorKind, Read};
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Mutex};
8
9use cargo::core::compiler::{unit_graph::UnitDep, unit_graph::UnitGraph, Executor, Unit};
10use cargo::core::profiles::Profiles;
11use cargo::core::{FeatureValue, Package, PackageId, Target, TargetKind, Workspace};
12use cargo::ops::{self, CompileFilter, CompileOptions, FilterRule, LibRule};
13use cargo::util::command_prelude::{ArgMatches, ArgMatchesExt, ProfileChecking, UserIntent};
14use cargo::util::interning::InternedString;
15use cargo::{CliResult, GlobalContext};
16
17use anyhow::Context as _;
18use cargo_platform::Platform;
19use cargo_util::paths::{copy, create_dir_all, open, read, read_bytes, write};
20use implib::def::ModuleDef;
21use implib::{Flavor, ImportLibrary, MachineType};
22use semver::Version;
23
24use crate::build_targets::BuildTargets;
25use crate::install::InstallPaths;
26use crate::pkg_config_gen::PkgConfig;
27use crate::target;
28
29fn build_include_file(
31 ws: &Workspace,
32 name: &str,
33 version: Option<&Version>,
34 root_output: &Path,
35 root_path: &Path,
36) -> anyhow::Result<()> {
37 ws.gctx()
38 .shell()
39 .status("Building", "header file using cbindgen")?;
40 let mut header_name = PathBuf::from(name);
41 header_name.set_extension("h");
42 let include_path = root_output.join(header_name);
43 let crate_path = root_path;
44
45 let mut config = cbindgen::Config::from_root_or_default(crate_path);
47 if let Some(version) = version {
48 let warning = config.autogen_warning.unwrap_or_default();
49 let version_info = format!(
50 "\n#define {0}_MAJOR {1}\n#define {0}_MINOR {2}\n#define {0}_PATCH {3}\n",
51 name.to_uppercase().replace('-', "_"),
52 version.major,
53 version.minor,
54 version.patch
55 );
56 config.autogen_warning = Some(warning + &version_info);
57 }
58 cbindgen::Builder::new()
59 .with_crate(crate_path)
60 .with_config(config)
61 .generate()
62 .unwrap()
63 .write_to_file(include_path);
64
65 Ok(())
66}
67
68fn copy_prebuilt_include_file(
70 ws: &Workspace,
71 build_targets: &BuildTargets,
72 root_output: &Path,
73) -> anyhow::Result<()> {
74 let mut shell = ws.gctx().shell();
75 shell.status("Populating", "uninstalled header directory")?;
76 for (from, to) in build_targets.extra.include.iter() {
77 let to = root_output.join("include").join(to);
78 create_dir_all(to.parent().unwrap())?;
79 copy(from, to)?;
80 }
81
82 Ok(())
83}
84
85fn build_pc_file(name: &str, root_output: &Path, pc: &PkgConfig) -> anyhow::Result<()> {
86 let pc_path = root_output.join(format!("{name}.pc"));
87 let buf = pc.render();
88
89 write(pc_path, buf)
90}
91
92fn build_pc_files(
93 ws: &Workspace,
94 filename: &str,
95 root_output: &Path,
96 pc: &PkgConfig,
97) -> anyhow::Result<()> {
98 ws.gctx().shell().status("Building", "pkg-config files")?;
99 build_pc_file(filename, root_output, pc)?;
100 let pc_uninstalled = pc.uninstalled(root_output);
101 build_pc_file(
102 &format!("{filename}-uninstalled"),
103 root_output,
104 &pc_uninstalled,
105 )
106}
107
108fn patch_target(
109 pkg: &mut Package,
110 library_types: LibraryTypes,
111 capi_config: &CApiConfig,
112) -> anyhow::Result<()> {
113 use cargo::core::compiler::CrateType;
114
115 let manifest = pkg.manifest_mut();
116 let targets = manifest.targets_mut();
117
118 let mut kinds = Vec::with_capacity(3);
119
120 if library_types.rlib {
121 kinds.push(CrateType::Rlib);
122 }
123
124 if library_types.staticlib {
125 kinds.push(CrateType::Staticlib);
126 }
127
128 if library_types.cdylib {
129 kinds.push(CrateType::Cdylib);
130 }
131
132 for target in targets.iter_mut().filter(|t| t.is_lib()) {
133 target.set_kind(TargetKind::Lib(kinds.to_vec()));
134 target.set_name(&capi_config.library.name);
135 }
136
137 Ok(())
138}
139
140fn build_def_file(
142 ws: &Workspace,
143 name: &str,
144 target: &target::Target,
145 targetdir: &Path,
146) -> anyhow::Result<()> {
147 if target.os == "windows" && target.env == "msvc" {
148 ws.gctx().shell().status("Building", ".def file")?;
149
150 let dll_path = targetdir.join(format!("{}.dll", name.replace('-', "_")));
152 let dll_content = std::fs::read(&dll_path)?;
153 let dll_file = object::File::parse(&*dll_content)?;
154
155 let def_file = cargo_util::paths::create(targetdir.join(format!("{name}.def")))?;
157
158 write_def_file(dll_file, def_file)?;
159 }
160
161 Ok(())
162}
163
164fn write_def_file<W: std::io::Write>(dll_file: object::File, mut def_file: W) -> anyhow::Result<W> {
165 use object::read::Object;
166
167 writeln!(def_file, "EXPORTS")?;
168
169 for export in dll_file.exports()? {
170 def_file.write_all(export.name())?;
171 def_file.write_all(b"\n")?;
172 }
173
174 Ok(def_file)
175}
176
177fn build_implib_file(
179 ws: &Workspace,
180 build_targets: &BuildTargets,
181 name: &str,
182 target: &target::Target,
183 targetdir: &Path,
184) -> anyhow::Result<()> {
185 if target.os == "windows" {
186 ws.gctx().shell().status("Building", "implib")?;
187
188 let def_path = targetdir.join(format!("{name}.def"));
189 let def_contents = cargo_util::paths::read(&def_path)?;
190
191 let flavor = match target.env.as_str() {
192 "msvc" => Flavor::Msvc,
193 _ => Flavor::Gnu,
194 };
195
196 let machine_type = match target.arch.as_str() {
197 "x86_64" => MachineType::AMD64,
198 "x86" => MachineType::I386,
199 "aarch64" => MachineType::ARM64,
200 _ => {
201 return Err(anyhow::anyhow!(
202 "Windows support for {} is not implemented yet.",
203 target.arch
204 ))
205 }
206 };
207
208 let lib_name = build_targets
209 .shared_output_file_name()
210 .unwrap()
211 .into_string()
212 .unwrap();
213 let implib_path = build_targets.impl_lib.as_ref().unwrap();
214
215 let implib_file = cargo_util::paths::create(implib_path)?;
216 write_implib(implib_file, lib_name, machine_type, flavor, &def_contents)?;
217 }
218
219 Ok(())
220}
221
222fn write_implib<W: std::io::Write + std::io::Seek>(
223 mut w: W,
224 lib_name: String,
225 machine_type: MachineType,
226 flavor: Flavor,
227 def_contents: &str,
228) -> anyhow::Result<W> {
229 let mut module_def = ModuleDef::parse(def_contents, machine_type)?;
230 module_def.import_name = lib_name;
231
232 let import_library = ImportLibrary::from_def(module_def, machine_type, flavor);
233
234 import_library.write_to(&mut w)?;
235
236 Ok(w)
237}
238
239#[derive(Debug)]
240struct FingerPrint {
241 id: PackageId,
242 root_output: PathBuf,
243 build_targets: BuildTargets,
244 install_paths: InstallPaths,
245 static_libs: String,
246 hasher: DefaultHasher,
247}
248
249#[derive(serde::Serialize, serde::Deserialize)]
250struct Cache {
251 hash: String,
252 static_libs: String,
253}
254
255impl FingerPrint {
256 fn new(
257 id: &PackageId,
258 root_output: &Path,
259 build_targets: &BuildTargets,
260 install_paths: &InstallPaths,
261 capi_config: &CApiConfig,
262 ) -> Self {
263 let mut hasher = DefaultHasher::new();
264
265 capi_config.hash(&mut hasher);
266
267 Self {
268 id: id.to_owned(),
269 root_output: root_output.to_owned(),
270 build_targets: build_targets.clone(),
271 install_paths: install_paths.clone(),
272 static_libs: String::new(),
273 hasher,
274 }
275 }
276
277 fn hash(&self) -> anyhow::Result<Option<String>> {
278 let mut hasher = self.hasher.clone();
279 self.install_paths.hash(&mut hasher);
280
281 let mut paths: Vec<&PathBuf> = Vec::new();
282 if let Some(include) = &self.build_targets.include {
283 paths.push(include);
284 }
285 paths.extend(&self.build_targets.static_lib);
286 paths.extend(&self.build_targets.shared_lib);
287
288 for path in paths.iter() {
289 if let Ok(buf) = read_bytes(path) {
290 hasher.write(&buf);
291 } else {
292 return Ok(None);
293 };
294 }
295
296 let hash = hasher.finish();
297 Ok(Some(hash.to_string()))
300 }
301
302 fn path(&self) -> PathBuf {
303 self.root_output
306 .join(format!("cargo-c-{}.cache", self.id.name()))
307 }
308
309 fn load_previous(&self) -> anyhow::Result<Cache> {
310 let mut f = open(self.path())?;
311 let mut cache_str = String::new();
312 f.read_to_string(&mut cache_str)?;
313 let cache = toml::from_str(&cache_str)?;
314
315 Ok(cache)
316 }
317
318 fn is_valid(&self) -> bool {
319 match (self.load_previous(), self.hash()) {
320 (Ok(prev), Ok(Some(current))) => prev.hash == current,
321 _ => false,
322 }
323 }
324
325 fn store(&self) -> anyhow::Result<()> {
326 if let Some(hash) = self.hash()? {
327 let cache = Cache {
328 hash,
329 static_libs: self.static_libs.to_owned(),
330 };
331 let buf = toml::to_string(&cache)?;
332 write(self.path(), buf)?;
333 }
334
335 Ok(())
336 }
337}
338
339#[derive(Debug, Hash)]
340pub struct CApiConfig {
341 pub header: HeaderCApiConfig,
342 pub pkg_config: PkgConfigCApiConfig,
343 pub library: LibraryCApiConfig,
344 pub install: InstallCApiConfig,
345}
346
347#[derive(Debug, Hash)]
348pub struct HeaderCApiConfig {
349 pub name: String,
350 pub subdirectory: String,
351 pub generation: bool,
352 pub enabled: bool,
353 pub emit_version_constants: bool,
354}
355
356#[derive(Debug, Hash)]
357pub struct PkgConfigCApiConfig {
358 pub name: String,
359 pub filename: String,
360 pub description: String,
361 pub version: String,
362 pub requires: Option<String>,
363 pub requires_private: Option<String>,
364 pub strip_include_path_components: usize,
365}
366
367#[derive(Debug, Hash)]
368pub enum VersionSuffix {
369 Major,
370 MajorMinor,
371 MajorMinorPatch,
372}
373
374#[derive(Debug, Hash)]
375pub struct LibraryCApiConfig {
376 pub name: String,
377 pub version: Version,
378 pub install_subdir: Option<String>,
379 pub versioning: bool,
380 pub version_suffix_components: Option<VersionSuffix>,
381 pub import_library: bool,
382 pub rustflags: Vec<String>,
383}
384
385impl LibraryCApiConfig {
386 pub fn sover(&self) -> String {
387 let major = self.version.major;
388 let minor = self.version.minor;
389 let patch = self.version.patch;
390
391 match self.version_suffix_components {
392 None => match (major, minor, patch) {
393 (0, 0, patch) => format!("0.0.{patch}"),
394 (0, minor, _) => format!("0.{minor}"),
395 (major, _, _) => format!("{major}"),
396 },
397 Some(VersionSuffix::Major) => format!("{major}"),
398 Some(VersionSuffix::MajorMinor) => format!("{major}.{minor}"),
399 Some(VersionSuffix::MajorMinorPatch) => format!("{major}.{minor}.{patch}"),
400 }
401 }
402}
403
404#[derive(Debug, Default, Hash)]
405pub struct InstallCApiConfig {
406 pub include: Vec<InstallTarget>,
407 pub data: Vec<InstallTarget>,
408}
409
410#[derive(Debug, Hash)]
411pub enum InstallTarget {
412 Asset(InstallTargetPaths),
413 Generated(InstallTargetPaths),
414}
415
416#[derive(Clone, Debug, Hash)]
417pub struct InstallTargetPaths {
418 pub from: String,
423 pub to: String,
426}
427
428impl InstallTargetPaths {
429 pub fn from_value(value: &toml::value::Value, default_to: &str) -> anyhow::Result<Self> {
430 let from = value
431 .get("from")
432 .and_then(|v| v.as_str())
433 .ok_or_else(|| anyhow::anyhow!("a from field is required"))?;
434 let to = value
435 .get("to")
436 .and_then(|v| v.as_str())
437 .unwrap_or(default_to);
438
439 Ok(InstallTargetPaths {
440 from: from.to_string(),
441 to: to.to_string(),
442 })
443 }
444
445 pub fn install_paths(
446 &self,
447 root: &Path,
448 ) -> anyhow::Result<impl Iterator<Item = (PathBuf, PathBuf)>> {
449 let pattern = root.join(&self.from);
450 let base_pattern = if self.from.contains("/**") {
451 pattern
452 .iter()
453 .take_while(|&c| c != std::ffi::OsStr::new("**"))
454 .collect()
455 } else {
456 pattern.parent().unwrap().to_path_buf()
457 };
458 let pattern = pattern.to_str().unwrap();
459 let to = PathBuf::from(&self.to);
460 let g = glob::glob(pattern)?.filter_map(move |p| {
461 if let Ok(p) = p {
462 if p.is_file() {
463 let from = p;
464 let to = to.join(from.strip_prefix(&base_pattern).unwrap());
465 Some((from, to))
466 } else {
467 None
468 }
469 } else {
470 None
471 }
472 });
473
474 Ok(g)
475 }
476}
477
478fn load_manifest_capi_config(
479 pkg: &Package,
480 rustc_target: &target::Target,
481) -> anyhow::Result<CApiConfig> {
482 let name = &pkg
483 .manifest()
484 .targets()
485 .iter()
486 .find(|t| t.is_lib())
487 .unwrap()
488 .crate_name();
489 let root_path = pkg.root().to_path_buf();
490 let manifest_str = read(&root_path.join("Cargo.toml"))?;
491 let toml = manifest_str.parse::<toml::Table>()?;
492
493 let capi = toml
494 .get("package")
495 .and_then(|v| v.get("metadata"))
496 .and_then(|v| v.get("capi"));
497
498 if let Some(min_version) = capi
499 .as_ref()
500 .and_then(|capi| capi.get("min_version"))
501 .and_then(|v| v.as_str())
502 {
503 let min_version = Version::parse(min_version)?;
504 let version = Version::parse(env!("CARGO_PKG_VERSION"))?;
505 if min_version > version {
506 anyhow::bail!(
507 "Minimum required cargo-c version is {} but using cargo-c version {}",
508 min_version,
509 version
510 );
511 }
512 }
513
514 let header = capi.and_then(|v| v.get("header"));
515
516 let subdirectory = header
517 .as_ref()
518 .and_then(|h| h.get("subdirectory"))
519 .map(|v| {
520 if let Ok(b) = v.clone().try_into::<bool>() {
521 Ok(if b {
522 String::from(name)
523 } else {
524 String::from("")
525 })
526 } else {
527 v.clone().try_into::<String>()
528 }
529 })
530 .unwrap_or_else(|| Ok(String::from(name)))?;
531
532 let header = if let Some(capi) = capi {
533 HeaderCApiConfig {
534 name: header
535 .as_ref()
536 .and_then(|h| h.get("name"))
537 .or_else(|| capi.get("header_name"))
538 .map(|v| v.clone().try_into())
539 .unwrap_or_else(|| Ok(String::from(name)))?,
540 subdirectory,
541 generation: header
542 .as_ref()
543 .and_then(|h| h.get("generation"))
544 .map(|v| v.clone().try_into())
545 .unwrap_or(Ok(true))?,
546 enabled: header
547 .as_ref()
548 .and_then(|h| h.get("enabled"))
549 .map(|v| v.clone().try_into())
550 .unwrap_or(Ok(true))?,
551 emit_version_constants: header
552 .as_ref()
553 .and_then(|h| h.get("emit_version_constants"))
554 .map(|v| v.clone().try_into())
555 .unwrap_or(Ok(true))?,
556 }
557 } else {
558 HeaderCApiConfig {
559 name: String::from(name),
560 subdirectory: String::from(name),
561 generation: true,
562 enabled: true,
563 emit_version_constants: true,
564 }
565 };
566
567 let pc = capi.and_then(|v| v.get("pkg_config"));
568 let mut pc_name = String::from(name);
569 let mut pc_filename = String::from(name);
570 let mut description = String::from(
571 pkg.manifest()
572 .metadata()
573 .description
574 .as_deref()
575 .unwrap_or(""),
576 );
577 let mut version = pkg.version().to_string();
578 let mut requires = None;
579 let mut requires_private = None;
580 let mut strip_include_path_components = 0;
581
582 if let Some(pc) = pc {
583 if let Some(override_name) = pc.get("name").and_then(|v| v.as_str()) {
584 pc_name = String::from(override_name);
585 }
586 if let Some(override_filename) = pc.get("filename").and_then(|v| v.as_str()) {
587 pc_filename = String::from(override_filename);
588 }
589 if let Some(override_description) = pc.get("description").and_then(|v| v.as_str()) {
590 description = String::from(override_description);
591 }
592 if let Some(override_version) = pc.get("version").and_then(|v| v.as_str()) {
593 version = String::from(override_version);
594 }
595 if let Some(req) = pc.get("requires").and_then(|v| v.as_str()) {
596 requires = Some(String::from(req));
597 }
598 if let Some(req) = pc.get("requires_private").and_then(|v| v.as_str()) {
599 requires_private = Some(String::from(req));
600 }
601 strip_include_path_components = pc
602 .get("strip_include_path_components")
603 .map(|v| v.clone().try_into())
604 .unwrap_or_else(|| Ok(0))?
605 }
606
607 let pkg_config = PkgConfigCApiConfig {
608 name: pc_name,
609 filename: pc_filename,
610 description,
611 version,
612 requires,
613 requires_private,
614 strip_include_path_components,
615 };
616
617 let library = capi.and_then(|v| v.get("library"));
618 let mut lib_name = String::from(name);
619 let mut version = pkg.version().clone();
620 let mut install_subdir = None;
621 let mut versioning = true;
622 let mut version_suffix_components = None;
623 let mut import_library = true;
624 let mut rustflags = Vec::new();
625
626 if let Some(library) = library {
627 if let Some(override_name) = library.get("name").and_then(|v| v.as_str()) {
628 lib_name = String::from(override_name);
629 }
630 if let Some(override_version) = library.get("version").and_then(|v| v.as_str()) {
631 version = Version::parse(override_version)?;
632 }
633 if let Some(subdir) = library.get("install_subdir").and_then(|v| v.as_str()) {
634 install_subdir = Some(String::from(subdir));
635 }
636 versioning = library
637 .get("versioning")
638 .and_then(|v| v.as_bool())
639 .unwrap_or(true);
640
641 if let Some(value) = library.get("version_suffix_components") {
642 let value = value.as_integer().with_context(|| {
643 format!("Value for `version_suffix_components` is not an integer: {value:?}")
644 })?;
645 version_suffix_components = Some(match value {
646 1 => VersionSuffix::Major,
647 2 => VersionSuffix::MajorMinor,
648 3 => VersionSuffix::MajorMinorPatch,
649 _ => anyhow::bail!("Out of range value for version suffix components: {value}"),
650 });
651 }
652
653 fn make_args(args: &str) -> impl Iterator<Item = String> + use<'_> {
654 args.split(' ')
655 .map(str::trim)
656 .filter(|s| !s.is_empty())
657 .map(str::to_string)
658 }
659
660 import_library = library
661 .get("import_library")
662 .and_then(|v| v.as_bool())
663 .unwrap_or(true);
664
665 if let Some(args) = library.get("rustflags").and_then(|v| v.as_str()) {
666 rustflags.extend(make_args(args));
667 }
668
669 if let Some(args) = library.get("target").and_then(|v| v.as_table()) {
670 let args = args
671 .iter()
672 .filter_map(|(p, v)| {
673 if Platform::from_str(p)
674 .ok()
675 .is_some_and(|p| p.matches(name, &rustc_target.cfg))
676 {
677 v.get("rustflags").and_then(|v| v.as_str())
678 } else {
679 None
680 }
681 })
682 .flat_map(make_args);
683
684 rustflags.extend(args)
685 }
686 }
687
688 if rustc_target.os == "android" {
689 versioning = false;
690 }
691
692 let library = LibraryCApiConfig {
693 name: lib_name,
694 version,
695 install_subdir,
696 versioning,
697 version_suffix_components,
698 import_library,
699 rustflags,
700 };
701
702 let default_assets_include = InstallTargetPaths {
703 from: "assets/capi/include/**/*".to_string(),
704 to: header.subdirectory.clone(),
705 };
706
707 let header_name = if header.name.ends_with(".h") {
708 format!("assets/{}", header.name)
709 } else {
710 format!("assets/{}.h", header.name)
711 };
712
713 let default_legacy_asset_include = InstallTargetPaths {
714 from: header_name,
715 to: header.subdirectory.clone(),
716 };
717
718 let default_generated_include = InstallTargetPaths {
719 from: "capi/include/**/*".to_string(),
720 to: header.subdirectory.clone(),
721 };
722
723 let mut include_targets = vec![
724 InstallTarget::Asset(default_assets_include),
725 InstallTarget::Asset(default_legacy_asset_include),
726 InstallTarget::Generated(default_generated_include),
727 ];
728 let mut data_targets = Vec::new();
729
730 let mut data_subdirectory = name.clone();
731
732 fn custom_install_target_paths(
733 root: &toml::Value,
734 subdirectory: &str,
735 targets: &mut Vec<InstallTarget>,
736 ) -> anyhow::Result<()> {
737 if let Some(assets) = root.get("asset").and_then(|v| v.as_array()) {
738 for asset in assets {
739 let target_paths = InstallTargetPaths::from_value(asset, subdirectory)?;
740 targets.push(InstallTarget::Asset(target_paths));
741 }
742 }
743
744 if let Some(generated) = root.get("generated").and_then(|v| v.as_array()) {
745 for gen in generated {
746 let target_paths = InstallTargetPaths::from_value(gen, subdirectory)?;
747 targets.push(InstallTarget::Generated(target_paths));
748 }
749 }
750
751 Ok(())
752 }
753
754 let install = capi.and_then(|v| v.get("install"));
755 if let Some(install) = install {
756 if let Some(includes) = install.get("include") {
757 custom_install_target_paths(includes, &header.subdirectory, &mut include_targets)?;
758 }
759 if let Some(data) = install.get("data") {
760 if let Some(subdir) = data.get("subdirectory").and_then(|v| v.as_str()) {
761 data_subdirectory = String::from(subdir);
762 }
763 custom_install_target_paths(data, &data_subdirectory, &mut data_targets)?;
764 }
765 }
766
767 let default_assets_data = InstallTargetPaths {
768 from: "assets/capi/share/**/*".to_string(),
769 to: data_subdirectory.clone(),
770 };
771
772 let default_generated_data = InstallTargetPaths {
773 from: "capi/share/**/*".to_string(),
774 to: data_subdirectory,
775 };
776
777 data_targets.extend([
778 InstallTarget::Asset(default_assets_data),
779 InstallTarget::Generated(default_generated_data),
780 ]);
781
782 let install = InstallCApiConfig {
783 include: include_targets,
784 data: data_targets,
785 };
786
787 Ok(CApiConfig {
788 header,
789 pkg_config,
790 library,
791 install,
792 })
793}
794
795fn compile_options(
796 ws: &Workspace,
797 gctx: &GlobalContext,
798 args: &ArgMatches,
799 profile: InternedString,
800 compile_intent: UserIntent,
801) -> anyhow::Result<CompileOptions> {
802 use cargo::core::compiler::CompileKind;
803 let mut compile_opts =
804 args.compile_options(gctx, compile_intent, Some(ws), ProfileChecking::Custom)?;
805
806 compile_opts.build_config.requested_profile = profile;
807
808 std::rc::Rc::get_mut(&mut compile_opts.cli_features.features)
809 .unwrap()
810 .insert(FeatureValue::new("capi".into()));
811
812 compile_opts.filter = CompileFilter::new(
813 LibRule::True,
814 FilterRule::none(),
815 FilterRule::none(),
816 FilterRule::none(),
817 FilterRule::none(),
818 );
819
820 compile_opts.build_config.unit_graph = false;
821
822 let rustc = gctx.load_global_rustc(Some(ws))?;
823
824 if compile_opts.build_config.requested_kinds[0].is_host() {
826 compile_opts.build_config.requested_kinds =
827 CompileKind::from_requested_targets(gctx, &[rustc.host.to_string()])?
828 }
829
830 Ok(compile_opts)
831}
832
833#[derive(Default)]
834struct Exec {
835 ran: AtomicBool,
836 link_line: Mutex<HashMap<PackageId, String>>,
837}
838
839use cargo::CargoResult;
840use cargo_util::ProcessBuilder;
841
842impl Executor for Exec {
843 fn exec(
844 &self,
845 cmd: &ProcessBuilder,
846 id: PackageId,
847 _target: &Target,
848 _mode: CompileMode,
849 on_stdout_line: &mut dyn FnMut(&str) -> CargoResult<()>,
850 on_stderr_line: &mut dyn FnMut(&str) -> CargoResult<()>,
851 ) -> CargoResult<()> {
852 self.ran.store(true, Ordering::Relaxed);
853 cmd.exec_with_streaming(
854 on_stdout_line,
855 &mut |s| {
856 #[derive(serde::Deserialize, Debug)]
857 struct Message {
858 message: String,
859 level: String,
860 }
861
862 if let Ok(msg) = serde_json::from_str::<Message>(s) {
863 if msg.level == "note" {
865 if msg.message.starts_with("Link against the following native artifacts when linking against this static library") {
866 Ok(())
867 } else if let Some(link_line) = msg.message.strip_prefix("native-static-libs:") {
868 self.link_line.lock().unwrap().insert(id, link_line.to_string());
869 Ok(())
870 } else {
871 on_stderr_line(s)
872 }
873 } else {
874 on_stderr_line(s)
875 }
876 } else {
877 on_stderr_line(s)
878 }
879 },
880 false,
881 )
882 .map(drop)
883 }
884}
885
886use cargo::core::compiler::{unit_graph, CompileMode, UnitInterner};
887use cargo::ops::create_bcx;
888
889fn set_deps_args(
890 dep: &UnitDep,
891 graph: &UnitGraph,
892 extra_compiler_args: &mut HashMap<Unit, Vec<String>>,
893 global_args: &[String],
894) {
895 if !dep.unit_for.is_for_host() {
896 for dep in graph[&dep.unit].iter() {
897 set_deps_args(dep, graph, extra_compiler_args, global_args);
898 }
899 extra_compiler_args
900 .entry(dep.unit.clone())
901 .or_insert_with(|| global_args.to_owned());
902 }
903}
904
905fn compile_with_exec(
906 ws: &Workspace<'_>,
907 options: &CompileOptions,
908 exec: &Arc<dyn Executor>,
909 rustc_target: &target::Target,
910 root_output: &Path,
911 args: &ArgMatches,
912) -> CargoResult<HashMap<PackageId, PathBuf>> {
913 ws.emit_warnings()?;
914 let interner = UnitInterner::new();
915 let mut bcx = create_bcx(ws, options, &interner)?;
916 let unit_graph = &bcx.unit_graph;
917 let extra_compiler_args = &mut bcx.extra_compiler_args;
918
919 for unit in bcx.roots.iter() {
920 let pkg = &unit.pkg;
921 let capi_config = load_manifest_capi_config(pkg, rustc_target)?;
922 let name = &capi_config.library.name;
923 let install_paths = InstallPaths::new(name, rustc_target, args, &capi_config);
924 let pkg_rustflags = &capi_config.library.rustflags;
925
926 let mut leaf_args: Vec<String> = rustc_target
927 .shared_object_link_args(&capi_config, &install_paths.libdir, root_output)
928 .into_iter()
929 .flat_map(|l| ["-C".to_string(), format!("link-arg={l}")])
930 .collect();
931
932 leaf_args.extend(pkg_rustflags.clone());
933
934 leaf_args.push("--cfg".into());
935 leaf_args.push("cargo_c".into());
936
937 leaf_args.push("--print".into());
938 leaf_args.push("native-static-libs".into());
939
940 if args.flag("crt-static") {
941 leaf_args.push("-C".into());
942 leaf_args.push("target-feature=+crt-static".into());
943 }
944
945 extra_compiler_args.insert(unit.clone(), leaf_args.to_owned());
946
947 for dep in unit_graph[unit].iter() {
948 set_deps_args(dep, unit_graph, extra_compiler_args, pkg_rustflags);
949 }
950 }
951
952 if options.build_config.unit_graph {
953 unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.gctx())?;
954 return Ok(HashMap::new());
955 }
956 let cx = cargo::core::compiler::BuildRunner::new(&bcx)?;
957
958 let r = cx.compile(exec)?;
959
960 let out_dirs = r
961 .cdylibs
962 .iter()
963 .filter_map(|l| {
964 let id = l.unit.pkg.package_id();
965 l.script_metas.iter().flatten().find_map(|m| {
966 if let Some(env) = r.extra_env.get(m) {
967 env.iter().find_map(|e| {
968 if e.0 == "OUT_DIR" {
969 Some((id, PathBuf::from(&e.1)))
970 } else {
971 None
972 }
973 })
974 } else {
975 None
976 }
977 })
978 })
979 .collect();
980
981 Ok(out_dirs)
982}
983
984#[derive(Debug)]
985pub struct CPackage {
986 pub version: Version,
987 pub root_path: PathBuf,
988 pub capi_config: CApiConfig,
989 pub build_targets: BuildTargets,
990 pub install_paths: InstallPaths,
991 finger_print: FingerPrint,
992}
993
994impl CPackage {
995 fn from_package(
996 pkg: &mut Package,
997 args: &ArgMatches,
998 library_types: LibraryTypes,
999 rustc_target: &target::Target,
1000 root_output: &Path,
1001 ) -> anyhow::Result<CPackage> {
1002 let id = pkg.package_id();
1003 let version = pkg.version().clone();
1004 let root_path = pkg.root().to_path_buf();
1005 let capi_config = load_manifest_capi_config(pkg, rustc_target)?;
1006
1007 patch_target(pkg, library_types, &capi_config)?;
1008
1009 let name = &capi_config.library.name;
1010
1011 let install_paths = InstallPaths::new(name, rustc_target, args, &capi_config);
1012 let build_targets = BuildTargets::new(
1013 name,
1014 rustc_target,
1015 root_output,
1016 library_types,
1017 &capi_config,
1018 args.get_flag("meson"),
1019 )?;
1020
1021 let finger_print = FingerPrint::new(
1022 &id,
1023 root_output,
1024 &build_targets,
1025 &install_paths,
1026 &capi_config,
1027 );
1028
1029 Ok(CPackage {
1030 version,
1031 root_path,
1032 capi_config,
1033 build_targets,
1034 install_paths,
1035 finger_print,
1036 })
1037 }
1038}
1039
1040fn deprecation_warnings(ws: &Workspace, args: &ArgMatches) -> anyhow::Result<()> {
1041 if args.contains_id("dlltool") {
1042 ws.gctx()
1043 .shell()
1044 .warn("The `dlltool` support is now builtin. The cli option is deprecated and will be removed in the future")?;
1045 }
1046
1047 Ok(())
1048}
1049
1050#[derive(Debug, Clone, Copy)]
1052pub struct LibraryTypes {
1053 pub staticlib: bool,
1054 pub cdylib: bool,
1055 pub rlib: bool,
1056}
1057
1058impl LibraryTypes {
1059 fn from_target(target: &target::Target) -> Self {
1060 Self {
1069 staticlib: true,
1070 cdylib: target.os != "none",
1071 rlib: false,
1072 }
1073 }
1074
1075 fn from_args(target: &target::Target, args: &ArgMatches, want_rlib: bool) -> Self {
1076 Self {
1077 rlib: want_rlib,
1078 ..match args.get_many::<String>("library-type") {
1079 Some(library_types) => Self::from_library_types(target, library_types),
1080 None => Self::from_target(target),
1081 }
1082 }
1083 }
1084
1085 fn from_library_types<S: AsRef<str>>(
1086 target: &target::Target,
1087 library_types: impl Iterator<Item = S>,
1088 ) -> Self {
1089 let (mut staticlib, mut cdylib) = (false, false);
1090
1091 for library_type in library_types {
1092 staticlib |= library_type.as_ref() == "staticlib";
1093 cdylib |= library_type.as_ref() == "cdylib";
1094 }
1095
1096 cdylib &= target.os != "none";
1099
1100 Self {
1101 staticlib,
1102 cdylib,
1103 rlib: false,
1104 }
1105 }
1106
1107 const fn only_staticlib(self) -> bool {
1108 self.staticlib && !self.cdylib
1109 }
1110
1111 const fn only_cdylib(self) -> bool {
1112 self.cdylib && !self.staticlib
1113 }
1114}
1115
1116fn static_libraries(link_line: &str, rustc_target: &target::Target) -> String {
1117 link_line
1118 .trim()
1119 .split(' ')
1120 .filter(|s| {
1121 if rustc_target.env == "msvc" && s.starts_with("/defaultlib") {
1122 return false;
1123 }
1124 !s.is_empty()
1125 })
1126 .map(|lib| {
1127 if rustc_target.env == "msvc" && lib.ends_with(".lib") {
1128 return format!("-l{}", lib.trim_end_matches(".lib"));
1129 }
1130 lib.trim().to_string()
1131 })
1132 .filter(|s| !s.is_empty())
1133 .collect::<Vec<_>>()
1134 .join(" ")
1135}
1136
1137pub fn cbuild(
1138 ws: &mut Workspace,
1139 config: &GlobalContext,
1140 args: &ArgMatches,
1141 default_profile: &str,
1142 tests: bool,
1143) -> anyhow::Result<(Vec<CPackage>, CompileOptions)> {
1144 deprecation_warnings(ws, args)?;
1145
1146 let (target, is_target_overridden) = match args.targets()?.as_slice() {
1147 [] => (config.load_global_rustc(Some(ws))?.host.to_string(), false),
1148 [target] => (target.to_string(), true),
1149 [..] => anyhow::bail!("Multiple targets not supported yet"),
1150 };
1151
1152 let rustc_target = target::Target::new(Some(&target), is_target_overridden)?;
1153
1154 let library_types = LibraryTypes::from_args(&rustc_target, args, tests);
1155
1156 let profile = args.get_profile_name(default_profile, ProfileChecking::Custom)?;
1157
1158 let profiles = Profiles::new(ws, profile)?;
1159
1160 let mut compile_opts = compile_options(ws, config, args, profile, UserIntent::Build)?;
1161
1162 let root_output = ws
1164 .target_dir()
1165 .as_path_unlocked()
1166 .to_path_buf()
1167 .join(PathBuf::from(target))
1168 .join(profiles.get_dir_name());
1169
1170 let mut members = Vec::new();
1171 let mut pristine = false;
1172
1173 let requested: Vec<_> = compile_opts
1174 .spec
1175 .get_packages(ws)?
1176 .iter()
1177 .map(|p| p.package_id())
1178 .collect();
1179
1180 let capi_feature = InternedString::new("capi");
1181 let is_relevant_package = |package: &Package| {
1182 package.library().is_some()
1183 && package.summary().features().contains_key(&capi_feature)
1184 && requested.contains(&package.package_id())
1185 };
1186
1187 for m in ws.members_mut().filter(|p| is_relevant_package(p)) {
1188 let cpkg = CPackage::from_package(m, args, library_types, &rustc_target, &root_output)?;
1189
1190 pristine |= cpkg.finger_print.load_previous().is_err() || !cpkg.finger_print.is_valid();
1191
1192 members.push(cpkg);
1193 }
1194
1195 compile_opts.build_config.force_rebuild |= pristine;
1197
1198 let exec = Arc::new(Exec::default());
1199 let out_dirs = compile_with_exec(
1200 ws,
1201 &compile_opts,
1202 &(exec.clone() as Arc<dyn Executor>),
1203 &rustc_target,
1204 &root_output,
1205 args,
1206 )?;
1207
1208 for cpkg in members.iter_mut() {
1209 let out_dir = out_dirs.get(&cpkg.finger_print.id).map(|p| p.as_path());
1210
1211 cpkg.build_targets
1212 .extra
1213 .setup(&cpkg.capi_config, &cpkg.root_path, out_dir)?;
1214
1215 if cpkg.capi_config.header.generation {
1216 let mut header_name = PathBuf::from(&cpkg.capi_config.header.name);
1217 header_name.set_extension("h");
1218 let from = root_output.join(&header_name);
1219 let to = Path::new(&cpkg.capi_config.header.subdirectory).join(&header_name);
1220 cpkg.build_targets.extra.include.push((from, to));
1221 }
1222 }
1223
1224 if pristine {
1225 compile_opts.build_config.force_rebuild = false;
1227 }
1228
1229 let new_build = exec.ran.load(Ordering::Relaxed);
1230
1231 for cpkg in members.iter_mut() {
1232 if new_build {
1234 let name = &cpkg.capi_config.library.name;
1235 let (pkg_config_static_libs, static_libs) = if library_types.only_cdylib() {
1236 (String::new(), String::new())
1237 } else if let Some(libs) = exec.link_line.lock().unwrap().get(&cpkg.finger_print.id) {
1238 (static_libraries(libs, &rustc_target), libs.to_string())
1239 } else {
1240 (String::new(), String::new())
1241 };
1242 let capi_config = &cpkg.capi_config;
1243 let build_targets = &cpkg.build_targets;
1244
1245 let mut pc = PkgConfig::from_workspace(name, &cpkg.install_paths, args, capi_config);
1246 if library_types.only_staticlib() {
1247 pc.add_lib(&pkg_config_static_libs);
1248 }
1249 pc.add_lib_private(&pkg_config_static_libs);
1250
1251 build_pc_files(ws, &capi_config.pkg_config.filename, &root_output, &pc)?;
1252
1253 if !library_types.only_staticlib() && capi_config.library.import_library {
1254 let lib_name = name;
1255 build_def_file(ws, lib_name, &rustc_target, &root_output)?;
1256 build_implib_file(ws, build_targets, lib_name, &rustc_target, &root_output)?;
1257 }
1258
1259 if capi_config.header.enabled {
1260 let header_name = &capi_config.header.name;
1261 let emit_version_constants = capi_config.header.emit_version_constants;
1262 if capi_config.header.generation {
1263 build_include_file(
1264 ws,
1265 header_name,
1266 emit_version_constants.then_some(&cpkg.version),
1267 &root_output,
1268 &cpkg.root_path,
1269 )?;
1270 }
1271
1272 copy_prebuilt_include_file(ws, build_targets, &root_output)?;
1273 }
1274
1275 if name.contains('-') {
1276 let from_build_targets = BuildTargets::new(
1277 &name.replace('-', "_"),
1278 &rustc_target,
1279 &root_output,
1280 library_types,
1281 capi_config,
1282 args.get_flag("meson"),
1283 )?;
1284
1285 if let (Some(from_static_lib), Some(to_static_lib)) = (
1286 from_build_targets.static_lib.as_ref(),
1287 build_targets.static_lib.as_ref(),
1288 ) {
1289 copy(from_static_lib, to_static_lib)?;
1290 }
1291 if let (Some(from_shared_lib), Some(to_shared_lib)) = (
1292 from_build_targets.shared_lib.as_ref(),
1293 build_targets.shared_lib.as_ref(),
1294 ) {
1295 copy(from_shared_lib, to_shared_lib)?;
1296 }
1297 if let (Some(from_debug_info), Some(to_debug_info)) = (
1298 from_build_targets.debug_info.as_ref(),
1299 build_targets.debug_info.as_ref(),
1300 ) {
1301 if from_debug_info.exists() {
1302 create_dir_all(to_debug_info.parent().unwrap())?;
1303 if from_debug_info.is_dir() {
1304 let files =
1305 from_debug_info.read_dir()?.collect::<Result<Vec<_>, _>>()?;
1306 for f in files.iter() {
1307 let src = f.path();
1308 let file_name = src.strip_prefix(from_debug_info)?;
1309 let dst = to_debug_info.join(file_name);
1310 match std::fs::create_dir_all(
1311 dst.parent().expect("Source path is not complete"),
1312 ) {
1313 Ok(()) => Ok(()),
1314 Err(v) => {
1315 if v.kind() == ErrorKind::AlreadyExists {
1316 Ok(())
1317 } else {
1318 Err(v)
1319 }
1320 }
1321 }?;
1322 copy(src, dst)?;
1323 }
1324 } else {
1325 copy(from_debug_info, to_debug_info)?;
1326 }
1327 }
1328 }
1329 }
1330
1331 cpkg.finger_print.static_libs = static_libs;
1334 cpkg.finger_print.store()?;
1335 } else {
1336 cpkg.finger_print.static_libs = cpkg.finger_print.load_previous()?.static_libs;
1338 }
1339
1340 ws.gctx().shell().verbose(|s| {
1341 let path = &format!("PKG_CONFIG_PATH=\"{}\"", root_output.display());
1342 s.note(path)
1343 })?;
1344 }
1345
1346 Ok((members, compile_opts))
1347}
1348
1349pub fn ctest(
1350 ws: &Workspace,
1351 args: &ArgMatches,
1352 packages: &[CPackage],
1353 mut compile_opts: CompileOptions,
1354) -> CliResult {
1355 compile_opts.build_config.requested_profile =
1356 args.get_profile_name("test", ProfileChecking::Custom)?;
1357 compile_opts.build_config.intent = UserIntent::Test;
1358
1359 compile_opts.filter = ops::CompileFilter::new(
1360 LibRule::Default, FilterRule::none(), FilterRule::All, FilterRule::none(), FilterRule::none(), );
1366
1367 compile_opts.target_rustc_args = None;
1368
1369 let ops = ops::TestOptions {
1370 no_run: args.flag("no-run"),
1371 no_fail_fast: args.flag("no-fail-fast"),
1372 compile_opts,
1373 };
1374
1375 let test_args = args.get_one::<String>("TESTNAME").into_iter();
1376 let test_args = test_args.chain(args.get_many::<String>("args").unwrap_or_default());
1377 let test_args = test_args.map(String::as_str).collect::<Vec<_>>();
1378
1379 use std::ffi::OsString;
1380
1381 let mut cflags = OsString::new();
1382
1383 for pkg in packages {
1384 let static_lib_path = pkg.build_targets.static_lib.as_ref().unwrap();
1385 let builddir = static_lib_path.parent().unwrap();
1386
1387 cflags.push("-I");
1388 cflags.push(builddir);
1389 cflags.push(" ");
1390
1391 cflags.push(static_lib_path);
1393
1394 cflags.push(" ");
1396 cflags.push(&pkg.finger_print.static_libs);
1397 }
1398
1399 std::env::set_var("INLINE_C_RS_CFLAGS", cflags);
1400
1401 ops::run_tests(ws, &ops, &test_args)
1402}
1403
1404#[cfg(test)]
1405mod tests {
1406 use super::*;
1407 use semver::Version;
1408
1409 fn make_test_library_config(version: &str) -> LibraryCApiConfig {
1410 LibraryCApiConfig {
1411 name: "example".to_string(),
1412 version: Version::parse(version).unwrap(),
1413 install_subdir: None,
1414 versioning: true,
1415 version_suffix_components: None,
1416 import_library: true,
1417 rustflags: vec![],
1418 }
1419 }
1420
1421 #[test]
1422 pub fn test_semver_zero_zero_zero() {
1423 let library = make_test_library_config("0.0.0");
1424 let sover = library.sover();
1425 assert_eq!(sover, "0.0.0");
1426 }
1427
1428 #[test]
1429 pub fn test_semver_zero_one_zero() {
1430 let library = make_test_library_config("0.1.0");
1431 let sover = library.sover();
1432 assert_eq!(sover, "0.1");
1433 }
1434
1435 #[test]
1436 pub fn test_semver_one_zero_zero() {
1437 let library = make_test_library_config("1.0.0");
1438 let sover = library.sover();
1439 assert_eq!(sover, "1");
1440 }
1441
1442 #[test]
1443 pub fn text_one_fixed_zero_zero_zero() {
1444 let mut library = make_test_library_config("0.0.0");
1445 library.version_suffix_components = Some(VersionSuffix::Major);
1446 let sover = library.sover();
1447 assert_eq!(sover, "0");
1448 }
1449
1450 #[test]
1451 pub fn text_two_fixed_one_zero_zero() {
1452 let mut library = make_test_library_config("1.0.0");
1453 library.version_suffix_components = Some(VersionSuffix::MajorMinor);
1454 let sover = library.sover();
1455 assert_eq!(sover, "1.0");
1456 }
1457
1458 #[test]
1459 pub fn text_three_fixed_one_zero_zero() {
1460 let mut library = make_test_library_config("1.0.0");
1461 library.version_suffix_components = Some(VersionSuffix::MajorMinorPatch);
1462 let sover = library.sover();
1463 assert_eq!(sover, "1.0.0");
1464 }
1465
1466 #[test]
1467 pub fn test_lib_listing() {
1468 let libs_osx = "-lSystem -lc -lm";
1469 let libs_linux = "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc";
1470 let libs_hurd = "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc";
1471 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";
1472 let libs_mingw = "-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32";
1473
1474 let target_osx = target::Target::new(Some("x86_64-apple-darwin"), false).unwrap();
1475 let target_linux = target::Target::new(Some("x86_64-unknown-linux-gnu"), false).unwrap();
1476 let target_hurd = target::Target::new(Some("x86_64-unknown-hurd-gnu"), false).unwrap();
1477 let target_msvc = target::Target::new(Some("x86_64-pc-windows-msvc"), false).unwrap();
1478 let target_mingw = target::Target::new(Some("x86_64-pc-windows-gnu"), false).unwrap();
1479
1480 assert_eq!(static_libraries(libs_osx, &target_osx), "-lSystem -lc -lm");
1481 assert_eq!(
1482 static_libraries(libs_linux, &target_linux),
1483 "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
1484 );
1485 assert_eq!(
1486 static_libraries(libs_hurd, &target_hurd),
1487 "-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
1488 );
1489 assert_eq!(
1490 static_libraries(libs_msvc, &target_msvc),
1491 "-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32 -lmsvcrt"
1492 );
1493 assert_eq!(
1494 static_libraries(libs_mingw, &target_mingw),
1495 "-lkernel32 -ladvapi32 -lkernel32 -lntdll -luserenv -lws2_32 -lkernel32 -lws2_32 -lkernel32"
1496 );
1497 }
1498}