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 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 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}