dinghy_lib/
project.rs

1use crate::config::Configuration;
2use crate::utils::copy_and_sync_file;
3use crate::Platform;
4use crate::Result;
5use crate::Runnable;
6use anyhow::anyhow;
7use anyhow::Context;
8use cargo_metadata::Metadata;
9use fs_err as fs;
10use ignore::WalkBuilder;
11use log::{debug, trace};
12use std::fs::File;
13use std::io::prelude::*;
14use std::path::Path;
15use std::path::PathBuf;
16use std::sync::Arc;
17
18#[derive(Debug)]
19pub struct Project {
20    pub conf: Arc<Configuration>,
21    pub metadata: Metadata,
22}
23
24impl Project {
25    pub fn new(conf: &Arc<Configuration>, metadata: Metadata) -> Project {
26        Project {
27            conf: Arc::clone(conf),
28            metadata,
29        }
30    }
31
32    pub fn project_dir(&self) -> Result<PathBuf> {
33        Ok(self.metadata.workspace_root.clone().into_std_path_buf())
34    }
35
36    pub fn overlay_work_dir(&self, platform: &dyn Platform) -> Result<PathBuf> {
37        Ok(self
38            .target_dir(platform.rustc_triple())?
39            .join(platform.rustc_triple()))
40    }
41
42    pub fn target_dir(&self, triple: &str) -> Result<PathBuf> {
43        Ok(self
44            .metadata
45            .target_directory
46            .clone()
47            .into_std_path_buf()
48            .join(triple))
49    }
50
51    pub fn link_test_data(&self, runnable: &Runnable) -> Result<PathBuf> {
52        let test_data_path = runnable
53            .exe
54            .parent()
55            .and_then(|it| it.parent())
56            .map(|it| it.join("dinghy"))
57            .map(|it| it.join(runnable.exe.file_name().unwrap()))
58            .map(|it| it.join("test_data"))
59            .unwrap();
60
61        fs::create_dir_all(&test_data_path)?;
62        let test_data_cfg_path = test_data_path.join("test_data.cfg");
63        let mut test_data_cfg = File::create(&test_data_cfg_path)?;
64        debug!("Generating {}", test_data_cfg_path.display());
65
66        for td in self.conf.test_data.iter() {
67            let target_path = td
68                .base
69                .parent()
70                .unwrap_or(&PathBuf::from("/"))
71                .join(&td.source);
72            let target_path = target_path
73                .to_str()
74                .ok_or_else(|| anyhow!("Invalid UTF-8 path {}", target_path.display()))?;
75
76            test_data_cfg.write_all(td.id.as_bytes())?;
77            test_data_cfg.write_all(b":")?;
78            test_data_cfg.write_all(target_path.as_bytes())?;
79            test_data_cfg.write_all(b"\n")?;
80        }
81        Ok(test_data_path)
82    }
83
84    pub fn copy_test_data<T: AsRef<Path>>(&self, app_path: T) -> Result<()> {
85        let app_path = app_path.as_ref();
86        let test_data_path = app_path.join("test_data");
87        fs::create_dir_all(&test_data_path)?;
88
89        for td in self.conf.test_data.iter() {
90            let file = td
91                .base
92                .parent()
93                .unwrap_or(&PathBuf::from("/"))
94                .join(&td.source);
95            if Path::new(&file).exists() {
96                let metadata = file.metadata()?;
97                let dst = test_data_path.join(&td.id);
98                if metadata.is_dir() {
99                    rec_copy(file, dst, td.copy_git_ignored)?;
100                } else {
101                    copy_and_sync_file(file, dst)?;
102                }
103            } else {
104                log::warn!(
105                    "configuration required test_data `{:?}` but it could not be found",
106                    td
107                );
108            }
109        }
110        Ok(())
111    }
112}
113
114pub fn rec_copy<P1: AsRef<Path>, P2: AsRef<Path>>(
115    src: P1,
116    dst: P2,
117    copy_ignored_test_data: bool,
118) -> Result<()> {
119    let empty: &[&str] = &[];
120    rec_copy_excl(src, dst, copy_ignored_test_data, empty)
121}
122
123pub fn rec_copy_excl<P1: AsRef<Path>, P2: AsRef<Path>, P3: AsRef<Path> + ::std::fmt::Debug>(
124    src: P1,
125    dst: P2,
126    copy_ignored_test_data: bool,
127    more_exclude: &[P3],
128) -> Result<()> {
129    let src = src.as_ref();
130    let dst = dst.as_ref();
131    let ignore_file = src.join(".dinghyignore");
132    debug!(
133        "Copying recursively from {} to {} excluding {:?}",
134        src.display(),
135        dst.display(),
136        more_exclude
137    );
138
139    let mut walker = WalkBuilder::new(src);
140    walker.follow_links(true);
141    walker.git_ignore(!copy_ignored_test_data);
142    walker.add_ignore(ignore_file);
143    for entry in walker.build() {
144        let entry = entry?;
145        let metadata = entry.metadata()?;
146
147        if more_exclude.iter().any(|ex| entry.path().starts_with(ex)) {
148            debug!("Exclude {:?}", entry.path());
149            continue;
150        }
151        trace!(
152            "Processing entry {:?} is_dir:{:?}",
153            entry.path(),
154            metadata.is_dir()
155        );
156
157        let path = entry.path().strip_prefix(src)?;
158
159        // Check if root path is a file or a directory
160        let target = if path.parent().is_none() && metadata.is_file() {
161            fs::create_dir_all(
162                &dst.parent()
163                    .ok_or_else(|| anyhow!("Invalid file {}", dst.display()))?,
164            )?;
165            dst.to_path_buf()
166        } else {
167            dst.join(path)
168        };
169
170        if metadata.is_dir() {
171            if target.exists() && target.is_file() {
172                fs::remove_file(&target)?;
173            }
174            trace!("Creating directory {}", target.display());
175            fs::create_dir_all(&target)?;
176        } else if metadata.is_file() {
177            if target.exists() && !target.is_file() {
178                trace!("Remove 2 {:?}", target);
179                fs::remove_dir_all(&target)?;
180            }
181            if !target.exists()
182                || target.metadata()?.len() != entry.metadata()?.len()
183                || target.metadata()?.modified()? < entry.metadata()?.modified()?
184            {
185                if target.exists() && target.metadata()?.permissions().readonly() {
186                    fs::remove_dir_all(&target)?;
187                }
188                trace!("Copying {} to {}", entry.path().display(), target.display());
189                copy_and_sync_file(entry.path(), &target)
190                    .with_context(|| format!("Syncing {entry:?} and {target:?}"))?;
191            } else {
192                trace!("{} is already up-to-date", target.display());
193            }
194        } else {
195            debug!("ignored {:?} ({:?})", path, metadata);
196        }
197    }
198    trace!(
199        "Copied recursively from {} to {} excluding {:?}",
200        src.display(),
201        dst.display(),
202        more_exclude
203    );
204    Ok(())
205}