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
30 kB 856 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2021 The Gleam contributors 3 4use std::collections::HashMap; 5use std::fmt; 6 7use crate::Result; 8use crate::io::{make_relative, ordered_map}; 9use crate::requirement::Requirement; 10use camino::{Utf8Path, Utf8PathBuf}; 11use ecow::EcoString; 12use hexpm::version::Version; 13use itertools::Itertools; 14 15#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] 16pub struct Manifest { 17 #[serde( 18 serialize_with = "ordered_map", 19 deserialize_with = "super::config::map_with_package_name_keys::deserialize" 20 )] 21 pub requirements: HashMap<EcoString, Requirement>, 22 #[serde(serialize_with = "sorted_vec")] 23 pub packages: Vec<ManifestPackage>, 24} 25 26impl Manifest { 27 // Rather than using the toml library to do serialization we implement it 28 // manually so that we can control the formatting. 29 // We want to keep entries on a single line each so that they are more 30 // resistant to merge conflicts and are easier to fix when it does happen. 31 pub fn to_toml(&self, root_path: &Utf8Path) -> String { 32 let mut buffer = String::new(); 33 let Self { 34 requirements, 35 packages, 36 } = self; 37 38 buffer.push_str( 39 "# Do not manually edit this file, it is managed by Gleam. 40# 41# This file locks the dependency versions used, to make your build 42# deterministic and to prevent unexpected versions from being included 43# in your application. 44# 45# You should check this file into your source control repository. 46 47", 48 ); 49 50 // Packages 51 buffer.push_str("packages = [\n"); 52 for ManifestPackage { 53 name, 54 source, 55 version, 56 otp_app, 57 build_tools, 58 requirements, 59 } in packages.iter().sorted_by(|a, b| a.name.cmp(&b.name)) 60 { 61 buffer.push_str(r#" {"#); 62 buffer.push_str(r#" name = ""#); 63 buffer.push_str(name); 64 buffer.push_str(r#"", version = ""#); 65 buffer.push_str(&version.to_string()); 66 buffer.push_str(r#"", build_tools = ["#); 67 for (i, tool) in build_tools.iter().enumerate() { 68 if i != 0 { 69 buffer.push_str(", "); 70 } 71 buffer.push('"'); 72 buffer.push_str(tool); 73 buffer.push('"'); 74 } 75 76 buffer.push_str("], requirements = ["); 77 for (i, package) in requirements.iter().sorted_by(|a, b| a.cmp(b)).enumerate() { 78 if i != 0 { 79 buffer.push_str(", "); 80 } 81 buffer.push('"'); 82 buffer.push_str(package); 83 buffer.push('"'); 84 } 85 buffer.push(']'); 86 87 if let Some(app) = otp_app { 88 buffer.push_str(", otp_app = \""); 89 buffer.push_str(app); 90 buffer.push('"'); 91 } 92 93 match source { 94 ManifestPackageSource::Hex { outer_checksum } => { 95 buffer.push_str(r#", source = "hex", outer_checksum = ""#); 96 buffer.push_str(&outer_checksum.base_16_encoded_string()); 97 buffer.push('"'); 98 } 99 ManifestPackageSource::Git { repo, commit } => { 100 buffer.push_str(r#", source = "git", repo = ""#); 101 buffer.push_str(repo); 102 buffer.push_str(r#"", commit = ""#); 103 buffer.push_str(commit); 104 buffer.push('"'); 105 } 106 ManifestPackageSource::Local { path } => { 107 buffer.push_str(r#", source = "local", path = ""#); 108 buffer.push_str(&make_relative(root_path, path).as_str().replace('\\', "/")); 109 buffer.push('"'); 110 } 111 }; 112 113 buffer.push_str(" },\n"); 114 } 115 buffer.push_str("]\n\n"); 116 117 // Requirements 118 buffer.push_str("[requirements]\n"); 119 for (name, requirement) in requirements.iter().sorted_by(|a, b| a.0.cmp(b.0)) { 120 buffer.push_str(name); 121 buffer.push_str(" = "); 122 buffer.push_str(&requirement.to_toml(root_path)); 123 buffer.push('\n'); 124 } 125 126 buffer 127 } 128} 129 130#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 131pub struct Base16Checksum(pub Vec<u8>); 132 133impl Base16Checksum { 134 pub fn base_16_encoded_string(&self) -> String { 135 base16::encode_upper(&self.0) 136 } 137} 138 139impl serde::Serialize for Base16Checksum { 140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 141 where 142 S: serde::Serializer, 143 { 144 serializer.serialize_str(&base16::encode_upper(&self.0)) 145 } 146} 147 148impl<'de> serde::Deserialize<'de> for Base16Checksum { 149 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 150 where 151 D: serde::Deserializer<'de>, 152 { 153 deserializer.deserialize_str(Base16ChecksumVisitor) 154 } 155} 156 157struct Base16ChecksumVisitor; 158 159impl<'de> serde::de::Visitor<'de> for Base16ChecksumVisitor { 160 type Value = Base16Checksum; 161 162 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 163 formatter.write_str("a base 16 checksum") 164 } 165 166 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 167 where 168 E: serde::de::Error, 169 { 170 base16::decode(value) 171 .map(Base16Checksum) 172 .map_err(serde::de::Error::custom) 173 } 174} 175 176#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 177pub struct ManifestPackage { 178 #[serde(deserialize_with = "super::config::package_name::deserialize")] 179 pub name: EcoString, 180 pub version: Version, 181 pub build_tools: Vec<EcoString>, 182 #[serde( 183 default, 184 deserialize_with = "super::config::package_name::optional_deserialize" 185 )] 186 pub otp_app: Option<EcoString>, 187 #[serde(serialize_with = "sorted_vec")] 188 pub requirements: Vec<EcoString>, 189 #[serde(flatten)] 190 pub source: ManifestPackageSource, 191} 192 193impl ManifestPackage { 194 pub fn with_build_tools(mut self, build_tools: &'static [&'static str]) -> Self { 195 self.build_tools = build_tools.iter().map(|s| (*s).into()).collect(); 196 self 197 } 198 199 pub fn application_name(&self) -> &EcoString { 200 match self.otp_app { 201 Some(ref app) => app, 202 None => &self.name, 203 } 204 } 205 206 #[inline] 207 pub fn is_hex(&self) -> bool { 208 matches!(self.source, ManifestPackageSource::Hex { .. }) 209 } 210 211 #[inline] 212 pub fn is_local(&self) -> bool { 213 matches!(self.source, ManifestPackageSource::Local { .. }) 214 } 215 216 #[inline] 217 pub fn is_git(&self) -> bool { 218 matches!(self.source, ManifestPackageSource::Git { .. }) 219 } 220} 221 222#[cfg(test)] 223impl Default for ManifestPackage { 224 fn default() -> Self { 225 Self { 226 name: Default::default(), 227 build_tools: Default::default(), 228 otp_app: Default::default(), 229 requirements: Default::default(), 230 version: Version::new(1, 0, 0), 231 source: ManifestPackageSource::Hex { 232 outer_checksum: Base16Checksum(vec![]), 233 }, 234 } 235 } 236} 237 238#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] 239#[serde(tag = "source")] 240pub enum ManifestPackageSource { 241 #[serde(rename = "hex")] 242 Hex { outer_checksum: Base16Checksum }, 243 #[serde(rename = "git")] 244 Git { repo: EcoString, commit: EcoString }, 245 #[serde(rename = "local")] 246 Local { path: Utf8PathBuf }, // should be the canonical path 247} 248 249impl ManifestPackageSource { 250 pub fn kind(&self) -> ManifestPackageSourceKind { 251 match self { 252 ManifestPackageSource::Hex { .. } => ManifestPackageSourceKind::Hex, 253 ManifestPackageSource::Git { .. } => ManifestPackageSourceKind::Git, 254 ManifestPackageSource::Local { .. } => ManifestPackageSourceKind::Local, 255 } 256 } 257} 258 259#[derive(Debug, PartialEq, Eq, Clone, Copy)] 260pub enum ManifestPackageSourceKind { 261 Hex, 262 Git, 263 Local, 264} 265 266fn sorted_vec<S, T>(value: &[T], serializer: S) -> Result<S::Ok, S::Error> 267where 268 S: serde::Serializer, 269 T: serde::Serialize + Ord, 270{ 271 use serde::Serialize; 272 let mut value: Vec<&T> = value.iter().collect(); 273 value.sort(); 274 value.serialize(serializer) 275} 276 277#[cfg(test)] 278mod tests { 279 use super::*; 280 281 #[cfg(windows)] 282 const HOME: &'static str = "C:\\home\\louis\\packages\\some_folder"; 283 284 #[cfg(windows)] 285 const PACKAGE: &'static str = "C:\\home\\louis\\packages\\path\\to\\package"; 286 287 #[cfg(windows)] 288 const PACKAGE_WITH_UNC: &'static str = "\\\\?\\C:\\home\\louis\\packages\\path\\to\\package"; 289 290 #[cfg(not(windows))] 291 const HOME: &str = "/home/louis/packages/some_folder"; 292 293 #[cfg(not(windows))] 294 const PACKAGE: &str = "/home/louis/packages/path/to/package"; 295 296 #[test] 297 fn manifest_toml_format() { 298 let manifest = Manifest { 299 requirements: [ 300 ("zzz".into(), Requirement::hex("> 0.0.0").unwrap()), 301 ("aaa".into(), Requirement::hex("> 0.0.0").unwrap()), 302 ( 303 "awsome_local2".into(), 304 Requirement::git("https://github.com/gleam-lang/gleam.git", "bd9fe02f"), 305 ), 306 ( 307 "awsome_local1".into(), 308 Requirement::path("../path/to/package"), 309 ), 310 ("gleam_stdlib".into(), Requirement::hex("~> 0.17").unwrap()), 311 ("gleeunit".into(), Requirement::hex("~> 0.1").unwrap()), 312 ] 313 .into(), 314 packages: vec![ 315 ManifestPackage { 316 name: "gleam_stdlib".into(), 317 version: Version::new(0, 17, 1), 318 build_tools: ["gleam".into()].into(), 319 otp_app: None, 320 requirements: vec![], 321 source: ManifestPackageSource::Hex { 322 outer_checksum: Base16Checksum(vec![1, 22]), 323 }, 324 }, 325 ManifestPackage { 326 name: "aaa".into(), 327 version: Version::new(0, 4, 0), 328 build_tools: ["rebar3".into(), "make".into()].into(), 329 otp_app: Some("aaa_app".into()), 330 requirements: vec!["zzz".into(), "gleam_stdlib".into()], 331 source: ManifestPackageSource::Hex { 332 outer_checksum: Base16Checksum(vec![3, 22]), 333 }, 334 }, 335 ManifestPackage { 336 name: "zzz".into(), 337 version: Version::new(0, 4, 0), 338 build_tools: ["mix".into()].into(), 339 otp_app: None, 340 requirements: vec![], 341 source: ManifestPackageSource::Hex { 342 outer_checksum: Base16Checksum(vec![3, 22]), 343 }, 344 }, 345 ManifestPackage { 346 name: "awsome_local2".into(), 347 version: Version::new(1, 2, 3), 348 build_tools: ["gleam".into()].into(), 349 otp_app: None, 350 requirements: vec![], 351 source: ManifestPackageSource::Git { 352 repo: "https://github.com/gleam-lang/gleam.git".into(), 353 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 354 }, 355 }, 356 ManifestPackage { 357 name: "awsome_local1".into(), 358 version: Version::new(1, 2, 3), 359 build_tools: ["gleam".into()].into(), 360 otp_app: None, 361 requirements: vec![], 362 source: ManifestPackageSource::Local { 363 path: PACKAGE.into(), 364 }, 365 }, 366 ManifestPackage { 367 name: "gleeunit".into(), 368 version: Version::new(0, 4, 0), 369 build_tools: ["gleam".into()].into(), 370 otp_app: None, 371 requirements: vec!["gleam_stdlib".into()], 372 source: ManifestPackageSource::Hex { 373 outer_checksum: Base16Checksum(vec![3, 46]), 374 }, 375 }, 376 ], 377 }; 378 379 let buffer = manifest.to_toml(HOME.into()); 380 assert_eq!( 381 buffer, 382 r#"# Do not manually edit this file, it is managed by Gleam. 383# 384# This file locks the dependency versions used, to make your build 385# deterministic and to prevent unexpected versions from being included 386# in your application. 387# 388# You should check this file into your source control repository. 389 390packages = [ 391 { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, 392 { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, 393 { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, 394 { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, 395 { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, 396 { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, 397] 398 399[requirements] 400aaa = { version = "> 0.0.0" } 401awsome_local1 = { path = "../path/to/package" } 402awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git", ref = "bd9fe02f" } 403gleam_stdlib = { version = "~> 0.17" } 404gleeunit = { version = "~> 0.1" } 405zzz = { version = "> 0.0.0" } 406"# 407 ); 408 } 409 410 #[cfg(windows)] 411 #[test] 412 fn manifest_toml_format_with_unc() { 413 let manifest = Manifest { 414 requirements: [ 415 ("zzz".into(), Requirement::hex("> 0.0.0").unwrap()), 416 ("aaa".into(), Requirement::hex("> 0.0.0").unwrap()), 417 ( 418 "awsome_local2".into(), 419 Requirement::git("https://github.com/gleam-lang/gleam.git", "main"), 420 ), 421 ( 422 "awsome_local1".into(), 423 Requirement::path("../path/to/package"), 424 ), 425 ("gleam_stdlib".into(), Requirement::hex("~> 0.17").unwrap()), 426 ("gleeunit".into(), Requirement::hex("~> 0.1").unwrap()), 427 ] 428 .into(), 429 packages: vec![ 430 ManifestPackage { 431 name: "gleam_stdlib".into(), 432 version: Version::new(0, 17, 1), 433 build_tools: ["gleam".into()].into(), 434 otp_app: None, 435 requirements: vec![], 436 source: ManifestPackageSource::Hex { 437 outer_checksum: Base16Checksum(vec![1, 22]), 438 }, 439 }, 440 ManifestPackage { 441 name: "aaa".into(), 442 version: Version::new(0, 4, 0), 443 build_tools: ["rebar3".into(), "make".into()].into(), 444 otp_app: Some("aaa_app".into()), 445 requirements: vec!["zzz".into(), "gleam_stdlib".into()], 446 source: ManifestPackageSource::Hex { 447 outer_checksum: Base16Checksum(vec![3, 22]), 448 }, 449 }, 450 ManifestPackage { 451 name: "zzz".into(), 452 version: Version::new(0, 4, 0), 453 build_tools: ["mix".into()].into(), 454 otp_app: None, 455 requirements: vec![], 456 source: ManifestPackageSource::Hex { 457 outer_checksum: Base16Checksum(vec![3, 22]), 458 }, 459 }, 460 ManifestPackage { 461 name: "awsome_local2".into(), 462 version: Version::new(1, 2, 3), 463 build_tools: ["gleam".into()].into(), 464 otp_app: None, 465 requirements: vec![], 466 source: ManifestPackageSource::Git { 467 repo: "https://github.com/gleam-lang/gleam.git".into(), 468 commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), 469 }, 470 }, 471 ManifestPackage { 472 name: "awsome_local1".into(), 473 version: Version::new(1, 2, 3), 474 build_tools: ["gleam".into()].into(), 475 otp_app: None, 476 requirements: vec![], 477 source: ManifestPackageSource::Local { 478 path: PACKAGE_WITH_UNC.into(), 479 }, 480 }, 481 ManifestPackage { 482 name: "gleeunit".into(), 483 version: Version::new(0, 4, 0), 484 build_tools: ["gleam".into()].into(), 485 otp_app: None, 486 requirements: vec!["gleam_stdlib".into()], 487 source: ManifestPackageSource::Hex { 488 outer_checksum: Base16Checksum(vec![3, 46]), 489 }, 490 }, 491 ], 492 }; 493 494 let buffer = manifest.to_toml(HOME.into()); 495 assert_eq!( 496 buffer, 497 r#"# Do not manually edit this file, it is managed by Gleam. 498# 499# This file locks the dependency versions used, to make your build 500# deterministic and to prevent unexpected versions from being included 501# in your application. 502# 503# You should check this file into your source control repository. 504 505packages = [ 506 { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, 507 { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, 508 { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, 509 { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, 510 { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, 511 { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, 512] 513 514[requirements] 515aaa = { version = "> 0.0.0" } 516awsome_local1 = { path = "../path/to/package" } 517awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git", ref = "main" } 518gleam_stdlib = { version = "~> 0.17" } 519gleeunit = { version = "~> 0.1" } 520zzz = { version = "> 0.0.0" } 521"# 522 ); 523 } 524} 525 526#[derive(Debug)] 527pub struct Resolved { 528 pub manifest: Manifest, 529 pub package_changes: PackageChanges, 530 pub requirements_changed: bool, 531} 532 533#[derive(Debug)] 534pub struct PackageChanges { 535 pub added: Vec<(EcoString, Version)>, 536 pub changed: Vec<Changed>, 537 /// When updating git dependencies, it is possible to update to a newer commit 538 /// without updating the version of the package (which is specified in 539 /// `gleam.toml`). In this case, we still want to record the change, but it 540 /// must be stored differently. 541 pub changed_git: Vec<ChangedGit>, 542 pub removed: Vec<EcoString>, 543} 544 545#[derive(Debug, PartialEq)] 546pub struct Changed { 547 pub name: EcoString, 548 pub old: Version, 549 pub new: Version, 550} 551 552#[derive(Debug, PartialEq)] 553pub struct ChangedGit { 554 pub name: EcoString, 555 pub old_hash: EcoString, 556 pub new_hash: EcoString, 557} 558 559impl Resolved { 560 pub fn any_changes(&self) -> bool { 561 self.requirements_changed || self.package_changes.any_changes() 562 } 563 564 pub fn all_added(manifest: Manifest) -> Resolved { 565 let added = manifest 566 .packages 567 .iter() 568 .map(|package| (package.name.clone(), package.version.clone())) 569 .collect(); 570 Self { 571 manifest, 572 requirements_changed: true, 573 package_changes: PackageChanges { 574 added, 575 changed: vec![], 576 changed_git: vec![], 577 removed: vec![], 578 }, 579 } 580 } 581 582 pub fn no_change(manifest: Manifest) -> Self { 583 Self { 584 manifest, 585 requirements_changed: false, 586 package_changes: PackageChanges { 587 added: vec![], 588 changed: vec![], 589 changed_git: vec![], 590 removed: vec![], 591 }, 592 } 593 } 594} 595 596impl PackageChanges { 597 pub fn any_changes(&self) -> bool { 598 !self.added.is_empty() 599 || !self.changed.is_empty() 600 || !self.changed_git.is_empty() 601 || !self.removed.is_empty() 602 } 603 604 /// Compare the old and new versions of the manifest and determine the package changes 605 pub fn between_manifests(old: &Manifest, new: &Manifest) -> Self { 606 let mut added = vec![]; 607 let mut changed = vec![]; 608 let mut changed_git = vec![]; 609 let mut removed = vec![]; 610 let mut old: HashMap<_, _> = old 611 .packages 612 .iter() 613 .map(|package| (&package.name, package)) 614 .collect(); 615 616 for new in &new.packages { 617 match old.remove(&new.name) { 618 // If the kind of source changed, the packages bear essentially no connection 619 Some(old) if new.source.kind() != old.source.kind() => { 620 removed.push(old.name.clone()); 621 added.push((new.name.clone(), new.version.clone())); 622 } 623 Some(old) if old.version == new.version => match (&old.source, &new.source) { 624 ( 625 ManifestPackageSource::Git { 626 commit: old_hash, .. 627 }, 628 ManifestPackageSource::Git { 629 commit: new_hash, .. 630 }, 631 ) if old_hash != new_hash => changed_git.push(ChangedGit { 632 name: new.name.clone(), 633 old_hash: old_hash.clone(), 634 new_hash: new_hash.clone(), 635 }), 636 ( 637 ManifestPackageSource::Hex { .. } 638 | ManifestPackageSource::Local { .. } 639 | ManifestPackageSource::Git { .. }, 640 _, 641 ) => {} 642 }, 643 Some(old) => { 644 changed.push(Changed { 645 name: new.name.clone(), 646 old: old.version.clone(), 647 new: new.version.clone(), 648 }); 649 } 650 None => { 651 added.push((new.name.clone(), new.version.clone())); 652 } 653 } 654 } 655 656 removed.extend(old.into_keys().cloned()); 657 658 Self { 659 added, 660 changed, 661 changed_git, 662 removed, 663 } 664 } 665} 666 667#[cfg(test)] 668mod manifest_update_tests { 669 use std::collections::HashMap; 670 671 use ecow::EcoString; 672 use hexpm::version::Version; 673 674 use crate::manifest::{Base16Checksum, ManifestPackage, ManifestPackageSource, PackageChanges}; 675 use crate::manifest::{Changed, Manifest}; 676 677 #[test] 678 fn resolved_with_updated() { 679 let package = |name: &str, version| ManifestPackage { 680 name: EcoString::from(name), 681 version, 682 build_tools: vec![], 683 otp_app: None, 684 requirements: vec![], 685 source: ManifestPackageSource::Hex { 686 outer_checksum: Base16Checksum(vec![]), 687 }, 688 }; 689 690 let old = Manifest { 691 requirements: HashMap::new(), 692 packages: vec![ 693 package("unchanged1", Version::new(3, 0, 0)), 694 package("unchanged2", Version::new(0, 1, 0)), 695 package("changed1", Version::new(3, 0, 0)), 696 package("changed2", Version::new(0, 1, 0)), 697 package("removed1", Version::new(10, 0, 0)), 698 package("removed2", Version::new(20, 1, 0)), 699 ], 700 }; 701 702 let new = Manifest { 703 requirements: HashMap::new(), 704 packages: vec![ 705 package("new1", Version::new(1, 0, 0)), 706 package("new2", Version::new(2, 1, 0)), 707 package("unchanged1", Version::new(3, 0, 0)), 708 package("unchanged2", Version::new(0, 1, 0)), 709 package("changed1", Version::new(5, 0, 0)), 710 package("changed2", Version::new(3, 0, 0)), 711 ], 712 }; 713 714 let mut changes = PackageChanges::between_manifests(&old, &new); 715 changes.added.sort(); 716 changes.changed.sort_by(|a, b| a.name.cmp(&b.name)); 717 changes.removed.sort(); 718 719 assert_eq!( 720 changes.added, 721 vec![ 722 ("new1".into(), Version::new(1, 0, 0)), 723 ("new2".into(), Version::new(2, 1, 0)), 724 ] 725 ); 726 727 assert_eq!( 728 changes.changed, 729 vec![ 730 Changed { 731 name: "changed1".into(), 732 old: Version::new(3, 0, 0), 733 new: Version::new(5, 0, 0) 734 }, 735 Changed { 736 name: "changed2".into(), 737 old: Version::new(0, 1, 0), 738 new: Version::new(3, 0, 0) 739 }, 740 ] 741 ); 742 743 assert_eq!( 744 changes.removed, 745 vec![EcoString::from("removed1"), EcoString::from("removed2")] 746 ); 747 } 748 749 #[test] 750 fn resolved_with_source_type_change() { 751 let name = EcoString::from("wibble"); 752 let version = Version::new(1, 0, 0); 753 let package = |source| ManifestPackage { 754 name: name.clone(), 755 version: version.clone(), 756 build_tools: vec![], 757 otp_app: None, 758 requirements: vec![], 759 source, 760 }; 761 762 let old = Manifest { 763 requirements: HashMap::new(), 764 packages: vec![package(ManifestPackageSource::Local { 765 path: "wibble".into(), 766 })], 767 }; 768 769 let new = Manifest { 770 requirements: HashMap::new(), 771 packages: vec![package(ManifestPackageSource::Hex { 772 outer_checksum: Base16Checksum(vec![]), 773 })], 774 }; 775 776 let changes = PackageChanges::between_manifests(&old, &new); 777 assert!(changes.changed.is_empty()); 778 assert_eq!(changes.removed, vec![name.clone()]); 779 assert_eq!(changes.added, vec![(name.clone(), version.clone())]); 780 } 781} 782 783#[test] 784fn invalid_requirements_dependency_name() { 785 // This is valid toml but wibble/wobble is not a valid package name 786 let toml = r#"# This file was generated by Gleam 787# You typically do not need to edit this file 788 789packages = [ 790 { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, 791] 792 793[requirements] 794"wibble/wobble" = { version = ">= 0.58.0 and < 2.0.0" } 795"#; 796 797 let manifest: Result<Manifest, _> = toml::from_str(toml); 798 let error = manifest.expect_err("should fail to deserialise because invalid name"); 799 insta::assert_snapshot!(insta::internals::AutoName, error.to_string()); 800} 801 802#[test] 803fn invalid_packages_dependency_name() { 804 // This is valid toml but wibble/wobble is not a valid package name 805 let toml = r#"# This file was generated by Gleam 806# You typically do not need to edit this file 807 808packages = [ 809 { name = "wibble/wobble", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, 810] 811 812[requirements] 813gleam_stdlib = { version = ">= 0.58.0 and < 2.0.0" } 814"#; 815 816 let manifest: Result<Manifest, _> = toml::from_str(toml); 817 let error = manifest.expect_err("should fail to deserialise because invalid name"); 818 insta::assert_snapshot!(insta::internals::AutoName, error.to_string()); 819} 820 821#[test] 822fn invalid_packages_otp_app_name() { 823 // This is valid toml but wibble/wobble is not a valid package name 824 let toml = r#"# This file was generated by Gleam 825# You typically do not need to edit this file 826 827packages = [ 828 { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "wibble/wobble", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, 829] 830 831[requirements] 832gleam_stdlib = { version = ">= 0.58.0 and < 2.0.0" } 833"#; 834 835 let manifest: Result<Manifest, _> = toml::from_str(toml); 836 let error = manifest.expect_err("should fail to deserialise because invalid name"); 837 insta::assert_snapshot!(insta::internals::AutoName, error.to_string()); 838} 839 840#[test] 841fn no_otp_app() { 842 // This is valid toml but wibble/wobble is not a valid package name 843 let toml = r#"# This file was generated by Gleam 844# You typically do not need to edit this file 845 846packages = [ 847 { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, 848] 849 850[requirements] 851gleam_stdlib = { version = ">= 0.58.0 and < 2.0.0" } 852"#; 853 854 let manifest: Result<Manifest, _> = toml::from_str(toml); 855 assert!(manifest.is_ok()); 856}