Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / manifest.rs
17 kB 459 lines
1use std::collections::HashMap; 2 3use crate::io::{make_relative, ordered_map}; 4use crate::requirement::Requirement; 5use crate::Result; 6use camino::{Utf8Path, Utf8PathBuf}; 7use ecow::EcoString; 8use hexpm::version::Version; 9use itertools::Itertools; 10 11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] 12pub struct Manifest { 13 #[serde(serialize_with = "ordered_map")] 14 pub requirements: HashMap<EcoString, Requirement>, 15 #[serde(serialize_with = "sorted_vec")] 16 pub packages: Vec<ManifestPackage>, 17} 18 19impl Manifest { 20 // Rather than using the toml library to do serialization we implement it 21 // manually so that we can control the formatting. 22 // We want to keep entries on a single line each so that they are more 23 // resistant to merge conflicts and are easier to fix when it does happen. 24 pub fn to_toml(&self, root_path: &Utf8Path) -> String { 25 let mut buffer = String::new(); 26 let Self { 27 requirements, 28 packages, 29 } = self; 30 31 buffer.push_str( 32 "# This file was generated by Gleam 33# You typically do not need to edit this file 34 35", 36 ); 37 38 // Packages 39 buffer.push_str("packages = [\n"); 40 for ManifestPackage { 41 name, 42 source, 43 version, 44 otp_app, 45 build_tools, 46 requirements, 47 } in packages.iter().sorted_by(|a, b| a.name.cmp(&b.name)) 48 { 49 buffer.push_str(r#" {"#); 50 buffer.push_str(r#" name = ""#); 51 buffer.push_str(name); 52 buffer.push_str(r#"", version = ""#); 53 buffer.push_str(&version.to_string()); 54 buffer.push_str(r#"", build_tools = ["#); 55 for (i, tool) in build_tools.iter().enumerate() { 56 if i != 0 { 57 buffer.push_str(", "); 58 } 59 buffer.push('"'); 60 buffer.push_str(tool); 61 buffer.push('"'); 62 } 63 64 buffer.push_str("], requirements = ["); 65 for (i, package) in requirements.iter().sorted_by(|a, b| a.cmp(b)).enumerate() { 66 if i != 0 { 67 buffer.push_str(", "); 68 } 69 buffer.push('"'); 70 buffer.push_str(package); 71 buffer.push('"'); 72 } 73 buffer.push(']'); 74 75 if let Some(app) = otp_app { 76 buffer.push_str(", otp_app = \""); 77 buffer.push_str(app); 78 buffer.push('"'); 79 } 80 81 match source { 82 ManifestPackageSource::Hex { outer_checksum } => { 83 buffer.push_str(r#", source = "hex", outer_checksum = ""#); 84 buffer.push_str(&outer_checksum.to_string()); 85 buffer.push('"'); 86 } 87 ManifestPackageSource::Git { repo, commit } => { 88 buffer.push_str(r#", source = "git", repo = ""#); 89 buffer.push_str(repo); 90 buffer.push_str(r#"", commit = ""#); 91 buffer.push_str(commit); 92 buffer.push('"'); 93 } 94 ManifestPackageSource::Local { path } => { 95 buffer.push_str(r#", source = "local", path = ""#); 96 buffer.push_str(&make_relative(root_path, path).as_str().replace('\\', "/")); 97 buffer.push('"'); 98 } 99 }; 100 101 buffer.push_str(" },\n"); 102 } 103 buffer.push_str("]\n\n"); 104 105 // Requirements 106 buffer.push_str("[requirements]\n"); 107 for (name, requirement) in requirements.iter().sorted_by(|a, b| a.0.cmp(b.0)) { 108 buffer.push_str(name); 109 buffer.push_str(" = "); 110 buffer.push_str(&requirement.to_toml(root_path)); 111 buffer.push('\n'); 112 } 113 114 buffer 115 } 116} 117 118#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 119pub struct Base16Checksum(pub Vec<u8>); 120 121impl ToString for Base16Checksum { 122 fn to_string(&self) -> String { 123 base16::encode_upper(&self.0) 124 } 125} 126 127impl serde::Serialize for Base16Checksum { 128 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 129 where 130 S: serde::Serializer, 131 { 132 serializer.serialize_str(&base16::encode_upper(&self.0)) 133 } 134} 135 136impl<'de> serde::Deserialize<'de> for Base16Checksum { 137 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 138 where 139 D: serde::Deserializer<'de>, 140 { 141 let s: &str = serde::de::Deserialize::deserialize(deserializer)?; 142 base16::decode(s) 143 .map(Base16Checksum) 144 .map_err(serde::de::Error::custom) 145 } 146} 147 148#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 149pub struct ManifestPackage { 150 pub name: EcoString, 151 pub version: Version, 152 pub build_tools: Vec<EcoString>, 153 #[serde(default)] 154 pub otp_app: Option<EcoString>, 155 #[serde(serialize_with = "sorted_vec")] 156 pub requirements: Vec<EcoString>, 157 #[serde(flatten)] 158 pub source: ManifestPackageSource, 159} 160 161impl ManifestPackage { 162 pub fn with_build_tools(mut self, build_tools: &'static [&'static str]) -> Self { 163 self.build_tools = build_tools.iter().map(|s| (*s).into()).collect(); 164 self 165 } 166 167 pub fn application_name(&self) -> &EcoString { 168 match self.otp_app { 169 Some(ref app) => app, 170 None => &self.name, 171 } 172 } 173 174 #[inline] 175 pub fn is_hex(&self) -> bool { 176 matches!(self.source, ManifestPackageSource::Hex { .. }) 177 } 178 179 #[inline] 180 pub fn is_local(&self) -> bool { 181 matches!(self.source, ManifestPackageSource::Local { .. }) 182 } 183} 184 185#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 186#[serde(tag = "source")] 187pub enum ManifestPackageSource { 188 #[serde(rename = "hex")] 189 Hex { outer_checksum: Base16Checksum }, 190 #[serde(rename = "git")] 191 Git { repo: EcoString, commit: EcoString }, 192 #[serde(rename = "local")] 193 Local { path: Utf8PathBuf }, // should be the canonical path 194} 195 196fn sorted_vec<S, T>(value: &[T], serializer: S) -> Result<S::Ok, S::Error> 197where 198 S: serde::Serializer, 199 T: serde::Serialize + Ord, 200{ 201 use serde::Serialize; 202 let mut value: Vec<&T> = value.iter().collect(); 203 value.sort(); 204 value.serialize(serializer) 205} 206 207#[cfg(test)] 208mod tests { 209 use super::*; 210 211 #[cfg(windows)] 212 const HOME: &'static str = "C:\\home\\louis\\packages\\some_folder"; 213 214 #[cfg(windows)] 215 const PACKAGE: &'static str = "C:\\home\\louis\\packages\\path\\to\\package"; 216 217 #[cfg(windows)] 218 const PACKAGE_WITH_UNC: &'static str = "\\\\?\\C:\\home\\louis\\packages\\path\\to\\package"; 219 220 #[cfg(not(windows))] 221 const HOME: &str = "/home/louis/packages/some_folder"; 222 223 #[cfg(not(windows))] 224 const PACKAGE: &str = "/home/louis/packages/path/to/package"; 225 226 #[test] 227 fn manifest_toml_format() { 228 let manifest = Manifest { 229 requirements: [ 230 ("zzz".into(), Requirement::hex("> 0.0.0")), 231 ("aaa".into(), Requirement::hex("> 0.0.0")), 232 ( 233 "awsome_local2".into(), 234 Requirement::git("https://github.com/gleam-lang/gleam.git"), 235 ), 236 ( 237 "awsome_local1".into(), 238 Requirement::path("../path/to/package"), 239 ), 240 ("gleam_stdlib".into(), Requirement::hex("~> 0.17")), 241 ("gleeunit".into(), Requirement::hex("~> 0.1")), 242 ] 243 .into(), 244 packages: vec![ 245 ManifestPackage { 246 name: "gleam_stdlib".into(), 247 version: Version::new(0, 17, 1), 248 build_tools: ["gleam".into()].into(), 249 otp_app: None, 250 requirements: vec![], 251 source: ManifestPackageSource::Hex { 252 outer_checksum: Base16Checksum(vec![1, 22]), 253 }, 254 }, 255 ManifestPackage { 256 name: "aaa".into(), 257 version: Version::new(0, 4, 0), 258 build_tools: ["rebar3".into(), "make".into()].into(), 259 otp_app: Some("aaa_app".into()), 260 requirements: vec!["zzz".into(), "gleam_stdlib".into()], 261 source: ManifestPackageSource::Hex { 262 outer_checksum: Base16Checksum(vec![3, 22]), 263 }, 264 }, 265 ManifestPackage { 266 name: "zzz".into(), 267 version: Version::new(0, 4, 0), 268 build_tools: ["mix".into()].into(), 269 otp_app: None, 270 requirements: vec![], 271 source: ManifestPackageSource::Hex { 272 outer_checksum: Base16Checksum(vec![3, 22]), 273 }, 274 }, 275 ManifestPackage { 276 name: "awsome_local2".into(), 277 version: Version::new(1, 2, 3), 278 build_tools: ["gleam".into()].into(), 279 otp_app: None, 280 requirements: vec![], 281 source: ManifestPackageSource::Git { 282 repo: "https://github.com/gleam-lang/gleam.git".into(), 283 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 284 }, 285 }, 286 ManifestPackage { 287 name: "awsome_local1".into(), 288 version: Version::new(1, 2, 3), 289 build_tools: ["gleam".into()].into(), 290 otp_app: None, 291 requirements: vec![], 292 source: ManifestPackageSource::Local { 293 path: PACKAGE.into(), 294 }, 295 }, 296 ManifestPackage { 297 name: "gleeunit".into(), 298 version: Version::new(0, 4, 0), 299 build_tools: ["gleam".into()].into(), 300 otp_app: None, 301 requirements: vec!["gleam_stdlib".into()], 302 source: ManifestPackageSource::Hex { 303 outer_checksum: Base16Checksum(vec![3, 46]), 304 }, 305 }, 306 ], 307 }; 308 309 let buffer = manifest.to_toml(HOME.into()); 310 assert_eq!( 311 buffer, 312 r#"# This file was generated by Gleam 313# You typically do not need to edit this file 314 315packages = [ 316 { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, 317 { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, 318 { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, 319 { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, 320 { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, 321 { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, 322] 323 324[requirements] 325aaa = { version = "> 0.0.0" } 326awsome_local1 = { path = "../path/to/package" } 327awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git" } 328gleam_stdlib = { version = "~> 0.17" } 329gleeunit = { version = "~> 0.1" } 330zzz = { version = "> 0.0.0" } 331"# 332 ); 333 } 334 335 #[cfg(windows)] 336 #[test] 337 fn manifest_toml_format_with_unc() { 338 let manifest = Manifest { 339 requirements: [ 340 ("zzz".into(), Requirement::hex("> 0.0.0")), 341 ("aaa".into(), Requirement::hex("> 0.0.0")), 342 ( 343 "awsome_local2".into(), 344 Requirement::git("https://github.com/gleam-lang/gleam.git"), 345 ), 346 ( 347 "awsome_local1".into(), 348 Requirement::path("../path/to/package"), 349 ), 350 ("gleam_stdlib".into(), Requirement::hex("~> 0.17")), 351 ("gleeunit".into(), Requirement::hex("~> 0.1")), 352 ] 353 .into(), 354 packages: vec![ 355 ManifestPackage { 356 name: "gleam_stdlib".into(), 357 version: Version::new(0, 17, 1), 358 build_tools: ["gleam".into()].into(), 359 otp_app: None, 360 requirements: vec![], 361 source: ManifestPackageSource::Hex { 362 outer_checksum: Base16Checksum(vec![1, 22]), 363 }, 364 }, 365 ManifestPackage { 366 name: "aaa".into(), 367 version: Version::new(0, 4, 0), 368 build_tools: ["rebar3".into(), "make".into()].into(), 369 otp_app: Some("aaa_app".into()), 370 requirements: vec!["zzz".into(), "gleam_stdlib".into()], 371 source: ManifestPackageSource::Hex { 372 outer_checksum: Base16Checksum(vec![3, 22]), 373 }, 374 }, 375 ManifestPackage { 376 name: "zzz".into(), 377 version: Version::new(0, 4, 0), 378 build_tools: ["mix".into()].into(), 379 otp_app: None, 380 requirements: vec![], 381 source: ManifestPackageSource::Hex { 382 outer_checksum: Base16Checksum(vec![3, 22]), 383 }, 384 }, 385 ManifestPackage { 386 name: "awsome_local2".into(), 387 version: Version::new(1, 2, 3), 388 build_tools: ["gleam".into()].into(), 389 otp_app: None, 390 requirements: vec![], 391 source: ManifestPackageSource::Git { 392 repo: "https://github.com/gleam-lang/gleam.git".into(), 393 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 394 }, 395 }, 396 ManifestPackage { 397 name: "awsome_local1".into(), 398 version: Version::new(1, 2, 3), 399 build_tools: ["gleam".into()].into(), 400 otp_app: None, 401 requirements: vec![], 402 source: ManifestPackageSource::Local { 403 path: PACKAGE_WITH_UNC.into(), 404 }, 405 }, 406 ManifestPackage { 407 name: "gleeunit".into(), 408 version: Version::new(0, 4, 0), 409 build_tools: ["gleam".into()].into(), 410 otp_app: None, 411 requirements: vec!["gleam_stdlib".into()], 412 source: ManifestPackageSource::Hex { 413 outer_checksum: Base16Checksum(vec![3, 46]), 414 }, 415 }, 416 ], 417 }; 418 419 let buffer = manifest.to_toml(HOME.into()); 420 assert_eq!( 421 buffer, 422 r#"# This file was generated by Gleam 423# You typically do not need to edit this file 424 425packages = [ 426 { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, 427 { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, 428 { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, 429 { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, 430 { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, 431 { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, 432] 433 434[requirements] 435aaa = { version = "> 0.0.0" } 436awsome_local1 = { path = "../path/to/package" } 437awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git" } 438gleam_stdlib = { version = "~> 0.17" } 439gleeunit = { version = "~> 0.1" } 440zzz = { version = "> 0.0.0" } 441"# 442 ); 443 } 444 445 impl Default for ManifestPackage { 446 fn default() -> Self { 447 Self { 448 name: Default::default(), 449 build_tools: Default::default(), 450 otp_app: Default::default(), 451 requirements: Default::default(), 452 version: Version::new(1, 0, 0), 453 source: ManifestPackageSource::Hex { 454 outer_checksum: Base16Checksum(vec![]), 455 }, 456 } 457 } 458 } 459}