1#![allow(dead_code)]
2
3use crate::build::CApiConfig;
4use crate::install::InstallPaths;
5use std::path::{Component, Path, PathBuf};
6
7fn canonicalize<P: AsRef<Path>>(path: P) -> String {
8 let mut stack = Vec::with_capacity(16);
9
10 struct Item<'a> {
11 separator: bool,
12 component: Component<'a>,
13 }
14
15 let mut separator = false;
16
17 for component in path.as_ref().components() {
18 match component {
19 Component::RootDir => {
20 separator = true;
21 }
22 Component::Prefix(_) => stack.push(Item {
23 separator: false,
24 component,
25 }),
26 Component::ParentDir => {
27 let _ = stack.pop();
28 }
29 Component::CurDir => stack.push(Item {
30 separator: false,
31 component,
32 }),
33 Component::Normal(_) => {
34 stack.push(Item {
35 separator,
36 component,
37 });
38 separator = true;
39 }
40 }
41 }
42
43 if stack.is_empty() {
44 String::from("/")
45 } else {
46 let mut buf = String::with_capacity(64);
47
48 for item in stack {
49 if item.separator {
50 buf.push('/');
51 }
52
53 buf.push_str(&item.component.as_os_str().to_string_lossy());
54 }
55
56 buf
57 }
58}
59
60#[derive(Debug, Clone)]
61pub struct PkgConfig {
62 prefix: PathBuf,
63 exec_prefix: PathBuf,
64 includedir: PathBuf,
65 libdir: PathBuf,
66
67 name: String,
68 description: String,
69 version: String,
70
71 requires: Vec<String>,
72 requires_private: Vec<String>,
73
74 libs: Vec<String>,
75 libs_private: Vec<String>,
76
77 cflags: Vec<String>,
78
79 conflicts: Vec<String>,
80}
81
82impl PkgConfig {
83 pub fn new(_name: &str, capi_config: &CApiConfig) -> Self {
98 let requires = match &capi_config.pkg_config.requires {
99 Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
100 _ => Vec::new(),
101 };
102 let requires_private = match &capi_config.pkg_config.requires_private {
103 Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
104 _ => Vec::new(),
105 };
106
107 let mut libdir = PathBuf::new();
108 libdir.push("${libdir}");
109 if let Some(subdir) = &capi_config.library.install_subdir {
110 libdir.push(subdir);
111 }
112
113 let libs = vec![
114 format!("-L{}", canonicalize(libdir.display().to_string())),
115 format!("-l{}", capi_config.library.name),
116 ];
117
118 let cflags = if capi_config.header.enabled {
119 let includedir = Path::new("${includedir}").join(&capi_config.header.subdirectory);
120 let includedir = includedir
121 .ancestors()
122 .nth(capi_config.pkg_config.strip_include_path_components)
123 .unwrap_or_else(|| Path::new(""));
124
125 format!("-I{}", canonicalize(includedir))
126 } else {
127 String::from("")
128 };
129
130 PkgConfig {
131 name: capi_config.pkg_config.name.clone(),
132 description: capi_config.pkg_config.description.clone(),
133 version: capi_config.pkg_config.version.clone(),
134
135 prefix: "/usr/local".into(),
136 exec_prefix: "${prefix}".into(),
137 includedir: "${prefix}/include".into(),
138 libdir: "${exec_prefix}/lib".into(),
139
140 libs,
141 libs_private: Vec::new(),
142
143 requires,
144 requires_private,
145
146 cflags: vec![cflags],
147
148 conflicts: Vec::new(),
149 }
150 }
151
152 pub(crate) fn from_workspace(
153 name: &str,
154 install_paths: &InstallPaths,
155 args: &clap::ArgMatches,
156 capi_config: &CApiConfig,
157 ) -> Self {
158 let mut pc = PkgConfig::new(name, capi_config);
159
160 pc.prefix.clone_from(&install_paths.prefix);
161 if args.contains_id("includedir") {
163 if let Ok(suffix) = install_paths.includedir.strip_prefix(&pc.prefix) {
164 pc.includedir = PathBuf::from("${prefix}").join(suffix);
165 } else {
166 pc.includedir.clone_from(&install_paths.includedir);
167 }
168 }
169 if args.contains_id("libdir") {
170 if let Ok(suffix) = install_paths.libdir.strip_prefix(&pc.prefix) {
171 pc.libdir = PathBuf::from("${prefix}").join(suffix);
172 } else {
173 pc.libdir.clone_from(&install_paths.libdir);
174 }
175 }
176 pc
177 }
178
179 pub(crate) fn uninstalled(&self, output: &Path) -> Self {
180 let mut uninstalled = self.clone();
181 uninstalled.prefix = output.to_path_buf();
182 uninstalled.includedir = "${prefix}/include".into();
183 uninstalled.libdir = "${prefix}".into();
184 uninstalled.libs[0] = "-L${prefix}".into();
186
187 uninstalled
188 }
189
190 pub fn set_description<S: AsRef<str>>(&mut self, descr: S) -> &mut Self {
191 descr.as_ref().clone_into(&mut self.description);
192 self
193 }
194
195 pub fn set_libs<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
196 let lib = lib.as_ref().to_owned();
197 self.libs.clear();
198 self.libs.push(lib);
199 self
200 }
201
202 pub fn add_lib<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
203 let lib = lib.as_ref().to_owned();
204 self.libs.push(lib);
205 self
206 }
207
208 pub fn set_libs_private<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
209 let lib = lib.as_ref().to_owned();
210 self.libs_private.clear();
211 self.libs_private.push(lib);
212 self
213 }
214
215 pub fn add_lib_private<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
216 let lib = lib.as_ref().to_owned();
217 self.libs_private.push(lib);
218 self
219 }
220
221 pub fn set_cflags<S: AsRef<str>>(&mut self, flag: S) -> &mut Self {
222 let flag = flag.as_ref().to_owned();
223 self.cflags.clear();
224 self.cflags.push(flag);
225 self
226 }
227
228 pub fn add_cflag<S: AsRef<str>>(&mut self, flag: S) -> &mut Self {
229 let flag = flag.as_ref();
230 self.cflags.push(flag.to_owned());
231 self
232 }
233
234 pub fn render(&self) -> String {
235 self.render_help(String::with_capacity(1024)).unwrap()
237 }
238
239 fn render_help<W: core::fmt::Write>(&self, mut w: W) -> Result<W, core::fmt::Error> {
240 writeln!(w, "prefix={}", canonicalize(&self.prefix))?;
241 writeln!(w, "exec_prefix={}", canonicalize(&self.exec_prefix))?;
242 writeln!(w, "libdir={}", canonicalize(&self.libdir))?;
243 writeln!(w, "includedir={}", canonicalize(&self.includedir))?;
244
245 writeln!(w)?;
246
247 writeln!(w, "Name: {}", self.name)?;
248 writeln!(w, "Description: {}", self.description.replace('\n', " "))?; writeln!(w, "Version: {}", self.version)?;
250 writeln!(w, "Libs: {}", self.libs.join(" "))?;
251 writeln!(w, "Cflags: {}", self.cflags.join(" "))?;
252
253 if !self.libs_private.is_empty() {
254 writeln!(w, "Libs.private: {}", self.libs_private.join(" "))?;
255 }
256
257 if !self.requires.is_empty() {
258 writeln!(w, "Requires: {}", self.requires.join(", "))?;
259 }
260
261 if !self.requires_private.is_empty() {
262 let joined = self.requires_private.join(", ");
263 writeln!(w, "Requires.private: {joined}")?;
264 }
265
266 Ok(w)
267 }
268}
269
270#[cfg(test)]
271mod test {
272 use super::*;
273 use semver::Version;
274
275 #[test]
276 fn simple() {
277 let mut pkg = PkgConfig::new(
278 "foo",
279 &CApiConfig {
280 header: crate::build::HeaderCApiConfig {
281 name: "foo".into(),
282 subdirectory: "".into(),
283 generation: true,
284 enabled: true,
285 emit_version_constants: true,
286 },
287 pkg_config: crate::build::PkgConfigCApiConfig {
288 name: "foo".into(),
289 filename: "foo".into(),
290 description: "".into(),
291 version: "0.1".into(),
292 requires: Some("somelib, someotherlib".into()),
293 requires_private: Some("someprivatelib >= 1.0".into()),
294 strip_include_path_components: 0,
295 },
296 library: crate::build::LibraryCApiConfig {
297 name: "foo".into(),
298 version: Version::parse("0.1.0").unwrap(),
299 install_subdir: None,
300 versioning: true,
301 version_suffix_components: None,
302 import_library: true,
303 rustflags: Vec::default(),
304 },
305 install: Default::default(),
306 },
307 );
308 pkg.add_lib("-lbar").add_cflag("-DFOO");
309
310 let expected = concat!(
311 "prefix=/usr/local\n",
312 "exec_prefix=${prefix}\n",
313 "libdir=${exec_prefix}/lib\n",
314 "includedir=${prefix}/include\n",
315 "\n",
316 "Name: foo\n",
317 "Description: \n",
318 "Version: 0.1\n",
319 "Libs: -L${libdir} -lfoo -lbar\n",
320 "Cflags: -I${includedir} -DFOO\n",
321 "Requires: somelib, someotherlib\n",
322 "Requires.private: someprivatelib >= 1.0\n",
323 );
324
325 assert_eq!(expected, pkg.render());
326 }
327
328 mod test_canonicalize {
329 use super::canonicalize;
330
331 #[test]
332 fn test_absolute_path() {
333 let path = "/home/user/docs";
334 let result = canonicalize(path);
335 assert_eq!(result, "/home/user/docs");
336 }
337
338 #[test]
339 fn test_relative_path() {
340 let path = "home/user/docs";
341 let result = canonicalize(path);
342 assert_eq!(result, "home/user/docs");
343 }
344
345 #[test]
346 fn test_current_directory() {
347 let path = "/home/user/./docs";
348 let result = canonicalize(path);
349 assert_eq!(result, "/home/user/docs");
350 }
351
352 #[test]
353 fn test_parent_directory() {
354 let path = "/home/user/../docs";
355 let result = canonicalize(path);
356 assert_eq!(result, "/home/docs");
357 }
358
359 #[test]
360 fn test_mixed_dots_and_parent_dirs() {
361 let path = "/home/./user/../docs/./files";
362 let result = canonicalize(path);
363 assert_eq!(result, "/home/docs/files");
364 }
365
366 #[test]
367 fn test_multiple_consecutive_slashes() {
368 let path = "/home//user///docs";
369 let result = canonicalize(path);
370 assert_eq!(result, "/home/user/docs");
371 }
372
373 #[test]
374 fn test_empty_path() {
375 let path = "";
376 let result = canonicalize(path);
377 assert_eq!(result, "/");
378 }
379
380 #[test]
381 fn test_single_dot() {
382 let path = ".";
383 let result = canonicalize(path);
384 assert_eq!(result, ".");
385 }
386
387 #[test]
388 fn test_single_dot_in_absolute_path() {
389 let path = "/.";
390 let result = canonicalize(path);
391 assert_eq!(result, "/");
392 }
393
394 #[test]
395 fn test_trailing_slash() {
396 let path = "/home/user/docs/";
397 let result = canonicalize(path);
398 assert_eq!(result, "/home/user/docs");
399 }
400
401 #[test]
402 fn test_dots_complex_case() {
403 let path = "/a/b/./c/../d//e/./../f";
404 let result = canonicalize(path);
405 assert_eq!(result, "/a/b/d/f");
406 }
407
408 #[cfg(windows)]
409 mod windows {
410 use std::path::Path;
411
412 use super::*;
413
414 #[test]
415 fn test_canonicalize_basic_windows_path() {
416 let input = Path::new(r"C:\Users\test\..\Documents");
417 let expected = r"C:/Users/Documents";
418 let result = canonicalize(input);
419 assert_eq!(result, expected);
420 }
421
422 #[test]
423 fn test_canonicalize_with_current_dir() {
424 let input = Path::new(r"C:\Users\.\Documents");
425 let expected = r"C:/Users/Documents";
426 let result = canonicalize(input);
427 assert_eq!(result, expected);
428 }
429
430 #[test]
431 fn test_canonicalize_with_double_parent_dir() {
432 let input = Path::new(r"C:\Users\test\..\..\Documents");
433 let expected = r"C:/Documents";
434 let result = canonicalize(input);
435 assert_eq!(result, expected);
436 }
437
438 #[test]
439 fn test_canonicalize_with_trailing_slash() {
440 let input = Path::new(r"C:\Users\test\..\Documents\");
441 let expected = r"C:/Users/Documents";
442 let result = canonicalize(input);
443 assert_eq!(result, expected);
444 }
445
446 #[test]
447 fn test_canonicalize_relative_path() {
448 let input = Path::new(r"Users\test\..\Documents");
449 let expected = r"Users/Documents";
450 let result = canonicalize(input);
451 assert_eq!(result, expected);
452 }
453
454 #[test]
455 fn test_canonicalize_current_dir_only() {
456 let input = Path::new(r".\");
457 let expected = r".";
458 let result = canonicalize(input);
459 assert_eq!(result, expected);
460 }
461 }
462 }
463}