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
18 kB 464 lines
1use std::collections::HashMap; 2 3use crate::Result; 4use crate::io::{make_relative, ordered_map}; 5use crate::requirement::Requirement; 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 #[inline] 185 pub fn is_git(&self) -> bool { 186 matches!(self.source, ManifestPackageSource::Git { .. }) 187 } 188} 189 190#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 191#[serde(tag = "source")] 192pub enum ManifestPackageSource { 193 #[serde(rename = "hex")] 194 Hex { outer_checksum: Base16Checksum }, 195 #[serde(rename = "git")] 196 Git { repo: EcoString, commit: EcoString }, 197 #[serde(rename = "local")] 198 Local { path: Utf8PathBuf }, // should be the canonical path 199} 200 201fn sorted_vec<S, T>(value: &[T], serializer: S) -> Result<S::Ok, S::Error> 202where 203 S: serde::Serializer, 204 T: serde::Serialize + Ord, 205{ 206 use serde::Serialize; 207 let mut value: Vec<&T> = value.iter().collect(); 208 value.sort(); 209 value.serialize(serializer) 210} 211 212#[cfg(test)] 213mod tests { 214 use super::*; 215 216 #[cfg(windows)] 217 const HOME: &'static str = "C:\\home\\louis\\packages\\some_folder"; 218 219 #[cfg(windows)] 220 const PACKAGE: &'static str = "C:\\home\\louis\\packages\\path\\to\\package"; 221 222 #[cfg(windows)] 223 const PACKAGE_WITH_UNC: &'static str = "\\\\?\\C:\\home\\louis\\packages\\path\\to\\package"; 224 225 #[cfg(not(windows))] 226 const HOME: &str = "/home/louis/packages/some_folder"; 227 228 #[cfg(not(windows))] 229 const PACKAGE: &str = "/home/louis/packages/path/to/package"; 230 231 #[test] 232 fn manifest_toml_format() { 233 let manifest = Manifest { 234 requirements: [ 235 ("zzz".into(), Requirement::hex("> 0.0.0").unwrap()), 236 ("aaa".into(), Requirement::hex("> 0.0.0").unwrap()), 237 ( 238 "awsome_local2".into(), 239 Requirement::git("https://github.com/gleam-lang/gleam.git", "bd9fe02f"), 240 ), 241 ( 242 "awsome_local1".into(), 243 Requirement::path("../path/to/package"), 244 ), 245 ("gleam_stdlib".into(), Requirement::hex("~> 0.17").unwrap()), 246 ("gleeunit".into(), Requirement::hex("~> 0.1").unwrap()), 247 ] 248 .into(), 249 packages: vec![ 250 ManifestPackage { 251 name: "gleam_stdlib".into(), 252 version: Version::new(0, 17, 1), 253 build_tools: ["gleam".into()].into(), 254 otp_app: None, 255 requirements: vec![], 256 source: ManifestPackageSource::Hex { 257 outer_checksum: Base16Checksum(vec![1, 22]), 258 }, 259 }, 260 ManifestPackage { 261 name: "aaa".into(), 262 version: Version::new(0, 4, 0), 263 build_tools: ["rebar3".into(), "make".into()].into(), 264 otp_app: Some("aaa_app".into()), 265 requirements: vec!["zzz".into(), "gleam_stdlib".into()], 266 source: ManifestPackageSource::Hex { 267 outer_checksum: Base16Checksum(vec![3, 22]), 268 }, 269 }, 270 ManifestPackage { 271 name: "zzz".into(), 272 version: Version::new(0, 4, 0), 273 build_tools: ["mix".into()].into(), 274 otp_app: None, 275 requirements: vec![], 276 source: ManifestPackageSource::Hex { 277 outer_checksum: Base16Checksum(vec![3, 22]), 278 }, 279 }, 280 ManifestPackage { 281 name: "awsome_local2".into(), 282 version: Version::new(1, 2, 3), 283 build_tools: ["gleam".into()].into(), 284 otp_app: None, 285 requirements: vec![], 286 source: ManifestPackageSource::Git { 287 repo: "https://github.com/gleam-lang/gleam.git".into(), 288 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 289 }, 290 }, 291 ManifestPackage { 292 name: "awsome_local1".into(), 293 version: Version::new(1, 2, 3), 294 build_tools: ["gleam".into()].into(), 295 otp_app: None, 296 requirements: vec![], 297 source: ManifestPackageSource::Local { 298 path: PACKAGE.into(), 299 }, 300 }, 301 ManifestPackage { 302 name: "gleeunit".into(), 303 version: Version::new(0, 4, 0), 304 build_tools: ["gleam".into()].into(), 305 otp_app: None, 306 requirements: vec!["gleam_stdlib".into()], 307 source: ManifestPackageSource::Hex { 308 outer_checksum: Base16Checksum(vec![3, 46]), 309 }, 310 }, 311 ], 312 }; 313 314 let buffer = manifest.to_toml(HOME.into()); 315 assert_eq!( 316 buffer, 317 r#"# This file was generated by Gleam 318# You typically do not need to edit this file 319 320packages = [ 321 { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, 322 { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, 323 { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, 324 { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, 325 { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, 326 { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, 327] 328 329[requirements] 330aaa = { version = "> 0.0.0" } 331awsome_local1 = { path = "../path/to/package" } 332awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git", ref = "bd9fe02f" } 333gleam_stdlib = { version = "~> 0.17" } 334gleeunit = { version = "~> 0.1" } 335zzz = { version = "> 0.0.0" } 336"# 337 ); 338 } 339 340 #[cfg(windows)] 341 #[test] 342 fn manifest_toml_format_with_unc() { 343 let manifest = Manifest { 344 requirements: [ 345 ("zzz".into(), Requirement::hex("> 0.0.0").unwrap()), 346 ("aaa".into(), Requirement::hex("> 0.0.0").unwrap()), 347 ( 348 "awsome_local2".into(), 349 Requirement::git("https://github.com/gleam-lang/gleam.git", "main"), 350 ), 351 ( 352 "awsome_local1".into(), 353 Requirement::path("../path/to/package"), 354 ), 355 ("gleam_stdlib".into(), Requirement::hex("~> 0.17").unwrap()), 356 ("gleeunit".into(), Requirement::hex("~> 0.1").unwrap()), 357 ] 358 .into(), 359 packages: vec![ 360 ManifestPackage { 361 name: "gleam_stdlib".into(), 362 version: Version::new(0, 17, 1), 363 build_tools: ["gleam".into()].into(), 364 otp_app: None, 365 requirements: vec![], 366 source: ManifestPackageSource::Hex { 367 outer_checksum: Base16Checksum(vec![1, 22]), 368 }, 369 }, 370 ManifestPackage { 371 name: "aaa".into(), 372 version: Version::new(0, 4, 0), 373 build_tools: ["rebar3".into(), "make".into()].into(), 374 otp_app: Some("aaa_app".into()), 375 requirements: vec!["zzz".into(), "gleam_stdlib".into()], 376 source: ManifestPackageSource::Hex { 377 outer_checksum: Base16Checksum(vec![3, 22]), 378 }, 379 }, 380 ManifestPackage { 381 name: "zzz".into(), 382 version: Version::new(0, 4, 0), 383 build_tools: ["mix".into()].into(), 384 otp_app: None, 385 requirements: vec![], 386 source: ManifestPackageSource::Hex { 387 outer_checksum: Base16Checksum(vec![3, 22]), 388 }, 389 }, 390 ManifestPackage { 391 name: "awsome_local2".into(), 392 version: Version::new(1, 2, 3), 393 build_tools: ["gleam".into()].into(), 394 otp_app: None, 395 requirements: vec![], 396 source: ManifestPackageSource::Git { 397 repo: "https://github.com/gleam-lang/gleam.git".into(), 398 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 399 }, 400 }, 401 ManifestPackage { 402 name: "awsome_local1".into(), 403 version: Version::new(1, 2, 3), 404 build_tools: ["gleam".into()].into(), 405 otp_app: None, 406 requirements: vec![], 407 source: ManifestPackageSource::Local { 408 path: PACKAGE_WITH_UNC.into(), 409 }, 410 }, 411 ManifestPackage { 412 name: "gleeunit".into(), 413 version: Version::new(0, 4, 0), 414 build_tools: ["gleam".into()].into(), 415 otp_app: None, 416 requirements: vec!["gleam_stdlib".into()], 417 source: ManifestPackageSource::Hex { 418 outer_checksum: Base16Checksum(vec![3, 46]), 419 }, 420 }, 421 ], 422 }; 423 424 let buffer = manifest.to_toml(HOME.into()); 425 assert_eq!( 426 buffer, 427 r#"# This file was generated by Gleam 428# You typically do not need to edit this file 429 430packages = [ 431 { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, 432 { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, 433 { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, 434 { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, 435 { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, 436 { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, 437] 438 439[requirements] 440aaa = { version = "> 0.0.0" } 441awsome_local1 = { path = "../path/to/package" } 442awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git", ref = "main" } 443gleam_stdlib = { version = "~> 0.17" } 444gleeunit = { version = "~> 0.1" } 445zzz = { version = "> 0.0.0" } 446"# 447 ); 448 } 449 450 impl Default for ManifestPackage { 451 fn default() -> Self { 452 Self { 453 name: Default::default(), 454 build_tools: Default::default(), 455 otp_app: Default::default(), 456 requirements: Default::default(), 457 version: Version::new(1, 0, 0), 458 source: ManifestPackageSource::Hex { 459 outer_checksum: Base16Checksum(vec![]), 460 }, 461 } 462 } 463 } 464}