cps_deps/
generate_from_pkg_config.rs1use crate::lib_search::LibraryLocation;
2use crate::{cps, lib_search, pkg_config};
3use anyhow::{Context, Result};
4use std::collections::HashMap;
5use std::fs;
6use std::path::{Path, PathBuf};
7use walkdir::WalkDir;
8
9fn find_pc_files() -> Vec<PathBuf> {
10 [
11 "/usr/lib",
12 "/usr/share",
13 "/usr/local/lib",
14 "/usr/local/share",
15 ]
16 .iter()
17 .map(PathBuf::from)
18 .flat_map(|dir| WalkDir::new(dir).into_iter().filter_map(Result::ok))
19 .filter(|dir_entry| dir_entry.file_type().is_file())
20 .filter(|dir_entry| dir_entry.path().extension().is_some_and(|ex| ex == "pc"))
21 .map(|dir_entry| PathBuf::from(dir_entry.path()))
22 .collect()
23}
24
25impl TryFrom<pkg_config::PkgConfigFile> for cps::Package {
26 type Error = anyhow::Error;
27
28 fn try_from(pkg_config: pkg_config::PkgConfigFile) -> Result<cps::Package> {
29 let library_locations = lib_search::find_locations(&pkg_config)?;
30
31 let location_library_name = pkg_config.link_libraries.first();
32 let default_component_name = location_library_name.unwrap_or(&pkg_config.name);
33
34 let package_requires_map: HashMap<_, _> = pkg_config
35 .requires
36 .iter()
37 .filter(|req| req.version.is_some())
38 .map(|req| {
39 (
40 req.name.clone(),
41 cps::Requirement {
42 version: req.version.clone(),
43 ..cps::Requirement::default()
44 },
45 )
46 })
47 .collect();
48 let package_requires_map =
49 (!package_requires_map.is_empty()).then_some(package_requires_map);
50
51 let local_requires: Vec<String> = library_locations
52 .keys()
53 .filter(|&name| {
54 location_library_name.is_some() && name != location_library_name.unwrap()
55 })
56 .map(|name| format!(":{}", name))
57 .collect();
58 let local_requires = (!local_requires.is_empty()).then_some(local_requires);
59 let remote_requres = (!pkg_config.requires.is_empty()).then(|| {
60 pkg_config
61 .requires
62 .iter()
63 .map(|d| d.name.clone())
64 .collect::<Vec<_>>()
65 });
66 let default_component_requires = match (local_requires, remote_requres) {
67 (Some(local), Some(remote)) => Some(local.into_iter().chain(remote).collect()),
68 (Some(local), None) => Some(local),
69 (None, Some(remote)) => Some(remote),
70 (None, None) => None,
71 };
72
73 let mut package_configurations: Option<Vec<String>> = None;
74 let mut components = HashMap::<String, cps::MaybeComponent>::new();
75 for (name, location) in library_locations {
76 match location {
77 LibraryLocation::Dylib(location) => {
78 components.insert(
79 name.clone(),
80 cps::MaybeComponent::from_dylib_location(&location),
81 );
82 }
83 LibraryLocation::Archive(location) => {
84 components.insert(
85 name.clone(),
86 cps::MaybeComponent::from_archive_location(&location),
87 );
88 }
89 LibraryLocation::Both { archive, dylib } => {
90 package_configurations = Some(vec!["shared".to_string(), "static".to_string()]);
91 components.insert(
92 name.clone(),
93 cps::MaybeComponent::Component(cps::Component::Interface(
94 cps::ComponentFields {
95 configurations: Some(
96 [
97 (
98 "shared".to_string(),
99 cps::Configuration {
100 requires: Some(vec![format!(":{}-shared", name)]),
101 ..cps::Configuration::default()
102 },
103 ),
104 (
105 "static".to_string(),
106 cps::Configuration {
107 requires: Some(vec![format!(":{}-static", name)]),
108 ..cps::Configuration::default()
109 },
110 ),
111 ]
112 .into_iter()
113 .collect(),
114 ),
115 ..cps::ComponentFields::default()
116 },
117 )),
118 );
119 components.insert(
120 format!("{}-shared", name),
121 cps::MaybeComponent::from_dylib_location(&archive),
122 );
123 components.insert(
124 format!("{}-static", name),
125 cps::MaybeComponent::from_archive_location(&dylib),
126 );
127 }
128 };
129 }
130
131 let default_component = components.entry(default_component_name.clone()).or_insert(
132 cps::MaybeComponent::Component(cps::Component::Interface(
133 cps::ComponentFields::default(),
134 )),
135 );
136 let default_component = match default_component {
137 cps::MaybeComponent::Component(cps::Component::Interface(fields)) => fields,
138 cps::MaybeComponent::Component(cps::Component::Dylib(fields)) => fields,
139 cps::MaybeComponent::Component(cps::Component::Archive(fields)) => fields,
140 component => {
141 anyhow::bail!("Unknwon default component type found: {:?}", component)
142 }
143 };
144
145 if default_component_requires.is_some() {
147 if let Some(configurations) = &mut default_component.configurations {
148 for configuration in configurations.values_mut() {
149 configuration.requires = Some(
150 [
151 &configuration.requires.clone().unwrap_or_default()[..],
152 &default_component_requires.clone().unwrap_or_default()[..],
153 ]
154 .concat(),
155 );
156 }
157 } else {
158 default_component.requires = default_component_requires;
159 }
160 }
161
162 default_component.compile_flags = (!pkg_config.compile_flags.is_empty())
163 .then(|| cps::LanguageStringList::any_language_map(pkg_config.compile_flags));
164 default_component.definitions = (!pkg_config.definitions.is_empty())
165 .then(|| cps::LanguageStringList::any_language_map(pkg_config.definitions));
166 default_component.includes = (!pkg_config.includes.is_empty())
167 .then(|| cps::LanguageStringList::any_language_map(pkg_config.includes));
168 default_component.link_flags =
169 (!pkg_config.link_flags.is_empty()).then_some(pkg_config.link_flags);
170
171 let cps = cps::Package {
172 name: pkg_config.name.clone(),
173 version: Some(pkg_config.version),
174 description: Some(pkg_config.description),
175 default_components: Some(vec![default_component_name.clone()]),
176 requires: package_requires_map,
177 components,
178 configurations: package_configurations,
179 ..cps::Package::default()
180 };
181 Ok(cps)
182 }
183}
184
185pub fn generate_all_from_pkg_config(outdir: &Path) -> Result<()> {
186 let pc_files = find_pc_files();
187
188 fs::create_dir_all(outdir)?;
189
190 for path in pc_files {
191 dbg!(&path);
192 let pc_filename = path
193 .file_name()
194 .context("error getting filename of pc file")?
195 .to_str()
196 .context("error converting OsStr to str")?
197 .to_string();
198 let data = std::fs::read_to_string(path)?;
199 let pkg_config = match pkg_config::PkgConfigFile::parse(&data) {
200 Ok(pkg_config) => pkg_config,
201 Err(error) => {
202 eprintln!("Error:\n{}", error);
203 continue;
204 }
205 };
206 let cps_package: cps::Package = match pkg_config.try_into() {
207 Ok(cps) => cps,
208 Err(error) => {
209 eprintln!("Error:\n{}", error);
210 continue;
211 }
212 };
213 let json = serde_json::to_string_pretty(&cps_package)?;
214 let cps_filename = pc_filename.replace(".pc", ".cps");
215 std::fs::write(outdir.join(cps_filename), json)?;
216 }
217
218 Ok(())
219}
220
221pub fn generate_from_pkg_config(pc_filepath: &Path, cps_filepath: &Path) -> Result<()> {
222 let data = std::fs::read_to_string(pc_filepath)?;
223 let pkg_config = pkg_config::PkgConfigFile::parse(&data)?;
224 let cps_package: cps::Package = pkg_config.try_into()?;
225 let json = serde_json::to_string_pretty(&cps_package)?;
226 std::fs::write(cps_filepath, json)?;
227 Ok(())
228}