tugger_rust_toolchain/
manifest.rs1use {
8 crate::tar::CompressionFormat,
9 anyhow::{anyhow, Result},
10 std::collections::HashMap,
11 tugger_common::http::RemoteContent,
12};
13
14#[derive(Clone, Debug)]
16pub struct Manifest {
17 pub packages: HashMap<String, Package>,
18}
19
20impl Manifest {
21 pub fn from_toml_bytes(data: &[u8]) -> Result<Self> {
23 let table = toml::from_slice(data)?;
24
25 Self::from_toml(table)
26 }
27
28 pub fn from_toml(table: toml::value::Table) -> Result<Self> {
29 let manifest_version = match table
30 .get("manifest-version")
31 .ok_or_else(|| anyhow!("manifest TOML doesn't have manifest-version key"))?
32 {
33 toml::Value::String(s) => s,
34 _ => return Err(anyhow!("failed to obtain manifest-version from TOML")),
35 };
36
37 if manifest_version != "2" {
38 return Err(anyhow!(
39 "unhandled manifest-version: {}; only version 2 supported",
40 manifest_version,
41 ));
42 }
43
44 let packages = Self::parse_packages(table)?;
45
46 Ok(Self { packages })
47 }
48
49 pub fn parse_packages(mut table: toml::value::Table) -> Result<HashMap<String, Package>> {
50 let mut result = HashMap::new();
51
52 let pkg_table = match table
53 .remove("pkg")
54 .ok_or_else(|| anyhow!("manifest TOML doesn't have any [pkg]"))?
55 {
56 toml::Value::Table(table) => table,
57 _ => return Err(anyhow!("manifest TOML doesn't have table [pkg]")),
58 };
59
60 for (k, v) in pkg_table {
61 if let toml::Value::Table(v) = v {
62 result.insert(k.clone(), Package::from_table(v)?);
63 }
64 }
65
66 Ok(result)
67 }
68
69 pub fn find_package(
71 &self,
72 package: &str,
73 target_triple: &str,
74 ) -> Option<(String, ManifestTargetedPackage)> {
75 match self.packages.get(package) {
76 Some(package) => match &package.target {
77 PackageTarget::Wildcard => None,
78 PackageTarget::Targeted(targets) => targets
79 .get(target_triple)
80 .map(|target| (package.version.clone(), target.clone())),
81 },
82 None => None,
83 }
84 }
85}
86
87#[derive(Clone, Debug)]
89pub struct Package {
90 pub version: String,
91 pub target: PackageTarget,
92}
93
94impl Package {
95 pub fn from_table(mut table: toml::value::Table) -> Result<Self> {
96 let version = match table
97 .remove("version")
98 .ok_or_else(|| anyhow!("[pkg] doesn't have version"))?
99 {
100 toml::Value::String(v) => v,
101 _ => return Err(anyhow!("pkg TOML has non-string version")),
102 };
103
104 let mut target_table = match table
105 .remove("target")
106 .ok_or_else(|| anyhow!("[pkg] does not have .target table"))?
107 {
108 toml::Value::Table(t) => t,
109 _ => return Err(anyhow!("[pkg.target] is not a table")),
110 };
111
112 let target = if let Some(toml::Value::Table(_)) = target_table.remove("*") {
113 PackageTarget::Wildcard
114 } else {
115 let mut targets = HashMap::new();
116
117 for (k, v) in target_table {
118 if let toml::Value::Table(mut v) = v {
119 let available = v
120 .remove("available")
121 .ok_or_else(|| anyhow!("available not set"))?
122 .as_bool()
123 .ok_or_else(|| anyhow!("available not a bool"))?;
124
125 let mut urls = vec![];
126
127 for prefix in &["zst_", "xz_", ""] {
128 let url = v.remove(&format!("{}url", prefix));
129 let hash = v.remove(&format!("{}hash", prefix));
130
131 if let (Some(url), Some(hash)) = (url, hash) {
132 let url = url
133 .as_str()
134 .ok_or_else(|| anyhow!("url not a string"))?
135 .to_string();
136 let hash = hash
137 .as_str()
138 .ok_or_else(|| anyhow!("hash not a string"))?
139 .to_string();
140
141 urls.push((
142 match *prefix {
143 "zst_" => CompressionFormat::Zstd,
144 "xz_" => CompressionFormat::Xz,
145 "" => CompressionFormat::Gzip,
146 _ => panic!("logic error in compression format handling"),
147 },
148 url,
149 hash,
150 ));
151 }
152 }
153
154 targets.insert(
155 k.clone(),
156 ManifestTargetedPackage {
157 name: k,
158 available,
159 urls,
160 },
161 );
162 }
163 }
164
165 PackageTarget::Targeted(targets)
166 };
167
168 Ok(Self { version, target })
169 }
170}
171
172#[derive(Clone, Debug)]
173pub enum PackageTarget {
174 Wildcard,
175 Targeted(HashMap<String, ManifestTargetedPackage>),
176}
177
178#[derive(Clone, Debug)]
179pub struct ManifestTargetedPackage {
180 pub name: String,
181 pub available: bool,
182 pub urls: Vec<(CompressionFormat, String, String)>,
183}
184
185impl ManifestTargetedPackage {
186 pub fn download_info(&self) -> Option<(CompressionFormat, RemoteContent)> {
187 let (format, url, digest) = self.urls.get(0)?;
188
189 Some((
190 *format,
191 RemoteContent {
192 name: self.name.clone(),
193 url: url.to_string(),
194 sha256: digest.to_string(),
195 },
196 ))
197 }
198}