Skip to main content

i_slint_compiler/
fileaccess.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use std::borrow::Cow;
5use std::fs;
6
7#[derive(Clone)]
8pub struct VirtualFile {
9    pub canon_path: std::path::PathBuf,
10    pub builtin_contents: Option<&'static [u8]>,
11}
12
13impl VirtualFile {
14    pub fn read(&self) -> Cow<'static, [u8]> {
15        match self.builtin_contents {
16            Some(static_data) => Cow::Borrowed(static_data),
17            None => Cow::Owned(std::fs::read(&self.canon_path).unwrap()),
18        }
19    }
20
21    pub fn is_builtin(&self) -> bool {
22        self.builtin_contents.is_some()
23    }
24}
25
26pub fn styles() -> Vec<&'static str> {
27    builtin_library::styles()
28}
29
30pub fn load_file(path: &std::path::Path) -> Option<VirtualFile> {
31    match path.strip_prefix("builtin:/") {
32        Ok(builtin_path) => builtin_library::load_builtin_file(builtin_path),
33        Err(_) => path.exists().then(|| {
34            let path =
35                crate::pathutils::join(&std::env::current_dir().ok().unwrap_or_default(), path)
36                    .unwrap_or_else(|| path.to_path_buf());
37            VirtualFile { canon_path: crate::pathutils::clean_path(&path), builtin_contents: None }
38        }),
39    }
40}
41
42#[test]
43fn test_load_file() {
44    let builtin = load_file(&std::path::PathBuf::from(
45        "builtin:/foo/../common/./MadeWithSlint-logo-dark.svg",
46    ))
47    .unwrap();
48    assert!(builtin.is_builtin());
49    assert_eq!(
50        builtin.canon_path,
51        std::path::PathBuf::from("builtin:/common/MadeWithSlint-logo-dark.svg")
52    );
53
54    let dir = std::env::var_os("CARGO_MANIFEST_DIR").unwrap().to_string_lossy().to_string();
55    let dir_path = std::path::PathBuf::from(dir);
56
57    let non_existing = dir_path.join("XXXCargo.tomlXXX");
58    assert!(load_file(&non_existing).is_none());
59
60    assert!(dir_path.exists()); // We need some existing path for all the rest
61
62    let cargo_toml = dir_path.join("Cargo.toml");
63    let abs_cargo_toml = load_file(&cargo_toml).unwrap();
64    assert!(!abs_cargo_toml.is_builtin());
65    assert!(crate::pathutils::is_absolute(&abs_cargo_toml.canon_path));
66    assert!(abs_cargo_toml.canon_path.exists());
67
68    let current = std::env::current_dir().unwrap();
69    assert!(current.ends_with("compiler")); // This test is run in .../internal/compiler
70
71    let cargo_toml = std::path::PathBuf::from("./tests/../Cargo.toml");
72    let rel_cargo_toml = load_file(&cargo_toml).unwrap();
73    assert!(!rel_cargo_toml.is_builtin());
74    assert!(crate::pathutils::is_absolute(&rel_cargo_toml.canon_path));
75    assert!(rel_cargo_toml.canon_path.exists());
76
77    assert_eq!(abs_cargo_toml.canon_path, rel_cargo_toml.canon_path);
78}
79
80/// Writes a buffer into a file, but only if the content differs from the file content
81///
82/// Tries to read the destination file first, and only writes the new content if
83/// the file didn't exist or the file content differs from the content to write.
84/// This avoids unnecessary mtime modification of the file, which caused build
85/// systems like Ninja to rebuild other things even though the outpuf of
86/// slint-compiler didn't change.
87pub fn write_file_if_changed(path: &std::path::Path, content: &[u8]) -> std::io::Result<()> {
88    if fs::read(path).is_ok_and(|existing| existing == content) {
89        return Ok(());
90    }
91    fs::write(path, content)
92}
93
94mod builtin_library {
95    include!(env!("SLINT_WIDGETS_LIBRARY"));
96
97    pub type BuiltinDirectory<'a> = [&'a BuiltinFile<'a>];
98
99    pub struct BuiltinFile<'a> {
100        pub path: &'a str,
101        pub contents: &'static [u8],
102    }
103
104    use super::VirtualFile;
105
106    const ALIASES: &[(&str, &str)] = &[
107        ("cosmic-light", "cosmic"),
108        ("cosmic-dark", "cosmic"),
109        ("fluent-light", "fluent"),
110        ("fluent-dark", "fluent"),
111        ("material-light", "material"),
112        ("material-dark", "material"),
113        ("cupertino-light", "cupertino"),
114        ("cupertino-dark", "cupertino"),
115    ];
116
117    pub(crate) fn styles() -> Vec<&'static str> {
118        widget_library()
119            .iter()
120            .filter_map(|(style, directory)| {
121                if directory.iter().any(|f| f.path == "std-widgets.slint") {
122                    Some(*style)
123                } else {
124                    None
125                }
126            })
127            .chain(ALIASES.iter().map(|x| x.0))
128            .collect()
129    }
130
131    pub(crate) fn load_builtin_file(builtin_path: &std::path::Path) -> Option<VirtualFile> {
132        let mut components = Vec::new();
133        for part in builtin_path.iter() {
134            if part == ".." {
135                components.pop();
136            } else if part != "." {
137                components.push(part);
138            }
139        }
140        if let Some(f) = components.first_mut()
141            && let Some((_, x)) = ALIASES.iter().find(|x| x.0 == *f)
142        {
143            *f = std::ffi::OsStr::new(x);
144        }
145        if let &[folder, file] = components.as_slice() {
146            let library = widget_library().iter().find(|x| x.0 == folder)?.1;
147            library.iter().find_map(|builtin_file| {
148                if builtin_file.path == file {
149                    Some(VirtualFile {
150                        canon_path: std::path::PathBuf::from(format!(
151                            "builtin:/{}/{}",
152                            folder.to_str().unwrap(),
153                            builtin_file.path
154                        )),
155                        builtin_contents: Some(builtin_file.contents),
156                    })
157                } else {
158                    None
159                }
160            })
161        } else {
162            None
163        }
164    }
165}