1use std::{path::PathBuf, process::Command};
2
3use anyhow::Result;
4use home::home_dir;
5use serde::{Deserialize, Serialize};
6
7use super::overrides::{OverrideDatabase, Overrides};
8
9#[derive(Debug, Serialize, Deserialize)]
10pub enum LibKind {
11 #[serde(alias = "system")]
12 System,
13 #[serde(alias = "git")]
14 Git,
15}
16
17#[derive(Debug, Serialize, Deserialize)]
18pub struct Lib {
19 #[serde(default = "default_kind")]
20 pub kind: LibKind,
21 pub repo: Option<String>,
22 pub version: Option<String>,
23
24 pub overrides: Option<Overrides>,
25 }
35
36impl Lib {
37 pub fn headers(&self, name: &str, override_db: &OverrideDatabase) -> Result<String> {
72 match self.kind {
73 LibKind::System => {
74 let output = Command::new("pkg-config")
75 .arg(name)
76 .arg("--cflags")
77 .output()?;
78
79 Ok(String::from_utf8(output.stdout)?.trim_end().to_string())
80 }
81 LibKind::Git => {
82 let overrides = override_db.get(name);
83 let include_dir = if let Some(overrides) = overrides {
84 overrides
85 .include_dir
86 .as_ref()
87 .map(|include_dir| include_dir.to_string())
88 } else {
89 None
90 };
91 let include_dir = match include_dir {
92 Some(v) => v,
93 None => "build/include".into(),
94 };
95 Ok(format!(
96 "-I{}",
97 self.pathify_repo()?.join(include_dir).display(),
98 ))
99 }
100 }
101 }
102
103 pub fn lib_links(&self, name: &str, override_db: &OverrideDatabase) -> Result<String> {
104 match self.kind {
105 LibKind::System => {
106 let output = Command::new("pkg-config")
107 .arg(name)
108 .args(["--libs", "--static"])
109 .output()?;
110
111 Ok(String::from_utf8(output.stdout)?.trim_end().to_string())
112 }
113 LibKind::Git => {
114 let overrides = override_db.get(name);
115 let lib_dir = if let Some(overrides) = overrides {
116 overrides
117 .lib_dir
118 .as_ref()
119 .map(|include_dir| include_dir.to_string())
120 } else {
121 None
122 };
123 let lib_dir = match lib_dir {
124 Some(v) => v,
125 None => "build/lib".into(),
126 };
127 Ok(format!(
128 "-L{} -l{}",
129 self.pathify_repo()?.join(lib_dir).display(),
130 name,
131 ))
132 }
133 }
134 }
135
136 pub fn normalize_repo(&self) -> Option<String> {
137 match &self.repo {
138 Some(repo) => {
139 let mut new_repo = repo.clone();
140 if !repo.starts_with("https://") {
141 new_repo = format!("https://{}", new_repo);
142 }
143 if !new_repo.ends_with(".git") {
144 new_repo = format!("{}.git", new_repo)
145 }
146 Some(new_repo)
147 }
148 None => None,
149 }
150 }
151
152 pub fn directify_repo(&self) -> Option<String> {
153 match &self.repo {
154 Some(repo) => {
155 let new_repo = repo
156 .trim_start_matches("https://")
157 .trim_end_matches(".git")
158 .replace("/", "-")
159 .to_string();
160 Some(new_repo)
161 }
162 None => None,
163 }
164 }
165
166 pub fn pathify_repo_no_version(&self) -> Result<PathBuf, LibPathificationError> {
167 let home = home_dir().ok_or(LibPathificationError::HomeDirMissing)?;
168 let lib_dir = self
169 .directify_repo()
170 .ok_or(LibPathificationError::RepoUriMissing)?;
171
172 Ok(home.join(".bricks").join("libs").join(lib_dir))
173 }
174
175 pub fn pathify_repo(&self) -> Result<PathBuf, LibPathificationError> {
176 let home = home_dir().ok_or(LibPathificationError::HomeDirMissing)?;
177 let lib_dir = self
178 .directify_repo()
179 .ok_or(LibPathificationError::RepoUriMissing)?;
180 let version = self
181 .version
182 .as_ref()
183 .ok_or(LibPathificationError::VersionMissing)?;
184
185 Ok(home
186 .join(".bricks")
187 .join("libs")
188 .join(lib_dir)
189 .join(version))
190 }
191}
192
193#[derive(thiserror::Error, Debug)]
194pub enum LibPathificationError {
195 #[error("unable to get the home directory")]
196 HomeDirMissing,
197 #[error("lib is missing the `repo` property")]
198 RepoUriMissing,
199 #[error("lib is missing the `version` property")]
200 VersionMissing,
201}
202
203#[cfg(test)]
204mod tests {
205 use std::path::Path;
206
207 use super::*;
208
209 fn basic_lib() -> Lib {
210 Lib {
211 kind: LibKind::Git,
212 repo: Some("github.com/Tesohh/strings.git".to_string()),
213 version: Some("2020".to_string()),
214 overrides: None,
215
216 cflags: "".into(),
217 ldflags: "".into(),
218 macos: None,
219 windows: None,
220 linux: None,
221 }
222 }
223
224 #[test]
225 fn no_repo_should_return_none() {
226 let mut no_repo_lib = basic_lib();
227 no_repo_lib.repo = None;
228
229 assert_eq!(no_repo_lib.normalize_repo(), None);
230 assert_eq!(no_repo_lib.directify_repo(), None);
231 assert!(no_repo_lib.pathify_repo().is_err());
232 }
233
234 #[test]
235 fn repo_normalization() {
236 let mut lib = basic_lib();
237 lib.repo = Some("github.com/Tesohh/strings.git".to_string());
238
239 assert_eq!(
240 lib.normalize_repo(),
241 Some("https://github.com/Tesohh/strings.git".into())
242 );
243
244 lib.repo = Some("https://github.com/Tesohh/strings".into());
245 assert_eq!(
246 lib.normalize_repo(),
247 Some("https://github.com/Tesohh/strings.git".into())
248 );
249
250 lib.repo = Some("github.com/Tesohh/strings".into());
251 assert_eq!(
252 lib.normalize_repo(),
253 Some("https://github.com/Tesohh/strings.git".into())
254 );
255 }
256
257 #[test]
258 fn repo_directification() {
259 let lib = basic_lib();
260
261 assert_eq!(
262 lib.directify_repo(),
263 Some("github.com-Tesohh-strings".to_string())
264 );
265 }
266
267 #[test]
268 fn repo_pathification() {
269 let lib = basic_lib();
270
271 let path = lib.pathify_repo().unwrap();
272
273 assert_eq!(
274 path,
275 Path::new(&home_dir().unwrap())
276 .join(".bricks")
277 .join("libs")
278 .join("github.com-Tesohh-strings")
279 .join("2020")
280 .to_path_buf()
281 );
282 }
283
284 #[test]
285 fn git_headers_and_lib_links() {
286 let lib = basic_lib();
287 let override_db = OverrideDatabase::new();
288 assert_eq!(
289 lib.headers("strings", &override_db).unwrap(),
290 "-I".to_string()
291 + &Path::new(&home_dir().unwrap())
292 .join(".bricks/libs")
293 .join("github.com-Tesohh-strings/2020")
294 .join("build/include")
295 .display()
296 .to_string()
297 );
298 assert_eq!(
299 lib.lib_links("strings", &override_db).unwrap(),
300 "-L".to_string()
301 + &Path::new(&home_dir().unwrap())
302 .join(".bricks/libs")
303 .join("github.com-Tesohh-strings/2020")
304 .join("build/lib")
305 .display()
306 .to_string()
307 + " -lstrings"
308 );
309 }
310
311 #[test]
312 fn overrides() {
313 let lib = basic_lib();
314 let mut override_db = OverrideDatabase::new();
315 override_db.insert(
316 "strings".into(),
317 Overrides {
318 build: None,
319 run: None,
320 include_dir: Some("evil_build/evil_include".to_string()),
321 lib_dir: Some("evil_build/evil_lib".to_string()),
322 },
323 );
324
325 assert_eq!(
326 lib.headers("strings", &override_db).unwrap(),
327 "-I".to_string()
328 + &Path::new(&home_dir().unwrap())
329 .join(".bricks")
330 .join("libs")
331 .join("github.com-Tesohh-strings")
332 .join("2020")
333 .join("evil_build/evil_include")
334 .display()
335 .to_string()
336 );
337 assert_eq!(
338 lib.lib_links("strings", &override_db).unwrap(),
339 "-L".to_string()
340 + &Path::new(&home_dir().unwrap())
341 .join(".bricks")
342 .join("libs")
343 .join("github.com-Tesohh-strings")
344 .join("2020")
345 .join("evil_build/evil_lib")
346 .display()
347 .to_string()
348 + " -lstrings"
349 );
350 }
351}
352
353fn default_kind() -> LibKind {
354 LibKind::Git
355}