jocker_lib/command/
cargo.rs1use std::{
2 collections::{HashMap, HashSet},
3 ffi::OsStr,
4 fmt::Display,
5 hash::Hash,
6 path::Path,
7 process::Stdio,
8};
9
10use dotenvy::dotenv_iter;
11use serde::{Deserialize, Serialize};
12use tokio::process::{Child, Command};
13use url::Url;
14
15use crate::error::{Error, InnerError, Result};
16
17pub struct Cargo;
18
19impl Cargo {
20 pub async fn build<S>(target_dir: &Path, binaries: &[S], cargo_args: &[S]) -> Result<Child>
22 where
23 S: AsRef<OsStr> + Display + Eq + Hash,
24 {
25 let mut env: HashMap<String, String> = HashMap::new();
26 if let Ok(dotenv) = dotenv_iter() {
27 for (key, val) in dotenv.flatten() {
28 env.insert(key, val);
29 }
30 }
31 let env = env;
32
33 let mut build = Command::new("cargo");
34 build.stdout(Stdio::piped()).stderr(Stdio::piped());
35 build.arg("build");
36 for arg in HashSet::<&S>::from_iter(cargo_args) {
37 build.arg(arg);
38 }
39 for binary in HashSet::<&S>::from_iter(binaries) {
40 build.arg(format!("--bin={binary}"));
41 }
42 for (key, val) in env.iter() {
43 build.env(key, val);
44 }
45 build.current_dir(target_dir);
46 let build = build
47 .spawn()
48 .map_err(Error::with_context(InnerError::Start(
49 "Unable to start `cargo build` command".to_string(),
50 )))?;
51 Ok(build)
52 }
53
54 pub async fn metadata(target_dir: &Path) -> Result<Vec<SerializedPackage>> {
55 let metadata = Command::new("cargo")
56 .arg("metadata")
57 .arg("--format-version=1")
58 .current_dir(target_dir)
59 .output()
60 .await
61 .map_err(Error::with_context(InnerError::Cargo))?;
62 let info: ExportInfoMinimal = serde_json::from_slice(&metadata.stdout).unwrap();
63 let ret = info
64 .packages
65 .into_iter()
66 .filter(|package| {
67 package
68 .targets
69 .iter()
70 .filter(|target| {
71 target
72 .kind
73 .iter()
74 .filter(|kind| matches!(kind, TargetKind::Bin))
75 .count()
76 >= 1
77 })
78 .count()
79 >= 1
80 && package.id.scheme().eq("path+file")
81 })
82 .collect();
83 Ok(ret)
84 }
85}
86
87#[derive(Debug, Deserialize)]
88pub struct ExportInfoMinimal {
89 pub packages: Vec<SerializedPackage>,
90}
91
92#[derive(Debug, Deserialize, Serialize)]
93pub struct SerializedPackage {
94 pub name: String,
95 pub id: Url,
96 pub targets: Vec<TargetInner>,
97}
98
99#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
100pub struct TargetInner {
101 pub kind: Vec<TargetKind>,
102 pub name: String,
103 pub bin_name: Option<String>,
104}
105
106#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
107#[serde(rename_all = "kebab-case")]
108pub enum TargetKind {
109 Lib,
110 Bin,
111 Test,
112 Bench,
113 ExampleLib,
114 ExampleBin,
115 CustomBuild,
116 #[serde(untagged)]
117 Other(String),
118}
119
120pub struct BinaryPackage {
121 pub name: String,
122 pub id: Url,
123}
124
125impl BinaryPackage {
126 pub fn name(&self) -> &str {
127 &self.name
128 }
129}
130
131impl From<SerializedPackage> for BinaryPackage {
132 fn from(value: SerializedPackage) -> Self {
133 Self {
134 name: value.name,
135 id: value.id,
136 }
137 }
138}