uvm_install2/
lib.rs

1mod error;
2mod install;
3mod sys;
4use crate::error::InstallError::{InstallFailed, InstallerCreatedFailed, LoadingInstallerFailed};
5pub use error::*;
6use install::utils;
7use install::{InstallManifest, Loader};
8use lazy_static::lazy_static;
9use log::{debug, info, trace};
10use ssri::Integrity;
11use std::collections::HashSet;
12use std::ops::{Deref, DerefMut};
13use std::path::{Path, PathBuf};
14use std::{fs, io};
15use sys::create_installer;
16pub use unity_hub::error::UnityError;
17pub use unity_hub::error::UnityHubError;
18pub use unity_hub::unity;
19use unity_hub::unity::hub;
20use unity_hub::unity::hub::editors::EditorInstallation;
21use unity_hub::unity::hub::module::Module;
22use unity_hub::unity::hub::paths::locks_dir;
23use unity_hub::unity::{Installation, UnityInstallation};
24pub use unity_version::error::VersionError;
25pub use unity_version::Version;
26use uvm_install_graph::{InstallGraph, InstallStatus, UnityComponent, Walker};
27pub use uvm_live_platform::error::LivePlatformError;
28pub use uvm_live_platform::fetch_release;
29use uvm_live_platform::Release;
30
31lazy_static! {
32    static ref UNITY_BASE_PATTERN: &'static Path = Path::new("{UNITY_PATH}");
33}
34
35impl AsRef<Path> for UNITY_BASE_PATTERN {
36    fn as_ref(&self) -> &Path {
37        self.deref()
38    }
39}
40
41fn print_graph<'a>(graph: &'a InstallGraph<'a>) {
42    use console::Style;
43
44    for node in graph.topo().iter(graph.context()) {
45        let component = graph.component(node).unwrap();
46        let install_status = graph.install_status(node).unwrap();
47        let prefix: String = [' '].iter().cycle().take(graph.depth(node) * 2).collect();
48
49        let style = match install_status {
50            InstallStatus::Unknown => Style::default().dim(),
51            InstallStatus::Missing => Style::default().yellow().blink(),
52            InstallStatus::Installed => Style::default().green(),
53        };
54
55        info!(
56            "{}- {} ({})",
57            prefix,
58            component,
59            style.apply_to(install_status)
60        );
61    }
62}
63
64pub fn install<V, P, I>(
65    version: V,
66    requested_modules: Option<I>,
67    install_sync: bool,
68    destination: Option<P>,
69) -> Result<UnityInstallation>
70where
71    V: AsRef<Version>,
72    P: AsRef<Path>,
73    I: IntoIterator,
74    I::Item: Into<String>,
75{
76    let version = version.as_ref();
77    let version_string = version.to_string();
78
79    let locks_dir = locks_dir().ok_or_else(|| {
80        InstallError::LockProcessFailure(io::Error::new(
81            io::ErrorKind::NotFound,
82            "Unable to locate locks directory.",
83        ))
84    })?;
85
86    fs::DirBuilder::new().recursive(true).create(&locks_dir)?;
87    lock_process!(locks_dir.join(format!("{}.lock", version_string)));
88
89    let unity_release = fetch_release(version.to_owned())?;
90    eprintln!("{:#?}", unity_release);
91    let mut graph = InstallGraph::from(&unity_release);
92
93    //
94
95    let mut editor_installation: Option<EditorInstallation> = None;
96    let base_dir = if let Some(destination) = destination {
97        let destination = destination.as_ref();
98        if destination.exists() && !destination.is_dir() {
99            return Err(io::Error::new(
100                io::ErrorKind::InvalidInput,
101                "Requested destination is not a directory.",
102            )
103            .into());
104        }
105
106        editor_installation = Some(EditorInstallation::new(
107            version.to_owned(),
108            destination.to_path_buf(),
109        ));
110        destination.to_path_buf()
111    } else {
112        hub::paths::install_path()
113            .map(|path| path.join(format!("{}", version)))
114            .or_else(|| {
115                {
116                    #[cfg(any(target_os = "windows", target_os = "macos"))]
117                    let application_path = dirs_2::application_dir();
118                    #[cfg(target_os = "linux")]
119                    let application_path = dirs_2::executable_dir();
120                    application_path
121                }
122                .map(|path| path.join(format!("Unity-{}", version)))
123            })
124            .expect("default installation directory")
125    };
126
127    let installation = UnityInstallation::new(&base_dir);
128    if let Ok(ref installation) = installation {
129        let modules = installation.installed_modules()?;
130        let mut module_ids: HashSet<String> =
131            modules.into_iter().map(|m| m.id().to_string()).collect();
132        module_ids.insert("Unity".to_string());
133        graph.mark_installed(&module_ids);
134    } else {
135        info!("\nFresh install");
136        graph.mark_all_missing();
137    }
138
139    // info!("All available modules for Unity {}", version);
140    // print_graph(&graph);
141
142    let base_iterator = ["Unity".to_string()].into_iter();
143    let all_components: HashSet<String> = match requested_modules {
144        Some(modules) => modules
145            .into_iter()
146            .flat_map(|module| {
147                let module = module.into();
148                let node = graph.get_node_id(&module).ok_or_else(|| {
149                    debug!(
150                        "Unsupported module '{}' for selected api version {}",
151                        module, version
152                    );
153                    InstallError::UnsupportedModule(module.to_string(), version.to_string())
154                });
155
156                match node {
157                    Ok(node) => {
158                        let mut out = vec![Ok(module.to_string())];
159                        out.append(
160                            &mut graph
161                                .get_dependend_modules(node)
162                                .iter()
163                                .map({
164                                    |((c, _), _)| match c {
165                                        UnityComponent::Editor(_) => Ok("Unity".to_string()),
166                                        UnityComponent::Module(m) => Ok(m.id().to_string()),
167                                    }
168                                })
169                                .collect(),
170                        );
171                        if install_sync {
172                            out.append(
173                                &mut graph
174                                    .get_sub_modules(node)
175                                    .iter()
176                                    .map({
177                                        |((c, _), _)| match c {
178                                            UnityComponent::Editor(_) => Ok("Unity".to_string()),
179                                            UnityComponent::Module(m) => Ok(m.id().to_string()),
180                                        }
181                                    })
182                                    .collect(),
183                            );
184                        }
185                        out
186                    }
187                    Err(err) => vec![Err(err.into())],
188                }
189            })
190            .chain(base_iterator.map(|c| Ok(c)))
191            .collect::<Result<HashSet<_>>>(),
192        None => base_iterator.map(|c| Ok(c)).collect::<Result<HashSet<_>>>(),
193    }?;
194
195    debug!("\nAll requested components");
196    for c in all_components.iter() {
197        debug!("- {}", c);
198    }
199
200    graph.keep(&all_components);
201
202    info!("\nInstall Graph");
203    print_graph(&graph);
204
205    install_module_and_dependencies(&graph, &base_dir)?;
206    let installation = installation.or_else(|_| UnityInstallation::new(&base_dir))?;
207    let mut modules = match installation.get_modules() {
208        Err(_) => unity_release
209            .downloads
210            .first()
211            .cloned()
212            .map(|d| {
213                let mut modules = vec![];
214                for module in &d.modules {
215                   fetch_modules_from_release(&mut modules, module);
216                }
217                modules
218            })
219            .unwrap(),
220        Ok(m) => m,
221    };
222
223    for module in modules.iter_mut() {
224        if module.is_installed == false {
225            module.is_installed = all_components.contains(module.id());
226            trace!("module {} is installed", module.id());
227        }
228    }
229
230    write_modules_json(&installation, modules)?;
231
232    //write new api hub editor installation
233    if let Some(installation) = editor_installation {
234        let mut _editors = unity_hub::Editors::load().and_then(|mut editors| {
235            editors.add(&installation);
236            editors.flush()?;
237            Ok(())
238        });
239    }
240
241    Ok(installation)
242}
243
244fn fetch_modules_from_release(
245    modules: &mut Vec<Module>, module: &uvm_live_platform::Module,
246) {
247    modules.push(module.clone().into());
248    for sub_module in module.sub_modules() {
249        fetch_modules_from_release(modules, sub_module);
250    }
251}
252
253fn write_modules_json(
254    installation: &UnityInstallation,
255    modules: Vec<unity_hub::unity::hub::module::Module>,
256) -> io::Result<()> {
257    use console::style;
258    use std::fs::OpenOptions;
259    use std::io::Write;
260
261    let output_path = installation
262        .location()
263        .parent()
264        .unwrap()
265        .join("modules.json");
266    info!(
267        "{}",
268        style(format!("write {}", output_path.display())).green()
269    );
270    let mut f = OpenOptions::new()
271        .write(true)
272        .truncate(true)
273        .create(true)
274        .open(output_path)?;
275
276    let j = serde_json::to_string_pretty(&modules)?;
277    write!(f, "{}", j)?;
278    trace!("{}", j);
279    Ok(())
280}
281
282struct UnityComponent2<'a>(UnityComponent<'a>);
283
284impl<'a> Deref for UnityComponent2<'a> {
285    type Target = UnityComponent<'a>;
286
287    fn deref(&self) -> &Self::Target {
288        &self.0
289    }
290}
291
292impl<'a> DerefMut for UnityComponent2<'a> {
293    fn deref_mut(&mut self) -> &mut Self::Target {
294        &mut self.0
295    }
296}
297
298impl<'a> InstallManifest for UnityComponent2<'a> {
299    fn is_editor(&self) -> bool {
300        match self.0 {
301            UnityComponent::Editor(_) => true,
302            _ => false,
303        }
304    }
305    fn id(&self) -> &str {
306        match self.0 {
307            UnityComponent::Editor(_) => "Unity",
308            UnityComponent::Module(m) => m.id(),
309        }
310    }
311    fn install_size(&self) -> u64 {
312        let download_size = match self.0 {
313            UnityComponent::Editor(e) => e.download_size,
314            UnityComponent::Module(m) => m.download_size,
315        };
316        download_size.to_bytes() as u64
317    }
318
319    fn download_url(&self) -> &str {
320        match self.0 {
321            UnityComponent::Editor(e) => &e.release_file.url,
322            UnityComponent::Module(m) => &m.release_file().url,
323        }
324    }
325
326    //TODO find a way without clone
327    fn integrity(&self) -> Option<Integrity> {
328        match self.0 {
329            UnityComponent::Editor(e) => e.release_file.integrity.clone(),
330            UnityComponent::Module(m) => m.release_file().integrity.clone(),
331        }
332    }
333
334    fn install_rename_from_to<P: AsRef<Path>>(&self, base_path: P) -> Option<(PathBuf, PathBuf)> {
335        match self.0 {
336            UnityComponent::Editor(_) => None,
337            UnityComponent::Module(m) => {
338                if let Some(extracted_path_rename) = &m.extracted_path_rename() {
339                    Some((
340                        strip_unity_base_url(&extracted_path_rename.from, &base_path),
341                        strip_unity_base_url(&extracted_path_rename.to, &base_path),
342                    ))
343                } else {
344                    None
345                }
346            }
347        }
348    }
349
350    fn install_destination<P: AsRef<Path>>(&self, base_path: P) -> Option<PathBuf> {
351        match self.0 {
352            UnityComponent::Editor(_) => Some(base_path.as_ref().to_path_buf()),
353            UnityComponent::Module(m) => {
354                if let Some(destination) = &m.destination() {
355                    Some(strip_unity_base_url(destination, &base_path))
356                } else {
357                    None
358                }
359            }
360        }
361    }
362}
363
364fn strip_unity_base_url<P: AsRef<Path>, Q: AsRef<Path>>(path: P, base_dir: Q) -> PathBuf {
365    let path = path.as_ref();
366    base_dir
367        .as_ref()
368        .join(&path.strip_prefix(&UNITY_BASE_PATTERN).unwrap_or(path))
369}
370
371fn install_module_and_dependencies<'a, P: AsRef<Path>>(
372    graph: &'a InstallGraph<'a>,
373    base_dir: P,
374) -> Result<()> {
375    let base_dir = base_dir.as_ref();
376    for node in graph.topo().iter(graph.context()) {
377        if let Some(InstallStatus::Missing) = graph.install_status(node) {
378            let component = graph.component(node).unwrap();
379            let module = UnityComponent2(component);
380            let version = &graph.release().version;
381            let hash = &graph.release().short_revision;
382
383            info!("install {}", module.id());
384            info!("download installer for {}", module.id());
385
386            let loader = Loader::new(version, hash, &module);
387            let installer = loader
388                .download()
389                .map_err(|installer_err| LoadingInstallerFailed(installer_err))?;
390
391            info!("create installer for {}", component);
392            let installer = create_installer(base_dir, installer, &module)
393                .map_err(|installer_err| InstallerCreatedFailed(installer_err))?;
394
395            info!("install {}", component);
396            installer
397                .install()
398                .map_err(|installer_err| InstallFailed(module.id().to_string(), installer_err))?;
399        }
400    }
401
402    Ok(())
403}