1mod manifest;
11
12use std::collections::HashMap;
13use std::env;
14use std::fs;
15use std::io;
16use std::path::PathBuf;
17
18use once_cell::sync::OnceCell;
19use serde::de::DeserializeSeed as _;
20use thiserror::Error;
21
22use crate::manifest::ResourcesMap;
23
24#[derive(Debug, Error)]
25pub enum BuckResourcesError {
26 #[error("Failed to look up our own executable path")]
27 NoCurrentExe { source: io::Error },
28
29 #[error(
30 "Failed to read manifest file: `{manifest_path}`. \
31 Are you maybe running `buck1`? `rust_binary` only supports `resources` under `buck2`!"
32 )]
33 ReadFailed {
34 manifest_path: PathBuf,
35 source: io::Error,
36 },
37
38 #[error("Failed to parse manifest file: `{manifest_path}`")]
39 ParsingFailed {
40 manifest_path: PathBuf,
41 source: serde_json::Error,
42 },
43
44 #[error("No resource named `{name}` found in manifest file: `{manifest_path}`")]
45 NoSuchResource {
46 name: String,
47 manifest_path: PathBuf,
48 },
49
50 #[error(
51 "Resource `{name}` points to invalid path `{resource_path}` in manifest `{manifest_path}`"
52 )]
53 BadResourcePath {
54 name: String,
55 resource_path: PathBuf,
56 manifest_path: PathBuf,
57 source: io::Error,
58 },
59}
60
61pub fn get<S>(name: S) -> Result<PathBuf, BuckResourcesError>
81where
82 S: AsRef<str>,
83{
84 static MANIFEST: OnceCell<(PathBuf, HashMap<String, PathBuf>)> = OnceCell::new();
85
86 let (manifest_path, manifest) = MANIFEST.get_or_try_init(|| {
87 let manifest_path = match env::current_exe() {
88 Ok(mut value) => {
89 value.as_mut_os_string().push(".resources.json");
90 value
91 }
92 Err(source) => {
93 return Err(BuckResourcesError::NoCurrentExe { source });
94 }
95 };
96
97 let data = match fs::read(&manifest_path) {
98 Ok(x) => x,
99 Err(source) => {
100 return Err(BuckResourcesError::ReadFailed {
101 manifest_path,
102 source,
103 });
104 }
105 };
106
107 let base_dir = manifest_path.parent().unwrap_or(&manifest_path);
108
109 let deserializer = &mut serde_json::Deserializer::from_slice(&data);
110 let manifest = match ResourcesMap::new(base_dir).deserialize(deserializer) {
111 Ok(x) => x,
112 Err(source) => {
113 return Err(BuckResourcesError::ParsingFailed {
114 manifest_path,
115 source,
116 });
117 }
118 };
119
120 Ok((manifest_path, manifest))
121 })?;
122
123 if let Some(resource_path) = manifest.get(name.as_ref()) {
124 dunce::canonicalize(resource_path).map_err(|source| BuckResourcesError::BadResourcePath {
125 name: name.as_ref().to_owned(),
126 resource_path: resource_path.clone(),
127 manifest_path: manifest_path.clone(),
128 source,
129 })
130 } else {
131 Err(BuckResourcesError::NoSuchResource {
132 name: name.as_ref().to_owned(),
133 manifest_path: manifest_path.clone(),
134 })
135 }
136}