Fork of daniellemaywood.uk/gleam — Wasm codegen work
5.5 kB
169 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4use camino::{Utf8Path, Utf8PathBuf};
5
6use gleam_core::{
7 Error, Result,
8 error::{FileIoAction, FileKind},
9 paths::ProjectPaths,
10};
11use hexpm::version::{Identifier, Version};
12
13use crate::{
14 cli,
15 dependencies::{self, parse_gleam_add_specifier},
16 fs,
17};
18
19pub fn command(paths: &ProjectPaths, packages_to_add: Vec<String>, dev: bool) -> Result<()> {
20 let config = crate::config::root_config(paths)?;
21 if packages_to_add.iter().any(|name| name == &config.name) {
22 return Err(Error::CannotAddSelfAsDependency { name: config.name });
23 }
24
25 let mut new_package_requirements = Vec::with_capacity(packages_to_add.len());
26 for specifier in packages_to_add {
27 new_package_requirements.push(parse_gleam_add_specifier(&specifier)?);
28 }
29
30 // Insert the new packages into the manifest and perform dependency
31 // resolution to determine suitable versions
32 let manifest = dependencies::resolve_and_download(
33 paths,
34 cli::Reporter::new(),
35 Some((new_package_requirements.clone(), dev)),
36 Vec::new(),
37 dependencies::DependencyManagerConfig {
38 use_manifest: dependencies::UseManifest::Yes,
39 check_major_versions: dependencies::CheckMajorVersions::No,
40 },
41 )?;
42
43 // Read gleam.toml and manifest.toml so we can insert new deps into it
44 let mut gleam_toml = read_toml_edit(&paths.root_config())?;
45 let mut manifest_toml = read_toml_edit(&paths.manifest())?;
46
47 // Insert the new deps
48 for (added_package, _) in new_package_requirements {
49 let added_package = added_package.to_string();
50
51 // Pull the selected version out of the new manifest so we know what it is
52 let version = &manifest
53 .packages
54 .iter()
55 .find(|package| package.name == *added_package)
56 .expect("Added package not found in resolved manifest")
57 .version;
58
59 tracing::info!(version=%version, "new_package_version_resolved");
60
61 // Produce a version requirement locked to the major version.
62 // i.e. if 1.2.3 is selected we want >= 1.2.3 and < 2.0.0
63 let range = format!(
64 ">= {} and < {}.0.0",
65 version_to_string(version),
66 version.major + 1
67 );
68
69 // False positive. This package doesn't use the indexing API correctly.
70 #[allow(clippy::indexing_slicing)]
71 {
72 if dev {
73 let canonical_name = "dev_dependencies";
74 let deprecated_name = "dev-dependencies";
75 let has_canonical = gleam_toml.as_table().contains_key(canonical_name);
76 let has_deprecated = gleam_toml.as_table().contains_key(deprecated_name);
77 if !has_canonical && !has_deprecated {
78 gleam_toml["dev_dependencies"] = toml_edit::table();
79 }
80 let name = if has_deprecated {
81 deprecated_name
82 } else {
83 canonical_name
84 };
85 gleam_toml[name][&added_package] = toml_edit::value(range.clone());
86 } else {
87 if !gleam_toml.as_table().contains_key("dependencies") {
88 gleam_toml["dependencies"] = toml_edit::table();
89 }
90 gleam_toml["dependencies"][&added_package] = toml_edit::value(range.clone());
91 };
92 manifest_toml["requirements"][&added_package]["version"] = range.into();
93 }
94 }
95
96 // Write the updated config
97 fs::write(&paths.root_config(), &gleam_toml.to_string())?;
98 fs::write(&paths.manifest(), &manifest_toml.to_string())?;
99
100 Ok(())
101}
102
103fn read_toml_edit(name: &Utf8Path) -> Result<toml_edit::DocumentMut, Error> {
104 fs::read(name)?
105 .parse::<toml_edit::DocumentMut>()
106 .map_err(|e| Error::FileIo {
107 kind: FileKind::File,
108 action: FileIoAction::Parse,
109 path: Utf8PathBuf::from("gleam.toml"),
110 err: Some(e.to_string()),
111 })
112}
113
114fn version_to_string(version: &Version) -> String {
115 let mut text = String::new();
116 text.push_str(&format!(
117 "{}.{}.{}",
118 version.major, version.minor, version.patch
119 ));
120 if !version.pre.is_empty() {
121 text.push('-');
122 for (i, identifier) in version.pre.iter().enumerate() {
123 if i != 0 {
124 text.push('.');
125 }
126 match *identifier {
127 Identifier::Numeric(ref id) => text.push_str(&id.to_string()),
128 Identifier::AlphaNumeric(ref id) => text.push_str(id),
129 }
130 }
131 }
132 if let Some(build) = version.build.as_ref() {
133 text.push_str(&format!("+{build}"));
134 }
135 text
136}
137
138#[test]
139fn displays_simple_version_correctly() {
140 let version = Version {
141 major: 1,
142 minor: 23,
143 patch: 4,
144 pre: vec![],
145 build: None,
146 };
147
148 let displayed = version_to_string(&version);
149
150 assert_eq!(displayed, "1.23.4");
151}
152
153#[test]
154fn displays_full_version_correctly() {
155 let version = Version {
156 major: 1,
157 minor: 23,
158 patch: 4,
159 pre: vec![
160 Identifier::Numeric(123),
161 Identifier::AlphaNumeric("123abc".to_string()),
162 ],
163 build: Some("12345abc".to_string()),
164 };
165
166 let displayed = version_to_string(&version);
167
168 assert_eq!(displayed, "1.23.4-123.123abc+12345abc");
169}