dinghy_lib/
overlay.rs

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        // Project may be outside home directory. So add it too.
74        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()) // Ignore invalid directories
112            .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            // Fallback on PKG_CONFIG_LIBPATH for host as it doesn't erase pkg_config paths
132            .unwrap_or("PKG_CONFIG_LIBPATH");
133
134        // Setup overlay work directory
135        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()) // Ignore unreadable directories, maybe could warn...
158                .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            // Override the 'prefix' pkg-config variable for the specified overlay only.
186            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()) // Ignore unreadable files, maybe could warn...
226            .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}