playdate_build_utils/toolchain/
sdk.rs1use std::borrow::Cow;
4use std::io::Error;
5use std::io::ErrorKind;
6use std::path::Path;
7use std::path::PathBuf;
8
9use crate::consts::SDK_ENV_VAR;
10
11
12pub struct Sdk {
13 path: PathBuf,
14}
15
16
17impl Sdk {
19 pub fn path(&self) -> &Path { &self.path }
20 pub fn c_api(&self) -> PathBuf { self.path.join("C_API") }
21 pub fn build_support(&self) -> BuildSupport { BuildSupport { path: self.c_api().join("buildsupport").into() } }
22 pub fn version_file(&self) -> PathBuf { self.path.join("VERSION.txt") }
23
24 pub fn bin(&self) -> PathBuf { self.path.join("bin") }
25
26 pub fn pdc(&self) -> PathBuf {
27 #[cfg(unix)]
28 const PDC: &str = "pdc";
29 #[cfg(windows)]
30 const PDC: &'static str = "PDC.EXE";
31 self.bin().join(PDC)
32 }
33
34 pub fn pdutil(&self) -> PathBuf {
35 #[cfg(unix)]
36 const PDUTIL: &str = "pdutil";
37 #[cfg(windows)]
38 const PDUTIL: &'static str = "PDUTIL.EXE";
39 self.bin().join(PDUTIL)
40 }
41}
42
43
44impl Sdk {
46 pub fn try_new() -> Result<Self, Error> {
48 let try_with = move |f: fn() -> Result<Self, Error>| {
49 let result = f();
50 match result {
51 Err(ref result_error) => crate::error!("{result_error}"),
52 Ok(ref sdk) => {
53 crate::info!("Found SDK in {}", sdk.path().display())
54 },
55 }
56 result
57 };
58
59 let res = try_with(Self::try_from_default_env).or_else(|_| try_with(Self::try_from_default_config));
60 #[cfg(unix)]
61 let res = res.or_else(|_| try_with(Self::try_xdg_unix_path));
62
63 #[cfg(windows)]
64 let res = res.or_else(|_| try_with(Self::try_windows_registry));
65
66 res.or_else(|_| try_with(Self::try_from_default_path))
67 }
68
69 pub fn try_new_exact<P: Into<PathBuf>>(root: P) -> Result<Self, Error> {
71 let path = root.into();
72 let err = |p: &Path| {
73 Error::new(
74 ErrorKind::InvalidInput,
75 format!("Invalid SDK path '{}'", p.display()),
76 )
77 };
78
79 if path.exists() && path.is_dir() {
80 let sdk = Self { path };
81 if sdk.build_support().link_map().exists() {
82 Ok(sdk)
83 } else {
84 Err(err(&sdk.path))
85 }
86 } else {
87 Err(err(&path))
88 }
89 }
90
91 #[cfg(windows)]
92 pub fn try_windows_registry() -> Result<Self, Error> {
93 let key: String = windows_registry::CURRENT_USER.open(r#"Software\\PlaydateSDK"#)?
94 .get_string("")?;
95 return Self::try_new_exact(key);
96 }
97
98 pub fn try_from_default_env() -> Result<Self, Error> {
100 let sdk = std::env::var_os(SDK_ENV_VAR).map(PathBuf::from)
101 .map(Self::try_new_exact);
102 sdk.ok_or(Error::new(ErrorKind::NotFound, format!("Missed env {SDK_ENV_VAR}")))?
103 }
104
105 pub fn try_from_default_config() -> Result<Self, Error> {
107 let cfg = config::Cfg::try_default()?;
108 let path = cfg.sdk_path()
109 .ok_or(Error::new(ErrorKind::InvalidInput, "SDK path is not set"))?;
110 Self::try_new_exact(path)
111 }
112
113 pub fn try_from_default_path() -> Result<Self, Error> {
115 #[cfg(unix)]
116 const SDK_HOME_DIR: &str = "Developer";
117 #[cfg(windows)]
118 const SDK_HOME_DIR: &'static str = "Documents";
119
120 let home = utils::home_dir()?;
121 Self::try_new_exact(home.join(SDK_HOME_DIR).join("PlaydateSDK"))
122 }
123
124 #[cfg(unix)]
125 pub fn try_xdg_unix_path() -> Result<Self, Error> {
126 const XDG_CONFIG_DATA_ENV: &str = "XDG_CONFIG_DATA";
127
128 let xdg_data_path = match std::env::var(XDG_CONFIG_DATA_ENV) {
129 Ok(ref variable) => PathBuf::from(variable),
130 Err(_) => utils::home_dir()?.join(".local").join("share"),
131 };
132
133 Self::try_new_exact(xdg_data_path.join("playdate-sdk"))
134 }
135}
136
137
138impl Sdk {
140 pub fn read_version(&self) -> Result<String, Error> {
141 let value = std::fs::read_to_string(self.version_file())?;
142 Ok(value)
143 }
144}
145
146
147pub struct BuildSupport<'t> {
148 path: Cow<'t, Path>,
149}
150
151
152impl<'t> BuildSupport<'t> {
153 pub fn setup(&self) -> Cow<'t, Path> { self.path.join("setup.c").into() }
154 pub fn link_map(&self) -> Cow<'t, Path> { self.path.join("link_map.ld").into() }
155}
156
157
158mod utils {
159 use super::Error;
160 use super::ErrorKind;
161 use std::path::PathBuf;
162
163 pub fn home_dir() -> Result<PathBuf, Error> {
164 dirs::home_dir().ok_or(Error::new(ErrorKind::InvalidInput, "Could not find home dir"))
165 }
166}
167
168
169mod config {
170 use super::Error;
171 use super::utils;
172 use std::collections::HashMap;
173 use std::path::PathBuf;
174 use std::str::FromStr;
175
176 #[cfg(unix)]
177 const DEFAULT_XDG_CONFIG_DIR: &str = ".config";
178 #[cfg(unix)]
179 const XDG_CONFIG_HOME_ENV: &str = "XDG_CONFIG_HOME";
180 const CFG_DIR: &str = ".Playdate";
181 const CFG_FILENAME: &str = "config";
182 const CFG_KEY_SDK_ROOT: &str = "SDKRoot";
183
184 pub(super) struct Cfg(HashMap<String, String>);
185
186 impl Cfg {
187 pub fn try_default() -> Result<Self, Error> {
188 fn find_config_folder() -> Result<PathBuf, Error> {
189 #[cfg(unix)]
190 {
191 let xdg_cfg_path = match std::env::var(XDG_CONFIG_HOME_ENV) {
192 Ok(ref variable) => PathBuf::from(variable),
193 Err(_) => utils::home_dir()?.join(DEFAULT_XDG_CONFIG_DIR),
194 }.join(CFG_DIR)
195 .join(CFG_FILENAME);
196
197 if xdg_cfg_path.exists() {
198 return Ok(xdg_cfg_path);
199 }
200 }
202
203 let cfg_path = utils::home_dir()?.join(CFG_DIR).join(CFG_FILENAME);
204
205 if cfg_path.exists() {
206 return Ok(cfg_path);
207 }
208
209 Err(Error::new(std::io::ErrorKind::NotFound, "Config file not found"))
210 }
211
212 std::fs::read_to_string(find_config_folder()?)?.parse()
213 }
214
215 pub fn sdk_path(&self) -> Option<PathBuf> { self.0.get(CFG_KEY_SDK_ROOT).map(PathBuf::from) }
216 }
217
218 impl FromStr for Cfg {
219 type Err = Error;
220
221 fn from_str(s: &str) -> Result<Self, Self::Err> {
222 Ok(Self(
223 s.trim()
224 .lines()
225 .filter_map(|line| line.split_once('\t').map(|(k, v)| (k.to_owned(), v.to_owned())))
226 .collect(),
227 ))
228 }
229 }
230
231
232 #[cfg(test)]
233 mod tests {
234 use super::*;
235
236 #[test]
237 fn parse() {
238 let path = "/path/PlaydateSDK-dir";
239 let cfg: Cfg = format!("{k}\t{v}\n", k = CFG_KEY_SDK_ROOT, v = path).parse()
240 .unwrap();
241 assert_eq!(cfg.sdk_path(), Some(PathBuf::from(path)));
242 }
243 }
244}
245
246
247#[cfg(test)]
249mod tests {
250 use super::*;
251
252
253 #[test]
254 fn sdk() { Sdk::try_new().unwrap(); }
255}