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