cargo_c/
install.rs

1use clap::ArgMatches;
2use std::io::ErrorKind;
3use std::path::{Component, Path, PathBuf};
4
5use cargo::core::Workspace;
6use cargo_util::paths::{self, create_dir_all};
7
8use crate::build::*;
9use crate::build_targets::BuildTargets;
10use crate::target::Target;
11
12pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(ws: &Workspace, from: P, to: Q) -> anyhow::Result<u64> {
13    ws.gctx().shell().verbose(|shell| {
14        shell.status(
15            "Copying",
16            format!("{} to {}", from.as_ref().display(), to.as_ref().display()),
17        )
18    })?;
19
20    paths::copy(from, to)
21}
22
23fn append_to_destdir(destdir: Option<&Path>, path: &Path) -> PathBuf {
24    if let Some(destdir) = destdir {
25        let mut joined = destdir.to_path_buf();
26        for component in path.components() {
27            match component {
28                Component::Prefix(_) | Component::RootDir => {}
29                _ => joined.push(component),
30            };
31        }
32        joined
33    } else {
34        path.to_path_buf()
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use std::path::{Path, PathBuf};
41
42    #[test]
43    fn append_to_destdir() {
44        assert_eq!(
45            super::append_to_destdir(Some(Path::new(r"/foo")), &PathBuf::from(r"/bar/./..")),
46            PathBuf::from(r"/foo/bar/./..")
47        );
48
49        assert_eq!(
50            super::append_to_destdir(Some(Path::new(r"foo")), &PathBuf::from(r"bar")),
51            PathBuf::from(r"foo/bar")
52        );
53
54        assert_eq!(
55            super::append_to_destdir(Some(Path::new(r"")), &PathBuf::from(r"")),
56            PathBuf::from(r"")
57        );
58
59        if cfg!(windows) {
60            assert_eq!(
61                super::append_to_destdir(Some(Path::new(r"X:\foo")), &PathBuf::from(r"Y:\bar")),
62                PathBuf::from(r"X:\foo\bar")
63            );
64
65            assert_eq!(
66                super::append_to_destdir(Some(Path::new(r"A:\foo")), &PathBuf::from(r"B:bar")),
67                PathBuf::from(r"A:\foo\bar")
68            );
69
70            assert_eq!(
71                super::append_to_destdir(Some(Path::new(r"\foo")), &PathBuf::from(r"\bar")),
72                PathBuf::from(r"\foo\bar")
73            );
74
75            assert_eq!(
76                super::append_to_destdir(
77                    Some(Path::new(r"C:\dest")),
78                    Path::new(r"\\server\share\foo\bar")
79                ),
80                PathBuf::from(r"C:\\dest\\foo\\bar")
81            );
82        }
83    }
84}
85
86pub(crate) enum LibType {
87    So,
88    Dylib,
89    Windows,
90}
91
92impl LibType {
93    pub(crate) fn from_build_targets(build_targets: &BuildTargets) -> Self {
94        let target = &build_targets.target;
95        let os = &target.os;
96        let env = &target.env;
97
98        match (os.as_str(), env.as_str()) {
99            ("linux", _)
100            | ("freebsd", _)
101            | ("dragonfly", _)
102            | ("netbsd", _)
103            | ("android", _)
104            | ("haiku", _)
105            | ("illumos", _)
106            | ("openbsd", _)
107            | ("emscripten", _)
108            | ("hurd", _) => LibType::So,
109            ("macos", _) | ("ios", _) | ("tvos", _) | ("visionos", _) => LibType::Dylib,
110            ("windows", _) => LibType::Windows,
111            _ => unimplemented!("The target {}-{} is not supported yet", os, env),
112        }
113    }
114}
115
116pub(crate) struct UnixLibNames {
117    canonical: String,
118    with_main_ver: String,
119    with_full_ver: String,
120}
121
122impl UnixLibNames {
123    pub(crate) fn new(lib_type: LibType, library: &LibraryCApiConfig) -> Option<Self> {
124        let lib_name = &library.name;
125        let lib_version = &library.version;
126        let main_version = library.sover();
127
128        match lib_type {
129            LibType::So => {
130                let lib = format!("lib{lib_name}.so");
131                let lib_with_full_ver = format!(
132                    "{}.{}.{}.{}",
133                    lib, lib_version.major, lib_version.minor, lib_version.patch
134                );
135                let lib_with_main_ver = format!("{lib}.{main_version}");
136
137                Some(Self {
138                    canonical: lib,
139                    with_main_ver: lib_with_main_ver,
140                    with_full_ver: lib_with_full_ver,
141                })
142            }
143            LibType::Dylib => {
144                let lib = format!("lib{lib_name}.dylib");
145                let lib_with_main_ver = format!("lib{lib_name}.{main_version}.dylib");
146
147                let lib_with_full_ver = format!(
148                    "lib{}.{}.{}.{}.dylib",
149                    lib_name, lib_version.major, lib_version.minor, lib_version.patch
150                );
151                Some(Self {
152                    canonical: lib,
153                    with_main_ver: lib_with_main_ver,
154                    with_full_ver: lib_with_full_ver,
155                })
156            }
157            LibType::Windows => None,
158        }
159    }
160
161    fn links(&self, install_path_lib: &Path) {
162        if self.with_main_ver != self.with_full_ver {
163            let mut ln_sf = std::process::Command::new("ln");
164            ln_sf.arg("-sf");
165            ln_sf
166                .arg(&self.with_full_ver)
167                .arg(install_path_lib.join(&self.with_main_ver));
168            let _ = ln_sf.status().unwrap();
169        }
170
171        let mut ln_sf = std::process::Command::new("ln");
172        ln_sf.arg("-sf");
173        ln_sf
174            .arg(&self.with_full_ver)
175            .arg(install_path_lib.join(&self.canonical));
176        let _ = ln_sf.status().unwrap();
177    }
178
179    pub(crate) fn install(
180        &self,
181        ws: &Workspace,
182        capi_config: &CApiConfig,
183        shared_lib: &Path,
184        install_path_lib: &Path,
185    ) -> anyhow::Result<()> {
186        if capi_config.library.versioning {
187            copy(ws, shared_lib, install_path_lib.join(&self.with_full_ver))?;
188            self.links(install_path_lib);
189        } else {
190            copy(ws, shared_lib, install_path_lib.join(&self.canonical))?;
191        }
192        Ok(())
193    }
194}
195
196pub fn cinstall(ws: &Workspace, packages: &[CPackage]) -> anyhow::Result<()> {
197    for pkg in packages {
198        let paths = &pkg.install_paths;
199        let capi_config = &pkg.capi_config;
200        let build_targets = &pkg.build_targets;
201
202        let destdir = &paths.destdir;
203
204        let mut install_path_lib = paths.libdir.clone();
205        if let Some(subdir) = &capi_config.library.install_subdir {
206            install_path_lib.push(subdir);
207        }
208
209        let install_path_bin = append_to_destdir(destdir.as_deref(), &paths.bindir);
210        let install_path_lib = append_to_destdir(destdir.as_deref(), &install_path_lib);
211        let install_path_pc = append_to_destdir(destdir.as_deref(), &paths.pkgconfigdir);
212        let install_path_include = append_to_destdir(destdir.as_deref(), &paths.includedir);
213        let install_path_data = append_to_destdir(destdir.as_deref(), &paths.datadir);
214
215        create_dir_all(&install_path_lib)?;
216        create_dir_all(&install_path_pc)?;
217
218        ws.gctx().shell().status("Installing", "pkg-config file")?;
219
220        copy(
221            ws,
222            &build_targets.pc,
223            install_path_pc.join(build_targets.pc.file_name().unwrap()),
224        )?;
225
226        if capi_config.header.enabled {
227            ws.gctx().shell().status("Installing", "header file")?;
228            for (from, to) in build_targets.extra.include.iter() {
229                let to = install_path_include.join(to);
230                create_dir_all(to.parent().unwrap())?;
231                copy(ws, from, to)?;
232            }
233        }
234
235        if !build_targets.extra.data.is_empty() {
236            ws.gctx().shell().status("Installing", "data file")?;
237            for (from, to) in build_targets.extra.data.iter() {
238                let to = install_path_data.join(to);
239                create_dir_all(to.parent().unwrap())?;
240                copy(ws, from, to)?;
241            }
242        }
243
244        if let Some(ref static_lib) = build_targets.static_lib {
245            ws.gctx().shell().status("Installing", "static library")?;
246            let file_name = build_targets.static_output_file_name().unwrap();
247
248            copy(ws, static_lib, install_path_lib.join(file_name))?;
249        }
250
251        if let Some(ref shared_lib) = build_targets.shared_lib {
252            ws.gctx().shell().status("Installing", "shared library")?;
253
254            let lib_type = LibType::from_build_targets(build_targets);
255            match lib_type {
256                LibType::So | LibType::Dylib => {
257                    let lib = UnixLibNames::new(lib_type, &capi_config.library).unwrap();
258                    lib.install(ws, capi_config, shared_lib, &install_path_lib)?;
259                }
260                LibType::Windows => {
261                    let lib_name = build_targets.shared_output_file_name().unwrap();
262
263                    if capi_config.library.install_subdir.is_none() {
264                        let install_path_bin = append_to_destdir(destdir.as_deref(), &paths.bindir);
265                        create_dir_all(&install_path_bin)?;
266
267                        copy(ws, shared_lib, install_path_bin.join(lib_name))?;
268                    } else {
269                        // We assume they are plugins, install them in the custom libdir path
270                        copy(ws, shared_lib, install_path_lib.join(lib_name))?;
271                    }
272                    if capi_config.library.import_library {
273                        let impl_lib = build_targets.impl_lib.as_ref().unwrap();
274                        let impl_lib_name = impl_lib.file_name().unwrap();
275                        copy(ws, impl_lib, install_path_lib.join(impl_lib_name))?;
276                        let def = build_targets.def.as_ref().unwrap();
277                        let def_name = def.file_name().unwrap();
278                        copy(ws, def, install_path_lib.join(def_name))?;
279                    }
280                }
281            }
282        }
283
284        if let Some(ref debug_info) = build_targets.debug_info {
285            if debug_info.exists() {
286                ws.gctx()
287                    .shell()
288                    .status("Installing", "debugging information")?;
289
290                let destination_path = if capi_config.library.install_subdir.is_none() {
291                    build_targets
292                        .debug_info_file_name(&install_path_bin, &install_path_lib)
293                        .unwrap()
294                } else {
295                    // We assume they are plugins, install them in the custom libdir path
296                    build_targets
297                        .debug_info_file_name(&install_path_lib, &install_path_lib)
298                        .unwrap()
299                };
300
301                create_dir_all(destination_path.parent().unwrap())?;
302                if debug_info.is_dir() {
303                    let files = debug_info.read_dir()?.collect::<Result<Vec<_>, _>>()?;
304                    for f in files.iter() {
305                        let src = f.path();
306                        let file_name = src.strip_prefix(debug_info)?;
307                        let dst = destination_path.join(file_name);
308                        match std::fs::create_dir_all(
309                            dst.parent().expect("Source path is not complete"),
310                        ) {
311                            Ok(()) => Ok(()),
312                            Err(v) => {
313                                if v.kind() == ErrorKind::AlreadyExists {
314                                    Ok(())
315                                } else {
316                                    Err(v)
317                                }
318                            }
319                        }?;
320                        copy(ws, src, dst)?;
321                    }
322                } else {
323                    copy(ws, debug_info, destination_path)?;
324                }
325            } else {
326                ws.gctx()
327                    .shell()
328                    .verbose(|shell| shell.status("Absent", "debugging information"))?;
329            }
330        }
331    }
332
333    Ok(())
334}
335
336#[derive(Debug, Hash, Clone)]
337pub struct InstallPaths {
338    pub subdir_name: PathBuf,
339    pub destdir: Option<PathBuf>,
340    pub prefix: PathBuf,
341    pub libdir: PathBuf,
342    pub includedir: PathBuf,
343    pub datadir: PathBuf,
344    pub bindir: PathBuf,
345    pub pkgconfigdir: PathBuf,
346}
347
348fn get_path_or(args: &ArgMatches, id: &str, f: impl FnOnce() -> PathBuf) -> PathBuf {
349    if matches!(
350        args.value_source(id),
351        Some(clap::parser::ValueSource::DefaultValue)
352    ) {
353        f()
354    } else {
355        args.get_one::<PathBuf>(id).unwrap().to_owned()
356    }
357}
358
359impl InstallPaths {
360    pub fn new(
361        _name: &str,
362        rustc_target: &Target,
363        args: &ArgMatches,
364        capi_config: &CApiConfig,
365    ) -> Self {
366        let destdir = args.get_one::<PathBuf>("destdir").map(PathBuf::from);
367        let prefix = get_path_or(args, "prefix", || rustc_target.default_prefix());
368        let libdir = prefix.join(get_path_or(args, "libdir", || {
369            rustc_target.default_libdir()
370        }));
371        let includedir = prefix.join(get_path_or(args, "includedir", || {
372            rustc_target.default_includedir()
373        }));
374        let datarootdir = prefix.join(get_path_or(args, "datarootdir", || {
375            rustc_target.default_datadir()
376        }));
377        let datadir = args
378            .get_one::<PathBuf>("datadir")
379            .map(|d| prefix.join(d))
380            .unwrap_or_else(|| datarootdir.clone());
381
382        let subdir_name = PathBuf::from(&capi_config.header.subdirectory);
383
384        let bindir = prefix.join(args.get_one::<PathBuf>("bindir").unwrap());
385        let pkgconfigdir = args
386            .get_one::<PathBuf>("pkgconfigdir")
387            .map(|d| prefix.join(d))
388            .unwrap_or_else(|| libdir.join("pkgconfig"));
389
390        InstallPaths {
391            subdir_name,
392            destdir,
393            prefix,
394            libdir,
395            includedir,
396            datadir,
397            bindir,
398            pkgconfigdir,
399        }
400    }
401}