#![allow(dead_code)]
use crate::build::CApiConfig;
use crate::install::InstallPaths;
use std::path::{Component, Path, PathBuf};
fn canonicalize<P: AsRef<Path>>(path: P) -> String {
let mut stack = Vec::with_capacity(16);
struct Item<'a> {
separator: bool,
component: Component<'a>,
}
let mut separator = false;
for component in path.as_ref().components() {
match component {
Component::RootDir => {
separator = true;
}
Component::Prefix(_) => stack.push(Item {
separator: false,
component,
}),
Component::ParentDir => {
let _ = stack.pop();
}
Component::CurDir => stack.push(Item {
separator: false,
component,
}),
Component::Normal(_) => {
stack.push(Item {
separator,
component,
});
separator = true;
}
}
}
if stack.is_empty() {
String::from("/")
} else {
let mut buf = String::with_capacity(64);
for item in stack {
if item.separator {
buf.push('/');
}
buf.push_str(&item.component.as_os_str().to_string_lossy());
}
buf
}
}
#[derive(Debug, Clone)]
pub struct PkgConfig {
prefix: PathBuf,
exec_prefix: PathBuf,
includedir: PathBuf,
libdir: PathBuf,
name: String,
description: String,
version: String,
requires: Vec<String>,
requires_private: Vec<String>,
libs: Vec<String>,
libs_private: Vec<String>,
cflags: Vec<String>,
conflicts: Vec<String>,
}
impl PkgConfig {
pub fn new(_name: &str, capi_config: &CApiConfig) -> Self {
let requires = match &capi_config.pkg_config.requires {
Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
_ => Vec::new(),
};
let requires_private = match &capi_config.pkg_config.requires_private {
Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
_ => Vec::new(),
};
let mut libdir = PathBuf::new();
libdir.push("${libdir}");
if let Some(subdir) = &capi_config.library.install_subdir {
libdir.push(subdir);
}
let libs = vec![
format!("-L{}", canonicalize(libdir.display().to_string())),
format!("-l{}", capi_config.library.name),
];
let cflags = if capi_config.header.enabled {
let includedir = Path::new("${includedir}").join(&capi_config.header.subdirectory);
let includedir = includedir
.ancestors()
.nth(capi_config.pkg_config.strip_include_path_components)
.unwrap_or_else(|| Path::new(""));
format!("-I{}", canonicalize(includedir))
} else {
String::from("")
};
PkgConfig {
name: capi_config.pkg_config.name.clone(),
description: capi_config.pkg_config.description.clone(),
version: capi_config.pkg_config.version.clone(),
prefix: "/usr/local".into(),
exec_prefix: "${prefix}".into(),
includedir: "${prefix}/include".into(),
libdir: "${exec_prefix}/lib".into(),
libs,
libs_private: Vec::new(),
requires,
requires_private,
cflags: vec![cflags],
conflicts: Vec::new(),
}
}
pub(crate) fn from_workspace(
name: &str,
install_paths: &InstallPaths,
args: &clap::ArgMatches,
capi_config: &CApiConfig,
) -> Self {
let mut pc = PkgConfig::new(name, capi_config);
pc.prefix.clone_from(&install_paths.prefix);
if args.contains_id("includedir") {
if let Ok(suffix) = install_paths.includedir.strip_prefix(&pc.prefix) {
pc.includedir = PathBuf::from("${prefix}").join(suffix);
} else {
pc.includedir.clone_from(&install_paths.includedir);
}
}
if args.contains_id("libdir") {
if let Ok(suffix) = install_paths.libdir.strip_prefix(&pc.prefix) {
pc.libdir = PathBuf::from("${prefix}").join(suffix);
} else {
pc.libdir.clone_from(&install_paths.libdir);
}
}
pc
}
pub(crate) fn uninstalled(&self, output: &Path) -> Self {
let mut uninstalled = self.clone();
uninstalled.prefix = output.to_path_buf();
uninstalled.includedir = "${prefix}/include".into();
uninstalled.libdir = "${prefix}".into();
uninstalled.libs[0] = "-L${prefix}".into();
uninstalled
}
pub fn set_description<S: AsRef<str>>(&mut self, descr: S) -> &mut Self {
descr.as_ref().clone_into(&mut self.description);
self
}
pub fn set_libs<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs.clear();
self.libs.push(lib);
self
}
pub fn add_lib<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs.push(lib);
self
}
pub fn set_libs_private<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs_private.clear();
self.libs_private.push(lib);
self
}
pub fn add_lib_private<S: AsRef<str>>(&mut self, lib: S) -> &mut Self {
let lib = lib.as_ref().to_owned();
self.libs_private.push(lib);
self
}
pub fn set_cflags<S: AsRef<str>>(&mut self, flag: S) -> &mut Self {
let flag = flag.as_ref().to_owned();
self.cflags.clear();
self.cflags.push(flag);
self
}
pub fn add_cflag<S: AsRef<str>>(&mut self, flag: S) -> &mut Self {
let flag = flag.as_ref();
self.cflags.push(flag.to_owned());
self
}
pub fn render(&self) -> String {
self.render_help(String::with_capacity(1024)).unwrap()
}
fn render_help<W: core::fmt::Write>(&self, mut w: W) -> Result<W, core::fmt::Error> {
writeln!(w, "prefix={}", canonicalize(&self.prefix))?;
writeln!(w, "exec_prefix={}", canonicalize(&self.exec_prefix))?;
writeln!(w, "libdir={}", canonicalize(&self.libdir))?;
writeln!(w, "includedir={}", canonicalize(&self.includedir))?;
writeln!(w)?;
writeln!(w, "Name: {}", self.name)?;
writeln!(w, "Description: {}", self.description.replace('\n', " "))?; writeln!(w, "Version: {}", self.version)?;
writeln!(w, "Libs: {}", self.libs.join(" "))?;
writeln!(w, "Cflags: {}", self.cflags.join(" "))?;
if !self.libs_private.is_empty() {
writeln!(w, "Libs.private: {}", self.libs_private.join(" "))?;
}
if !self.requires.is_empty() {
writeln!(w, "Requires: {}", self.requires.join(", "))?;
}
if !self.requires_private.is_empty() {
let joined = self.requires_private.join(", ");
writeln!(w, "Requires.private: {joined}")?;
}
Ok(w)
}
}
#[cfg(test)]
mod test {
use super::*;
use semver::Version;
#[test]
fn simple() {
let mut pkg = PkgConfig::new(
"foo",
&CApiConfig {
header: crate::build::HeaderCApiConfig {
name: "foo".into(),
subdirectory: "".into(),
generation: true,
enabled: true,
emit_version_constants: true,
},
pkg_config: crate::build::PkgConfigCApiConfig {
name: "foo".into(),
filename: "foo".into(),
description: "".into(),
version: "0.1".into(),
requires: Some("somelib, someotherlib".into()),
requires_private: Some("someprivatelib >= 1.0".into()),
strip_include_path_components: 0,
},
library: crate::build::LibraryCApiConfig {
name: "foo".into(),
version: Version::parse("0.1.0").unwrap(),
install_subdir: None,
versioning: true,
version_suffix_components: None,
import_library: true,
rustflags: Vec::default(),
},
install: Default::default(),
},
);
pkg.add_lib("-lbar").add_cflag("-DFOO");
let expected = concat!(
"prefix=/usr/local\n",
"exec_prefix=${prefix}\n",
"libdir=${exec_prefix}/lib\n",
"includedir=${prefix}/include\n",
"\n",
"Name: foo\n",
"Description: \n",
"Version: 0.1\n",
"Libs: -L${libdir} -lfoo -lbar\n",
"Cflags: -I${includedir} -DFOO\n",
"Requires: somelib, someotherlib\n",
"Requires.private: someprivatelib >= 1.0\n",
);
assert_eq!(expected, pkg.render());
}
mod test_canonicalize {
use super::canonicalize;
#[test]
fn test_absolute_path() {
let path = "/home/user/docs";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_relative_path() {
let path = "home/user/docs";
let result = canonicalize(path);
assert_eq!(result, "home/user/docs");
}
#[test]
fn test_current_directory() {
let path = "/home/user/./docs";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_parent_directory() {
let path = "/home/user/../docs";
let result = canonicalize(path);
assert_eq!(result, "/home/docs");
}
#[test]
fn test_mixed_dots_and_parent_dirs() {
let path = "/home/./user/../docs/./files";
let result = canonicalize(path);
assert_eq!(result, "/home/docs/files");
}
#[test]
fn test_multiple_consecutive_slashes() {
let path = "/home//user///docs";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_empty_path() {
let path = "";
let result = canonicalize(path);
assert_eq!(result, "/");
}
#[test]
fn test_single_dot() {
let path = ".";
let result = canonicalize(path);
assert_eq!(result, ".");
}
#[test]
fn test_single_dot_in_absolute_path() {
let path = "/.";
let result = canonicalize(path);
assert_eq!(result, "/");
}
#[test]
fn test_trailing_slash() {
let path = "/home/user/docs/";
let result = canonicalize(path);
assert_eq!(result, "/home/user/docs");
}
#[test]
fn test_dots_complex_case() {
let path = "/a/b/./c/../d//e/./../f";
let result = canonicalize(path);
assert_eq!(result, "/a/b/d/f");
}
#[cfg(windows)]
mod windows {
use std::path::Path;
use super::*;
#[test]
fn test_canonicalize_basic_windows_path() {
let input = Path::new(r"C:\Users\test\..\Documents");
let expected = r"C:/Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_with_current_dir() {
let input = Path::new(r"C:\Users\.\Documents");
let expected = r"C:/Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_with_double_parent_dir() {
let input = Path::new(r"C:\Users\test\..\..\Documents");
let expected = r"C:/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_with_trailing_slash() {
let input = Path::new(r"C:\Users\test\..\Documents\");
let expected = r"C:/Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_relative_path() {
let input = Path::new(r"Users\test\..\Documents");
let expected = r"Users/Documents";
let result = canonicalize(input);
assert_eq!(result, expected);
}
#[test]
fn test_canonicalize_current_dir_only() {
let input = Path::new(r".\");
let expected = r".";
let result = canonicalize(input);
assert_eq!(result, expected);
}
}
}
}