-
Notifications
You must be signed in to change notification settings - Fork 3
/
build.rs
176 lines (157 loc) · 6.02 KB
/
build.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use std::{
collections::HashMap,
env,
ffi::OsStr,
io,
path::{Path, PathBuf},
};
use gvdb::gresource::{GResourceBuilder, GResourceFileData, PreprocessOptions};
const CONFIG_FILE: &str = "icons.toml";
const GENERAL_PREFIX: &str = "/org/gtkrs/icons/scalable/actions/";
const SHIPPED_ICONS_PATH: &str = "icons";
const TARGET_FILE: &str = "resources.gresource";
const CONSTANTS_FILE: &str = "icon_names.rs";
#[derive(Default, serde::Deserialize)]
struct Config {
app_id: Option<String>,
base_resource_path: Option<String>,
icon_folder: Option<String>,
icons: Option<Vec<String>>,
}
impl Config {
fn load(dir: &str) -> Result<Self, io::Error> {
let config_path: PathBuf = [dir, CONFIG_FILE].iter().collect();
let config_file = std::fs::read_to_string(config_path)?;
Ok(toml::from_str(&config_file).expect("Couldn't parse icon config file"))
}
}
fn path_to_icon_name(string: &OsStr) -> String {
match string.to_str() {
Some(string) => {
if string.ends_with(".svg") {
string
.trim_end_matches("-symbolic.svg")
.trim_end_matches(".svg")
.to_owned()
} else {
panic!("Found non-icon file `{string}`");
}
}
None => panic!("Failed to convert file name `{string:?}` to string"),
}
}
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let mut manifest_dir = Path::new(&out_dir).canonicalize().unwrap();
eprintln!("Canonical manifest dir: {manifest_dir:?}");
let (config, config_dir) = if cfg!(docsrs) {
if let Ok(source_dir) = env::var("SOURCE_DIR") {
(Config::load(&source_dir).unwrap_or_default(), source_dir)
} else {
(Config::default(), "".into())
}
} else {
// Try finding the target directory which is just below the manifest directory
// of the user.
// Unfortunately, the CARGO_MANIFEST_DIR env var passed by cargo always points
// to this crate, so we wouldn't find the users config file this way.
while !manifest_dir.join("Cargo.toml").exists() {
if !manifest_dir.pop() {
panic!("Couldn't find your manifest directory");
}
}
let config_dir = manifest_dir
.to_str()
.expect("Couldn't convert manifest directory to string")
.to_owned();
(
Config::load(&config_dir).expect("Couldn't find `icons.toml` next to `Cargo.toml`"),
config_dir,
)
};
eprintln!("Canonical config dir: {config_dir:?}");
println!("cargo:rerun-if-changed={config_dir}/icons.toml");
let mut icons: HashMap<String, PathBuf> = HashMap::new();
if let Some(folder) = &config.icon_folder {
println!("cargo:rerun-if-changed={folder}");
let custom_icons_path: PathBuf = [&config_dir, folder].iter().collect();
let read_dir = std::fs::read_dir(custom_icons_path)
.expect("Couldn't open icon path specified in config (relative to the manifest)");
for entry in read_dir {
let entry = entry.unwrap();
let icon_name = path_to_icon_name(&entry.file_name());
if icons.insert(icon_name.clone(), entry.path()).is_some() {
panic!("Icon with name `{icon_name}` exists twice")
}
}
}
if let Some(icon_names) = config.icons {
let dirs =
std::fs::read_dir(SHIPPED_ICONS_PATH).expect("Couldn't open folder of shipped icons");
let dirs: Vec<_> = dirs
.map(|entry| {
let entry = entry.expect("Couldn't open directories in shipped icon folder");
entry.path()
})
.collect();
'outer: for icon_name in icon_names {
for dir in &dirs {
let icon_file_name = format!("{icon_name}-symbolic.svg");
let icon_path = dir.join(icon_file_name);
if icon_path.exists() {
if icons.insert(icon_name.clone(), icon_path).is_some() {
panic!("Icon with name `{icon_name}` exists twice")
}
continue 'outer;
}
}
panic!("Icon {icon_name} not found in shipped icons");
}
}
let prefix = if let Some(base_resource_path) = &config.base_resource_path {
format!("{}icons/scalable/actions/", base_resource_path)
} else if let Some(app_id) = &config.app_id {
format!("/{}/icons/scalable/actions/", app_id.replace('.', "/"))
} else {
GENERAL_PREFIX.into()
};
// Generate resource bundle
let resources = icons
.iter()
.map(|(icon, path)| {
GResourceFileData::from_file(
[&prefix, icon, "-symbolic.svg"].into_iter().collect(),
path,
true,
&PreprocessOptions::xml_stripblanks(),
)
.unwrap()
})
.collect();
let data = GResourceBuilder::from_file_data(resources)
.build()
.expect("Failed to build resource bundle");
std::fs::write(Path::new(&out_dir).join(TARGET_FILE), data).unwrap();
// Create file that contains the icon names as constants
let constants: String = icons
.iter()
.map(|(icon_name, icon_path)| {
let const_name = icon_name.to_uppercase().replace('-', "_");
format!(
"
/// Icon name of the icon `{icon_name}`, found at `{icon_path:?}`.
pub const {const_name}: &str = \"{icon_name}\";
"
)
})
.chain([format!(
"pub(crate) const APP_ID: &str = \"{}\";",
config.app_id.unwrap_or_default()
)])
.chain([format!(
"pub(crate) const BASE_RESOURCE_PATH: &str = \"{}\";",
config.base_resource_path.unwrap_or_default()
)])
.collect();
std::fs::write(Path::new(&out_dir).join(CONSTANTS_FILE), constants).unwrap();
}