cargo_c/
build.rs

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
29/// Build the C header
30fn 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    // TODO: map the errors
46    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
68/// Copy the pre-built C header from the asset directory to the root_dir
69fn 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
140/// Build def file for windows-msvc
141fn 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        // Parse the .dll as an object file
151        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        // Create the .def output file
156        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
177/// Build import library for windows
178fn 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        // the hash is stored in a toml file which does not support u64 so store
298        // it as a string to prevent overflows.
299        Ok(Some(hash.to_string()))
300    }
301
302    fn path(&self) -> PathBuf {
303        // Use the crate name in the cache file as the same target dir
304        // may be used to build various libs
305        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    /// pattern to feed to glob::glob()
419    ///
420    /// if the InstallTarget is Asset its root is the the root_path
421    /// if the InstallTarget is Generated its root is the root_output
422    pub from: String,
423    /// The path to be joined to the canonical directory to install the files discovered by the
424    /// glob, e.g. `{includedir}/{to}` for includes.
425    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    // Always set the target, requested_kinds is a vec of a single element.
825    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                    // suppress the native-static-libs messages
864                    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/// What library types to build
1051#[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        // for os == "none", cdylib does not make sense. By default cdylib is also not built on
1061        // musl, but that can be overriden by the user. That is useful when musl is being used as
1062        // main libc, e.g. in Alpine, Gentoo and OpenWRT
1063        //
1064        // See also
1065        //
1066        // - https://github.com/lu-zero/cargo-c?tab=readme-ov-file#shared-libraries-are-not-built-on-musl-systems
1067        // - https://github.com/lu-zero/cargo-c/issues/180
1068        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        // when os is none, a cdylib cannot be produced
1097        // forcing a cdylib for musl is allowed here (see [`LibraryTypes::from_target`])
1098        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    // TODO: there must be a simpler way to get the right path.
1163    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    // If the cache is somehow missing force a full rebuild;
1196    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        // restore the default to make sure the tests do not trigger a second rebuild.
1226        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        // it is a new build, build the additional files and update update the cache
1233        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            // This can be supplied to Rust, so it must be in
1332            // linker-native syntax
1333            cpkg.finger_print.static_libs = static_libs;
1334            cpkg.finger_print.store()?;
1335        } else {
1336            // It is not a new build, recover the static_libs value from the cache
1337            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,   // compile the library, so the unit tests can be run filtered
1361        FilterRule::none(), // we do not have binaries
1362        FilterRule::All,    // compile the tests, so the integration tests can be run filtered
1363        FilterRule::none(), // specify --examples to unit test binaries filtered
1364        FilterRule::none(), // specify --benches to unit test benchmarks filtered
1365    );
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        // We push the full path here to work around macos ld not supporting the -l:{filename} syntax
1392        cflags.push(static_lib_path);
1393
1394        // We push the static_libs as CFLAGS as well to avoid mangling the options on msvc
1395        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}