1use std::{
2 collections::HashMap,
3 fs,
4 ops::Index,
5 path::{Component, Path, PathBuf},
6 str::FromStr,
7};
8
9use log::warn;
10
11use super::{
12 WorkMode,
13 external_libraries::{ExternalLibrary, read_external_libraries},
14 gobjects,
15};
16use crate::{
17 analysis::namespaces::{self, Namespace, NsId},
18 config::error::TomlHelper,
19 env::Env,
20 git::{repo_hash, repo_remote_url, toplevel},
21 library::{self, Library},
22 nameutil::set_crate_name_overrides,
23 version::Version,
24};
25
26fn normalize_path(path: impl AsRef<Path>) -> PathBuf {
30 let mut parts: Vec<Component<'_>> = vec![];
31
32 for component in path.as_ref().components() {
33 match (component, parts.last()) {
34 (Component::CurDir, _) | (Component::ParentDir, Some(Component::RootDir)) => {}
35 (Component::ParentDir, None | Some(Component::ParentDir)) => {
36 parts.push(Component::ParentDir);
37 }
38 (Component::ParentDir, Some(_)) => {
39 parts
40 .pop()
41 .expect("Cannot navigate outside of base directory!");
42 }
43 (c, _) => parts.push(c),
44 }
45 }
46 parts.iter().collect()
47}
48
49#[test]
50fn test_normalize_path() {
51 assert_eq!(normalize_path("foo/../bar").as_os_str(), "bar");
52 assert_eq!(normalize_path("foo/./bar").as_os_str(), "foo/bar");
53 assert_eq!(normalize_path("./foo").as_os_str(), "foo");
54 assert_eq!(
55 normalize_path("foo/../bar/baz/../qux").as_os_str(),
56 "bar/qux"
57 );
58 assert_eq!(
59 normalize_path("foo/bar/baz/../../qux").as_os_str(),
60 "foo/qux"
61 );
62 assert_eq!(normalize_path("/foo/../bar").as_os_str(), "/bar");
63 assert_eq!(normalize_path("/../bar").as_os_str(), "/bar");
64 assert_eq!(normalize_path("foo/../../bar").as_os_str(), "../bar");
65}
66
67#[derive(Debug)]
68pub struct GirVersion {
69 pub gir_dir: PathBuf,
70 hash: Option<String>,
71 url: Option<String>,
72}
73
74impl GirVersion {
75 fn new(gir_dir: impl AsRef<Path>) -> Self {
76 let gir_dir = normalize_path(gir_dir);
77 let is_submodule = toplevel(&gir_dir) != Path::new(".").canonicalize().ok();
78 Self {
79 hash: is_submodule.then(|| repo_hash(&gir_dir).unwrap_or_else(|| "???".to_string())),
80 url: is_submodule.then(|| repo_remote_url(&gir_dir)).flatten(),
81 gir_dir,
82 }
83 }
84
85 pub fn get_hash(&self) -> Option<&str> {
86 self.hash.as_deref()
87 }
88
89 pub fn get_repository_url(&self) -> Option<&str> {
90 self.url.as_deref()
91 }
92}
93
94#[derive(Debug)]
95pub struct Config {
96 pub work_mode: WorkMode,
97 pub girs_dirs: Vec<PathBuf>,
98 pub girs_version: Vec<GirVersion>,
100 pub library_name: String,
101 pub library_version: String,
102 pub target_path: PathBuf,
103 pub auto_path: PathBuf,
105 pub doc_target_path: PathBuf,
106 pub external_libraries: Vec<ExternalLibrary>,
107 pub objects: gobjects::GObjects,
108 pub min_cfg_version: Version,
109 pub make_backup: bool,
110 pub generate_safety_asserts: bool,
111 pub deprecate_by_min_version: bool,
112 pub show_statistics: bool,
113 pub concurrency: library::Concurrency,
114 pub single_version_file: Option<PathBuf>,
115 pub trust_return_value_nullability: bool,
116 pub disable_format: bool,
117 pub split_build_rs: bool,
118 pub extra_versions: Vec<Version>,
119 pub lib_version_overrides: HashMap<Version, Version>,
120 pub feature_dependencies: HashMap<Version, Vec<String>>,
121 pub external_docs_url: Option<String>,
125}
126
127impl Config {
128 pub fn new<'a, S, W>(
129 config_file: S,
130 work_mode: W,
131 girs_dirs: &[String],
132 library_name: S,
133 library_version: S,
134 target_path: S,
135 doc_target_path: S,
136 make_backup: bool,
137 show_statistics: bool,
138 disable_format: bool,
139 ) -> Result<Self, String>
140 where
141 S: Into<Option<&'a str>>,
142 W: Into<Option<WorkMode>>,
143 {
144 let config_file: PathBuf = match config_file.into() {
145 Some("") | None => "Gir.toml",
146 Some(a) => a,
147 }
148 .into();
149
150 let config_dir = match config_file.parent() {
151 Some(path) => path.into(),
152 None => PathBuf::new(),
153 };
154
155 let toml = match read_toml(&config_file) {
156 Ok(toml) => toml,
157 Err(e) => {
158 return Err(format!(
159 "Error while reading \"{}\": {}",
160 config_file.display(),
161 e
162 ));
163 }
164 };
165
166 let overrides = read_crate_name_overrides(&toml);
167 if !overrides.is_empty() {
168 set_crate_name_overrides(overrides);
169 }
170
171 let work_mode = match work_mode.into() {
172 Some(w) => w,
173 None => {
174 let s = match toml.lookup_str("options.work_mode", "No options.work_mode") {
175 Ok(s) => s,
176 Err(e) => {
177 return Err(format!(
178 "Invalid toml file \"{}\": {}",
179 config_file.display(),
180 e
181 ));
182 }
183 };
184 WorkMode::from_str(s)?
185 }
186 };
187
188 let mut girs_dirs: Vec<PathBuf> = girs_dirs
189 .iter()
190 .filter(|x| !x.is_empty())
191 .map(|x| PathBuf::from(&x))
192 .collect();
193 if girs_dirs.is_empty() {
194 let dirs =
195 toml.lookup_vec("options.girs_directories", "No options.girs_directories")?;
196 for dir in dirs {
197 let dir = dir.as_str().ok_or_else(|| {
198 "options.girs_dirs expected to be array of string".to_string()
199 })?;
200 girs_dirs.push(config_dir.join(dir));
201 }
202 }
203 let mut girs_version = girs_dirs.iter().map(GirVersion::new).collect::<Vec<_>>();
204 girs_version.sort_by(|a, b| a.gir_dir.partial_cmp(&b.gir_dir).unwrap());
205
206 let (library_name, library_version) = match (library_name.into(), library_version.into()) {
207 (Some(""), Some("")) | (None, None) => (
208 toml.lookup_str("options.library", "No options.library")?
209 .to_owned(),
210 toml.lookup_str("options.version", "No options.version")?
211 .to_owned(),
212 ),
213 (Some(""), Some(_)) | (Some(_), Some("")) | (None, Some(_)) | (Some(_), None) => {
214 return Err("Library and version can not be specified separately".to_owned());
215 }
216 (Some(a), Some(b)) => (a.to_owned(), b.to_owned()),
217 };
218
219 let target_path: PathBuf = match target_path.into() {
220 Some("") | None => {
221 let path = toml.lookup_str("options.target_path", "No target path specified")?;
222 config_dir.join(path)
223 }
224 Some(a) => a.into(),
225 };
226
227 let generate_builder: bool = toml
228 .lookup("options.generate_builder")
229 .and_then(|a| a.as_bool())
230 .unwrap_or(false);
231
232 let auto_path = match toml.lookup("options.auto_path") {
233 Some(p) => target_path.join(p.as_result_str("options.auto_path")?),
234 None if work_mode == WorkMode::Normal => target_path.join("src").join("auto"),
235 None => target_path.join("src"),
236 };
237 if work_mode == WorkMode::Normal && auto_path.exists() {
238 std::fs::remove_dir_all(&auto_path)
239 .map_err(|e| format!("remove_dir_all failed: {e:?}"))?;
240 }
241
242 let doc_target_path: PathBuf = match doc_target_path.into() {
243 Some("") | None => match toml.lookup("options.doc_target_path") {
244 Some(p) => config_dir.join(p.as_result_str("options.doc_target_path")?),
245 None => target_path.join("vendor.md"),
246 },
247 Some(p) => config_dir.join(p),
248 };
249
250 let concurrency = match toml.lookup("options.concurrency") {
251 Some(v) => v.as_result_str("options.concurrency")?.parse()?,
252 None => Default::default(),
253 };
254
255 let trust_return_value_nullability =
256 match toml.lookup("options.trust_return_value_nullability") {
257 Some(v) => v.as_result_bool("options.trust_return_value_nullability")?,
258 None => false,
259 };
260
261 let mut objects = toml
264 .lookup("object")
265 .map(|t| {
266 gobjects::parse_toml(
267 t,
268 concurrency,
269 generate_builder,
270 trust_return_value_nullability,
271 )
272 })
273 .unwrap_or_default();
274 gobjects::parse_status_shorthands(
275 &mut objects,
276 &toml,
277 concurrency,
278 generate_builder,
279 trust_return_value_nullability,
280 );
281
282 let external_libraries = read_external_libraries(&toml)?;
283
284 let min_cfg_version = match toml.lookup("options.min_cfg_version") {
285 Some(v) => v.as_result_str("options.min_cfg_version")?.parse()?,
286 None => Default::default(),
287 };
288
289 let generate_safety_asserts = match toml.lookup("options.generate_safety_asserts") {
290 Some(v) => v.as_result_bool("options.generate_safety_asserts")?,
291 None => false,
292 };
293
294 let deprecate_by_min_version = match toml.lookup("options.deprecate_by_min_version") {
295 Some(v) => v.as_result_bool("options.deprecate_by_min_version")?,
296 None => false,
297 };
298
299 let single_version_file = match toml.lookup("options.single_version_file") {
300 Some(v) => match v.as_result_bool("options.single_version_file") {
301 Ok(false) => None,
302 Ok(true) => Some(make_single_version_file(None, &target_path)),
303 Err(_) => match v.as_str() {
304 Some(p) => Some(make_single_version_file(Some(p), &target_path)),
305 None => return Err("single_version_file must be bool or string path".into()),
306 },
307 },
308 None => None,
309 };
310
311 let disable_format: bool = if disable_format {
312 true
313 } else {
314 match toml.lookup("options.disable_format") {
315 Some(v) => v.as_result_bool("options.disable_format")?,
316 None => true,
317 }
318 };
319
320 let split_build_rs = match toml.lookup("options.split_build_rs") {
321 Some(v) => v.as_result_bool("options.split_build_rs")?,
322 None => false,
323 };
324
325 let extra_versions = read_extra_versions(&toml)?;
326 let lib_version_overrides = read_lib_version_overrides(&toml)?;
327 let feature_dependencies = read_feature_dependencies(&toml)?;
328 let external_docs_url = read_external_docs_url(&toml)?;
329
330 Ok(Self {
331 work_mode,
332 girs_dirs,
333 girs_version,
334 library_name,
335 library_version,
336 target_path,
337 auto_path,
338 doc_target_path,
339 external_libraries,
340 objects,
341 min_cfg_version,
342 make_backup,
343 generate_safety_asserts,
344 deprecate_by_min_version,
345 show_statistics,
346 concurrency,
347 single_version_file,
348 trust_return_value_nullability,
349 disable_format,
350 split_build_rs,
351 extra_versions,
352 lib_version_overrides,
353 feature_dependencies,
354 external_docs_url,
355 })
356 }
357
358 pub fn library_full_name(&self) -> String {
359 format!("{}-{}", self.library_name, self.library_version)
360 }
361
362 pub fn filter_version(&self, version: Option<Version>) -> Option<Version> {
363 version.filter(|&v| v > self.min_cfg_version)
364 }
365
366 pub fn find_ext_library(&self, namespace: &Namespace) -> Option<&ExternalLibrary> {
367 self.external_libraries
368 .iter()
369 .find(|lib| lib.crate_name == namespace.crate_name)
370 }
371
372 pub fn min_required_version(&self, env: &Env, ns_id: Option<NsId>) -> Option<Version> {
373 let ns_id = ns_id.unwrap_or(namespaces::MAIN);
374 if ns_id == namespaces::MAIN {
375 Some(env.config.min_cfg_version)
376 } else {
377 let namespace = env.namespaces.index(ns_id);
378 self.find_ext_library(namespace)
379 .and_then(|lib| lib.min_version)
380 }
381 }
382
383 pub fn resolve_type_ids(&mut self, library: &Library) {
384 gobjects::resolve_type_ids(&mut self.objects, library);
385 }
386
387 pub fn check_disable_format(&mut self) {
388 if !self.disable_format && !crate::fmt::check_fmt() {
389 warn!("Formatter not found, options.disable_format set to true");
390 self.disable_format = true;
391 }
392 }
393}
394
395fn read_toml<P: AsRef<Path>>(filename: P) -> Result<toml::Value, String> {
396 if !filename.as_ref().is_file() {
397 return Err("Config don't exists or not file".to_owned());
398 }
399 let input = fs::read(&filename)
400 .map_err(|e| format!("Failed to read file \"{:?}\": {}", filename.as_ref(), e))?;
401
402 let input = String::from_utf8(input)
403 .map_err(|e| format!("File is not valid UTF-8 \"{:?}\": {}", filename.as_ref(), e))?;
404
405 toml::from_str(&input).map_err(|e| {
406 format!(
407 "Invalid toml format in \"{}\": {}",
408 filename.as_ref().display(),
409 e
410 )
411 })
412}
413
414fn make_single_version_file(configured: Option<&str>, target_path: &Path) -> PathBuf {
415 let file_dir = match configured {
416 None | Some("") => target_path.join("src").join("auto"),
417 Some(path) => target_path.join(path),
418 };
419
420 if file_dir.extension().is_some() {
421 file_dir
422 } else {
423 file_dir.join("versions.txt")
424 }
425}
426
427fn read_crate_name_overrides(toml: &toml::Value) -> HashMap<String, String> {
428 let mut overrides = HashMap::new();
429 if let Some(a) = toml
430 .lookup("crate_name_overrides")
431 .and_then(toml::Value::as_table)
432 {
433 for (key, value) in a {
434 if let Some(s) = value.as_str() {
435 overrides.insert(key.clone(), s.to_string());
436 }
437 }
438 };
439 overrides
440}
441
442fn read_extra_versions(toml: &toml::Value) -> Result<Vec<Version>, String> {
443 match toml.lookup("options.extra_versions") {
444 Some(a) => a
445 .as_result_vec("options.extra_versions")?
446 .iter()
447 .map(|v| {
448 v.as_str().ok_or_else(|| {
449 "options.extra_versions expected to be array of string".to_string()
450 })
451 })
452 .map(|s| s.and_then(str::parse))
453 .collect(),
454 None => Ok(Vec::new()),
455 }
456}
457
458fn read_lib_version_overrides(toml: &toml::Value) -> Result<HashMap<Version, Version>, String> {
459 let v = match toml.lookup("lib_version_overrides") {
460 Some(a) => a.as_result_vec("lib_version_overrides")?,
461 None => return Ok(Default::default()),
462 };
463
464 let mut map = HashMap::with_capacity(v.len());
465 for o in v {
466 let cfg = o
467 .lookup_str("version", "No version in lib_version_overrides")?
468 .parse()?;
469 let lib = o
470 .lookup_str("lib_version", "No lib_version in lib_version_overrides")?
471 .parse()?;
472 map.insert(cfg, lib);
473 }
474
475 Ok(map)
476}
477
478fn read_feature_dependencies(toml: &toml::Value) -> Result<HashMap<Version, Vec<String>>, String> {
479 let v = match toml.lookup("feature_dependencies") {
480 Some(a) => a.as_result_vec("feature_dependencies")?,
481 None => return Ok(Default::default()),
482 };
483
484 let mut map = HashMap::with_capacity(v.len());
485 for o in v {
486 let cfg = o
487 .lookup_str("version", "No version in feature_dependencies")?
488 .parse()?;
489 let dependencies: Result<Vec<String>, String> = o
490 .lookup_vec("dependencies", "No dependencies in feature_dependencies")?
491 .iter()
492 .map(|v| {
493 v.as_str()
494 .ok_or_else(|| {
495 "feature_dependencies.dependencies expected to be array of string"
496 .to_string()
497 })
498 .map(str::to_owned)
499 })
500 .collect();
501 map.insert(cfg, dependencies?);
502 }
503
504 Ok(map)
505}
506
507fn read_external_docs_url(toml: &toml::Value) -> Result<Option<String>, String> {
508 Ok(
509 if let Some(value) = toml.lookup("options.external_docs_url") {
510 let value = value.as_result_str("options.external_docs_url")?;
511 Some(value.to_string())
512 } else {
513 None
514 },
515 )
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521
522 #[test]
523 fn test_make_single_version_file() {
524 let target_path = Path::new("/tmp/glib");
525 assert_eq!(
526 make_single_version_file(None, target_path),
527 PathBuf::from("/tmp/glib/src/auto/versions.txt")
528 );
529 assert_eq!(
530 make_single_version_file(Some(""), target_path),
531 PathBuf::from("/tmp/glib/src/auto/versions.txt")
532 );
533 assert_eq!(
534 make_single_version_file(Some("src"), target_path),
535 PathBuf::from("/tmp/glib/src/versions.txt")
536 );
537 assert_eq!(
538 make_single_version_file(Some("src/vers.txt"), target_path),
539 PathBuf::from("/tmp/glib/src/vers.txt")
540 );
541 assert_eq!(
542 make_single_version_file(Some("."), target_path),
543 PathBuf::from("/tmp/glib/versions.txt")
544 );
545 assert_eq!(
546 make_single_version_file(Some("./_vers.dat"), target_path),
547 PathBuf::from("/tmp/glib/_vers.dat")
548 );
549 }
550}