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.and_then(|v| {
364 if v > self.min_cfg_version {
365 Some(v)
366 } else {
367 None
368 }
369 })
370 }
371
372 pub fn find_ext_library(&self, namespace: &Namespace) -> Option<&ExternalLibrary> {
373 self.external_libraries
374 .iter()
375 .find(|lib| lib.crate_name == namespace.crate_name)
376 }
377
378 pub fn min_required_version(&self, env: &Env, ns_id: Option<NsId>) -> Option<Version> {
379 let ns_id = ns_id.unwrap_or(namespaces::MAIN);
380 if ns_id == namespaces::MAIN {
381 Some(env.config.min_cfg_version)
382 } else {
383 let namespace = env.namespaces.index(ns_id);
384 self.find_ext_library(namespace)
385 .and_then(|lib| lib.min_version)
386 }
387 }
388
389 pub fn resolve_type_ids(&mut self, library: &Library) {
390 gobjects::resolve_type_ids(&mut self.objects, library);
391 }
392
393 pub fn check_disable_format(&mut self) {
394 if !self.disable_format && !crate::fmt::check_fmt() {
395 warn!("Formatter not found, options.disable_format set to true");
396 self.disable_format = true;
397 }
398 }
399}
400
401fn read_toml<P: AsRef<Path>>(filename: P) -> Result<toml::Value, String> {
402 if !filename.as_ref().is_file() {
403 return Err("Config don't exists or not file".to_owned());
404 }
405 let input = fs::read(&filename)
406 .map_err(|e| format!("Failed to read file \"{:?}\": {}", filename.as_ref(), e))?;
407
408 let input = String::from_utf8(input)
409 .map_err(|e| format!("File is not valid UTF-8 \"{:?}\": {}", filename.as_ref(), e))?;
410
411 toml::from_str(&input).map_err(|e| {
412 format!(
413 "Invalid toml format in \"{}\": {}",
414 filename.as_ref().display(),
415 e
416 )
417 })
418}
419
420fn make_single_version_file(configured: Option<&str>, target_path: &Path) -> PathBuf {
421 let file_dir = match configured {
422 None | Some("") => target_path.join("src").join("auto"),
423 Some(path) => target_path.join(path),
424 };
425
426 if file_dir.extension().is_some() {
427 file_dir
428 } else {
429 file_dir.join("versions.txt")
430 }
431}
432
433fn read_crate_name_overrides(toml: &toml::Value) -> HashMap<String, String> {
434 let mut overrides = HashMap::new();
435 if let Some(a) = toml
436 .lookup("crate_name_overrides")
437 .and_then(toml::Value::as_table)
438 {
439 for (key, value) in a {
440 if let Some(s) = value.as_str() {
441 overrides.insert(key.clone(), s.to_string());
442 }
443 }
444 };
445 overrides
446}
447
448fn read_extra_versions(toml: &toml::Value) -> Result<Vec<Version>, String> {
449 match toml.lookup("options.extra_versions") {
450 Some(a) => a
451 .as_result_vec("options.extra_versions")?
452 .iter()
453 .map(|v| {
454 v.as_str().ok_or_else(|| {
455 "options.extra_versions expected to be array of string".to_string()
456 })
457 })
458 .map(|s| s.and_then(str::parse))
459 .collect(),
460 None => Ok(Vec::new()),
461 }
462}
463
464fn read_lib_version_overrides(toml: &toml::Value) -> Result<HashMap<Version, Version>, String> {
465 let v = match toml.lookup("lib_version_overrides") {
466 Some(a) => a.as_result_vec("lib_version_overrides")?,
467 None => return Ok(Default::default()),
468 };
469
470 let mut map = HashMap::with_capacity(v.len());
471 for o in v {
472 let cfg = o
473 .lookup_str("version", "No version in lib_version_overrides")?
474 .parse()?;
475 let lib = o
476 .lookup_str("lib_version", "No lib_version in lib_version_overrides")?
477 .parse()?;
478 map.insert(cfg, lib);
479 }
480
481 Ok(map)
482}
483
484fn read_feature_dependencies(toml: &toml::Value) -> Result<HashMap<Version, Vec<String>>, String> {
485 let v = match toml.lookup("feature_dependencies") {
486 Some(a) => a.as_result_vec("feature_dependencies")?,
487 None => return Ok(Default::default()),
488 };
489
490 let mut map = HashMap::with_capacity(v.len());
491 for o in v {
492 let cfg = o
493 .lookup_str("version", "No version in feature_dependencies")?
494 .parse()?;
495 let dependencies: Result<Vec<String>, String> = o
496 .lookup_vec("dependencies", "No dependencies in feature_dependencies")?
497 .iter()
498 .map(|v| {
499 v.as_str()
500 .ok_or_else(|| {
501 "feature_dependencies.dependencies expected to be array of string"
502 .to_string()
503 })
504 .map(str::to_owned)
505 })
506 .collect();
507 map.insert(cfg, dependencies?);
508 }
509
510 Ok(map)
511}
512
513fn read_external_docs_url(toml: &toml::Value) -> Result<Option<String>, String> {
514 Ok(
515 if let Some(value) = toml.lookup("options.external_docs_url") {
516 let value = value.as_result_str("options.external_docs_url")?;
517 Some(value.to_string())
518 } else {
519 None
520 },
521 )
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
529 fn test_make_single_version_file() {
530 let target_path = Path::new("/tmp/glib");
531 assert_eq!(
532 make_single_version_file(None, target_path),
533 PathBuf::from("/tmp/glib/src/auto/versions.txt")
534 );
535 assert_eq!(
536 make_single_version_file(Some(""), target_path),
537 PathBuf::from("/tmp/glib/src/auto/versions.txt")
538 );
539 assert_eq!(
540 make_single_version_file(Some("src"), target_path),
541 PathBuf::from("/tmp/glib/src/versions.txt")
542 );
543 assert_eq!(
544 make_single_version_file(Some("src/vers.txt"), target_path),
545 PathBuf::from("/tmp/glib/src/vers.txt")
546 );
547 assert_eq!(
548 make_single_version_file(Some("."), target_path),
549 PathBuf::from("/tmp/glib/versions.txt")
550 );
551 assert_eq!(
552 make_single_version_file(Some("./_vers.dat"), target_path),
553 PathBuf::from("/tmp/glib/_vers.dat")
554 );
555 }
556}