1use crate::config::PlatformConfiguration;
2use crate::errors::*;
3use crate::project::Project;
4use crate::utils::contains_file_with_ext;
5use crate::utils::destructure_path;
6use crate::utils::file_has_ext;
7use crate::utils::lib_name_from;
8use crate::Platform;
9use dinghy_build::build_env::append_path_to_target_env;
10use dinghy_build::build_env::envify;
11use dinghy_build::build_env::set_env_ifndef;
12use dinghy_build::utils::path_between;
13use dirs::home_dir;
14use fs_err::{create_dir_all, read_dir, remove_dir_all, File};
15use itertools::Itertools;
16use std::io::Write;
17use std::path::Path;
18use std::path::PathBuf;
19use walkdir::WalkDir;
20
21#[derive(Clone, Debug)]
22pub enum OverlayScope {
23 Application,
24 System,
25}
26
27#[derive(Clone, Debug)]
28pub struct Overlay {
29 pub id: String,
30 pub path: PathBuf,
31 pub scope: OverlayScope,
32}
33
34#[derive(Clone, Debug)]
35pub struct Overlayer {
36 platform_id: String,
37 rustc_triple: Option<String>,
38 sysroot: PathBuf,
39 work_dir: PathBuf,
40}
41
42impl Overlayer {
43 pub fn overlay<P: AsRef<Path>>(
44 configuration: &PlatformConfiguration,
45 platform: &dyn Platform,
46 project: &Project,
47 sysroot: P,
48 ) -> Result<()> {
49 let overlayer = Overlayer {
50 platform_id: platform.id().to_string(),
51 rustc_triple: Some(platform.rustc_triple().to_string()),
52 sysroot: sysroot.as_ref().to_path_buf(),
53 work_dir: project.overlay_work_dir(platform)?,
54 };
55
56 let mut path_to_try = vec![];
57 let project_path = project.project_dir()?;
58 let mut current_path = project_path.as_path();
59 while current_path.parent().is_some() {
60 path_to_try.push(
61 current_path
62 .join(".dinghy")
63 .join("overlay")
64 .join(&overlayer.platform_id),
65 );
66 if let Some(parent_path) = current_path.parent() {
67 current_path = parent_path;
68 } else {
69 break;
70 }
71 }
72
73 if let Some(dinghy_home_dir) = home_dir().map(|it| {
75 it.join(".dinghy")
76 .join("overlay")
77 .join(&overlayer.platform_id)
78 }) {
79 if !path_to_try.contains(&dinghy_home_dir) {
80 path_to_try.push(dinghy_home_dir)
81 }
82 }
83
84 overlayer.apply_overlay(
85 Overlayer::from_conf(configuration)?
86 .into_iter()
87 .chain(path_to_try.into_iter().flat_map(|path_to_try| {
88 Overlayer::from_directory(path_to_try).unwrap_or_default()
89 }))
90 .unique_by(|overlay| overlay.id.clone())
91 .collect_vec(),
92 )
93 }
94
95 fn from_conf(configuration: &PlatformConfiguration) -> Result<Vec<Overlay>> {
96 Ok(configuration
97 .overlays
98 .as_ref()
99 .unwrap_or(&::std::collections::HashMap::new())
100 .into_iter()
101 .map(|(overlay_id, overlay_conf)| Overlay {
102 id: overlay_id.to_string(),
103 path: PathBuf::from(overlay_conf.path.as_str()),
104 scope: OverlayScope::Application,
105 })
106 .collect())
107 }
108
109 fn from_directory<P: AsRef<Path>>(overlay_root_dir: P) -> Result<Vec<Overlay>> {
110 Ok(read_dir(overlay_root_dir.as_ref())?
111 .filter_map(|it| it.ok()) .map(|it| it.path())
113 .filter(|it| it.is_dir())
114 .filter_map(destructure_path)
115 .map(|(overlay_dir_path, overlay_dir_name)| Overlay {
116 id: overlay_dir_name,
117 path: overlay_dir_path.to_path_buf(),
118 scope: OverlayScope::Application,
119 })
120 .collect())
121 }
122
123 fn apply_overlay<I>(&self, overlays: I) -> Result<()>
124 where
125 I: IntoIterator<Item = Overlay>,
126 {
127 let pkg_config_env_var = self
128 .rustc_triple
129 .as_ref()
130 .map(|_| "PKG_CONFIG_LIBDIR")
131 .unwrap_or("PKG_CONFIG_LIBPATH");
133
134 if let Err(error) = remove_dir_all(&self.work_dir) {
136 if self.work_dir.exists() {
137 log::warn!(
138 "Couldn't cleanup directory overlay work directory {} ({:?})",
139 self.work_dir.display(),
140 error
141 )
142 }
143 }
144 create_dir_all(&self.work_dir)?;
145 append_path_to_target_env(
146 pkg_config_env_var,
147 self.rustc_triple.as_ref(),
148 &self.work_dir,
149 );
150
151 for overlay in overlays {
152 log::debug!("Overlaying '{}'", overlay.id.as_str());
153 let mut has_pkg_config_files = false;
154
155 let pkg_config_path_list = WalkDir::new(&overlay.path)
156 .into_iter()
157 .filter_map(|entry| entry.ok()) .filter(|entry| entry.file_type().is_dir())
159 .filter(|dir| {
160 dir.file_name() == "pkgconfig" || contains_file_with_ext(dir.path(), ".pc")
161 })
162 .map(|pkg_config_path| pkg_config_path.path().to_path_buf());
163
164 for pkg_config_path in pkg_config_path_list {
165 log::debug!(
166 "Discovered pkg-config directory '{}'",
167 pkg_config_path.display()
168 );
169 append_path_to_target_env(
170 pkg_config_env_var,
171 self.rustc_triple.as_ref(),
172 pkg_config_path,
173 );
174 has_pkg_config_files = true;
175 }
176 if !has_pkg_config_files {
177 self.generate_pkg_config_file(&overlay)?;
178 append_path_to_target_env(
179 pkg_config_env_var,
180 self.rustc_triple.as_ref(),
181 &overlay.path,
182 );
183 }
184
185 set_env_ifndef(
187 envify(format!("PKG_CONFIG_{}_PREFIX", overlay.id)),
188 path_between(&self.sysroot, &overlay.path),
189 );
190 }
191 Ok(())
192 }
193
194 fn generate_pkg_config_file(&self, overlay: &Overlay) -> Result<()> {
195 fn write_pkg_config_file<P: AsRef<Path>, T: AsRef<str>>(
196 pc_file_path: P,
197 name: &str,
198 libs: &[T],
199 ) -> Result<()> {
200 log::debug!(
201 "Generating pkg-config pc file {}",
202 pc_file_path.as_ref().display()
203 );
204 let mut pc_file = File::create(pc_file_path.as_ref())?;
205 pc_file.write_all(b"prefix:/")?;
206 pc_file.write_all(b"\nexec_prefix:${prefix}")?;
207 pc_file.write_all(b"\nName: ")?;
208 pc_file.write_all(name.as_bytes())?;
209 pc_file.write_all(b"\nDescription: ")?;
210 pc_file.write_all(name.as_bytes())?;
211 pc_file.write_all(b"\nVersion: unspecified")?;
212 pc_file.write_all(b"\nLibs: -L${prefix} ")?;
213 for lib in libs {
214 pc_file.write_all(b" -l")?;
215 pc_file.write_all(lib.as_ref().as_bytes())?;
216 }
217 pc_file.write_all(b"\nCflags: -I${prefix}")?;
218 Ok(())
219 }
220
221 let pc_file = self.work_dir.join(format!("{}.pc", self.platform_id));
222 let lib_list = WalkDir::new(&overlay.path)
223 .max_depth(1)
224 .into_iter()
225 .filter_map(|entry| entry.ok()) .filter(|entry| file_has_ext(entry.path(), ".so"))
227 .filter_map(|e| lib_name_from(e.path()).ok())
228 .collect_vec();
229
230 write_pkg_config_file(pc_file.as_path(), overlay.id.as_str(), &lib_list).with_context(
231 || {
232 format!(
233 "Dinghy couldn't generate pkg-config pc file {}",
234 pc_file.as_path().display()
235 )
236 },
237 )
238 }
239}