]> git.proxmox.com Git - rustc.git/blob - src/tools/cargo/src/cargo/util/toml_mut/manifest.rs
New upstream version 1.70.0+dfsg2
[rustc.git] / src / tools / cargo / src / cargo / util / toml_mut / manifest.rs
1 //! Parsing and editing of manifest files.
2
3 use std::ops::{Deref, DerefMut};
4 use std::path::{Path, PathBuf};
5 use std::str;
6
7 use anyhow::Context as _;
8
9 use super::dependency::Dependency;
10 use crate::core::dependency::DepKind;
11 use crate::core::FeatureValue;
12 use crate::util::interning::InternedString;
13 use crate::CargoResult;
14
15 /// Dependency table to add deps to.
16 #[derive(Clone, Debug, PartialEq, Eq)]
17 pub struct DepTable {
18 kind: DepKind,
19 target: Option<String>,
20 }
21
22 impl DepTable {
23 const KINDS: &'static [Self] = &[
24 Self::new().set_kind(DepKind::Normal),
25 Self::new().set_kind(DepKind::Development),
26 Self::new().set_kind(DepKind::Build),
27 ];
28
29 /// Reference to a Dependency Table.
30 pub const fn new() -> Self {
31 Self {
32 kind: DepKind::Normal,
33 target: None,
34 }
35 }
36
37 /// Choose the type of dependency.
38 pub const fn set_kind(mut self, kind: DepKind) -> Self {
39 self.kind = kind;
40 self
41 }
42
43 /// Choose the platform for the dependency.
44 pub fn set_target(mut self, target: impl Into<String>) -> Self {
45 self.target = Some(target.into());
46 self
47 }
48
49 /// Type of dependency.
50 pub fn kind(&self) -> DepKind {
51 self.kind
52 }
53
54 /// Platform for the dependency.
55 pub fn target(&self) -> Option<&str> {
56 self.target.as_deref()
57 }
58
59 /// Keys to the table.
60 pub fn to_table(&self) -> Vec<&str> {
61 if let Some(target) = &self.target {
62 vec!["target", target, self.kind.kind_table()]
63 } else {
64 vec![self.kind.kind_table()]
65 }
66 }
67 }
68
69 impl Default for DepTable {
70 fn default() -> Self {
71 Self::new()
72 }
73 }
74
75 impl From<DepKind> for DepTable {
76 fn from(other: DepKind) -> Self {
77 Self::new().set_kind(other)
78 }
79 }
80
81 /// An editable Cargo manifest.
82 #[derive(Debug, Clone)]
83 pub struct Manifest {
84 /// Manifest contents as TOML data.
85 pub data: toml_edit::Document,
86 }
87
88 impl Manifest {
89 /// Get the manifest's package name.
90 pub fn package_name(&self) -> CargoResult<&str> {
91 self.data
92 .as_table()
93 .get("package")
94 .and_then(|m| m.get("name"))
95 .and_then(|m| m.as_str())
96 .ok_or_else(parse_manifest_err)
97 }
98
99 /// Get the specified table from the manifest.
100 pub fn get_table<'a>(&'a self, table_path: &[String]) -> CargoResult<&'a toml_edit::Item> {
101 /// Descend into a manifest until the required table is found.
102 fn descend<'a>(
103 input: &'a toml_edit::Item,
104 path: &[String],
105 ) -> CargoResult<&'a toml_edit::Item> {
106 if let Some(segment) = path.get(0) {
107 let value = input
108 .get(&segment)
109 .ok_or_else(|| non_existent_table_err(segment))?;
110
111 if value.is_table_like() {
112 descend(value, &path[1..])
113 } else {
114 Err(non_existent_table_err(segment))
115 }
116 } else {
117 Ok(input)
118 }
119 }
120
121 descend(self.data.as_item(), table_path)
122 }
123
124 /// Get the specified table from the manifest.
125 pub fn get_table_mut<'a>(
126 &'a mut self,
127 table_path: &[String],
128 ) -> CargoResult<&'a mut toml_edit::Item> {
129 /// Descend into a manifest until the required table is found.
130 fn descend<'a>(
131 input: &'a mut toml_edit::Item,
132 path: &[String],
133 ) -> CargoResult<&'a mut toml_edit::Item> {
134 if let Some(segment) = path.get(0) {
135 let mut default_table = toml_edit::Table::new();
136 default_table.set_implicit(true);
137 let value = input[&segment].or_insert(toml_edit::Item::Table(default_table));
138
139 if value.is_table_like() {
140 descend(value, &path[1..])
141 } else {
142 Err(non_existent_table_err(segment))
143 }
144 } else {
145 Ok(input)
146 }
147 }
148
149 descend(self.data.as_item_mut(), table_path)
150 }
151
152 /// Get all sections in the manifest that exist and might contain
153 /// dependencies. The returned items are always `Table` or
154 /// `InlineTable`.
155 pub fn get_sections(&self) -> Vec<(DepTable, toml_edit::Item)> {
156 let mut sections = Vec::new();
157
158 for table in DepTable::KINDS {
159 let dependency_type = table.kind.kind_table();
160 // Dependencies can be in the three standard sections...
161 if self
162 .data
163 .get(dependency_type)
164 .map(|t| t.is_table_like())
165 .unwrap_or(false)
166 {
167 sections.push((table.clone(), self.data[dependency_type].clone()))
168 }
169
170 // ... and in `target.<target>.(build-/dev-)dependencies`.
171 let target_sections = self
172 .data
173 .as_table()
174 .get("target")
175 .and_then(toml_edit::Item::as_table_like)
176 .into_iter()
177 .flat_map(toml_edit::TableLike::iter)
178 .filter_map(|(target_name, target_table)| {
179 let dependency_table = target_table.get(dependency_type)?;
180 dependency_table.as_table_like().map(|_| {
181 (
182 table.clone().set_target(target_name),
183 dependency_table.clone(),
184 )
185 })
186 });
187
188 sections.extend(target_sections);
189 }
190
191 sections
192 }
193
194 pub fn get_legacy_sections(&self) -> Vec<String> {
195 let mut result = Vec::new();
196
197 for dependency_type in ["dev_dependencies", "build_dependencies"] {
198 if self.data.contains_key(dependency_type) {
199 result.push(dependency_type.to_owned());
200 }
201
202 // ... and in `target.<target>.(build-/dev-)dependencies`.
203 result.extend(
204 self.data
205 .as_table()
206 .get("target")
207 .and_then(toml_edit::Item::as_table_like)
208 .into_iter()
209 .flat_map(toml_edit::TableLike::iter)
210 .filter_map(|(target_name, target_table)| {
211 if target_table.as_table_like()?.contains_key(dependency_type) {
212 Some(format!("target.{target_name}.{dependency_type}"))
213 } else {
214 None
215 }
216 }),
217 );
218 }
219 result
220 }
221 }
222
223 impl str::FromStr for Manifest {
224 type Err = anyhow::Error;
225
226 /// Read manifest data from string
227 fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {
228 let d: toml_edit::Document = input.parse().context("Manifest not valid TOML")?;
229
230 Ok(Manifest { data: d })
231 }
232 }
233
234 impl std::fmt::Display for Manifest {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 self.data.fmt(f)
237 }
238 }
239
240 /// An editable Cargo manifest that is available locally.
241 #[derive(Debug, Clone)]
242 pub struct LocalManifest {
243 /// Path to the manifest.
244 pub path: PathBuf,
245 /// Manifest contents.
246 pub manifest: Manifest,
247 }
248
249 impl Deref for LocalManifest {
250 type Target = Manifest;
251
252 fn deref(&self) -> &Manifest {
253 &self.manifest
254 }
255 }
256
257 impl DerefMut for LocalManifest {
258 fn deref_mut(&mut self) -> &mut Manifest {
259 &mut self.manifest
260 }
261 }
262
263 impl LocalManifest {
264 /// Construct the `LocalManifest` corresponding to the `Path` provided..
265 pub fn try_new(path: &Path) -> CargoResult<Self> {
266 if !path.is_absolute() {
267 anyhow::bail!("can only edit absolute paths, got {}", path.display());
268 }
269 let data = cargo_util::paths::read(&path)?;
270 let manifest = data.parse().context("Unable to parse Cargo.toml")?;
271 Ok(LocalManifest {
272 manifest,
273 path: path.to_owned(),
274 })
275 }
276
277 /// Write changes back to the file.
278 pub fn write(&self) -> CargoResult<()> {
279 if !self.manifest.data.contains_key("package")
280 && !self.manifest.data.contains_key("project")
281 {
282 if self.manifest.data.contains_key("workspace") {
283 anyhow::bail!(
284 "found virtual manifest at {}, but this command requires running against an \
285 actual package in this workspace.",
286 self.path.display()
287 );
288 } else {
289 anyhow::bail!(
290 "missing expected `package` or `project` fields in {}",
291 self.path.display()
292 );
293 }
294 }
295
296 let s = self.manifest.data.to_string();
297 let new_contents_bytes = s.as_bytes();
298
299 cargo_util::paths::write(&self.path, new_contents_bytes)
300 }
301
302 /// Lookup a dependency.
303 pub fn get_dependency_versions<'s>(
304 &'s self,
305 dep_key: &'s str,
306 ) -> impl Iterator<Item = (DepTable, CargoResult<Dependency>)> + 's {
307 let crate_root = self.path.parent().expect("manifest path is absolute");
308 self.get_sections()
309 .into_iter()
310 .filter_map(move |(table_path, table)| {
311 let table = table.into_table().ok()?;
312 Some(
313 table
314 .into_iter()
315 .filter_map(|(key, item)| {
316 if key.as_str() == dep_key {
317 Some((table_path.clone(), key, item))
318 } else {
319 None
320 }
321 })
322 .collect::<Vec<_>>(),
323 )
324 })
325 .flatten()
326 .map(move |(table_path, dep_key, dep_item)| {
327 let dep = Dependency::from_toml(crate_root, &dep_key, &dep_item);
328 (table_path, dep)
329 })
330 }
331
332 /// Add entry to a Cargo.toml.
333 pub fn insert_into_table(
334 &mut self,
335 table_path: &[String],
336 dep: &Dependency,
337 ) -> CargoResult<()> {
338 let crate_root = self
339 .path
340 .parent()
341 .expect("manifest path is absolute")
342 .to_owned();
343 let dep_key = dep.toml_key();
344
345 let table = self.get_table_mut(table_path)?;
346 if let Some((mut dep_key, dep_item)) = table
347 .as_table_like_mut()
348 .unwrap()
349 .get_key_value_mut(dep_key)
350 {
351 dep.update_toml(&crate_root, &mut dep_key, dep_item);
352 } else {
353 let new_dependency = dep.to_toml(&crate_root);
354 table[dep_key] = new_dependency;
355 }
356 if let Some(t) = table.as_inline_table_mut() {
357 t.fmt()
358 }
359
360 Ok(())
361 }
362
363 /// Remove entry from a Cargo.toml.
364 pub fn remove_from_table(&mut self, table_path: &[String], name: &str) -> CargoResult<()> {
365 let parent_table = self.get_table_mut(table_path)?;
366
367 let dep = parent_table
368 .get_mut(name)
369 .filter(|t| !t.is_none())
370 .ok_or_else(|| non_existent_dependency_err(name, table_path.join(".")))?;
371
372 // remove the dependency
373 *dep = toml_edit::Item::None;
374
375 // remove table if empty
376 if parent_table.as_table_like().unwrap().is_empty() {
377 *parent_table = toml_edit::Item::None;
378 }
379
380 Ok(())
381 }
382
383 /// Remove references to `dep_key` if its no longer present.
384 pub fn gc_dep(&mut self, dep_key: &str) {
385 let explicit_dep_activation = self.is_explicit_dep_activation(dep_key);
386 let status = self.dep_status(dep_key);
387
388 if let Some(toml_edit::Item::Table(feature_table)) =
389 self.data.as_table_mut().get_mut("features")
390 {
391 for (_feature, mut feature_values) in feature_table.iter_mut() {
392 if let toml_edit::Item::Value(toml_edit::Value::Array(feature_values)) =
393 &mut feature_values
394 {
395 fix_feature_activations(
396 feature_values,
397 dep_key,
398 status,
399 explicit_dep_activation,
400 );
401 }
402 }
403 }
404 }
405
406 fn is_explicit_dep_activation(&self, dep_key: &str) -> bool {
407 if let Some(toml_edit::Item::Table(feature_table)) = self.data.as_table().get("features") {
408 for values in feature_table
409 .iter()
410 .map(|(_, a)| a)
411 .filter_map(|i| i.as_value())
412 .filter_map(|v| v.as_array())
413 {
414 for value in values.iter().filter_map(|v| v.as_str()) {
415 let value = FeatureValue::new(InternedString::new(value));
416 if let FeatureValue::Dep { dep_name } = &value {
417 if dep_name.as_str() == dep_key {
418 return true;
419 }
420 }
421 }
422 }
423 }
424
425 false
426 }
427
428 fn dep_status(&self, dep_key: &str) -> DependencyStatus {
429 let mut status = DependencyStatus::None;
430 for (_, tbl) in self.get_sections() {
431 if let toml_edit::Item::Table(tbl) = tbl {
432 if let Some(dep_item) = tbl.get(dep_key) {
433 let optional = dep_item
434 .get("optional")
435 .and_then(|i| i.as_value())
436 .and_then(|i| i.as_bool())
437 .unwrap_or(false);
438 if optional {
439 return DependencyStatus::Optional;
440 } else {
441 status = DependencyStatus::Required;
442 }
443 }
444 }
445 }
446 status
447 }
448 }
449
450 impl std::fmt::Display for LocalManifest {
451 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452 self.manifest.fmt(f)
453 }
454 }
455
456 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
457 enum DependencyStatus {
458 None,
459 Optional,
460 Required,
461 }
462
463 fn fix_feature_activations(
464 feature_values: &mut toml_edit::Array,
465 dep_key: &str,
466 status: DependencyStatus,
467 explicit_dep_activation: bool,
468 ) {
469 let remove_list: Vec<usize> = feature_values
470 .iter()
471 .enumerate()
472 .filter_map(|(idx, value)| value.as_str().map(|s| (idx, s)))
473 .filter_map(|(idx, value)| {
474 let parsed_value = FeatureValue::new(InternedString::new(value));
475 match status {
476 DependencyStatus::None => match (parsed_value, explicit_dep_activation) {
477 (FeatureValue::Feature(dep_name), false)
478 | (FeatureValue::Dep { dep_name }, _)
479 | (FeatureValue::DepFeature { dep_name, .. }, _) => dep_name == dep_key,
480 _ => false,
481 },
482 DependencyStatus::Optional => false,
483 DependencyStatus::Required => match (parsed_value, explicit_dep_activation) {
484 (FeatureValue::Feature(dep_name), false)
485 | (FeatureValue::Dep { dep_name }, _) => dep_name == dep_key,
486 (FeatureValue::Feature(_), true) | (FeatureValue::DepFeature { .. }, _) => {
487 false
488 }
489 },
490 }
491 .then(|| idx)
492 })
493 .collect();
494
495 // Remove found idx in revers order so we don't invalidate the idx.
496 for idx in remove_list.iter().rev() {
497 feature_values.remove(*idx);
498 }
499
500 if status == DependencyStatus::Required {
501 for value in feature_values.iter_mut() {
502 let parsed_value = if let Some(value) = value.as_str() {
503 FeatureValue::new(InternedString::new(value))
504 } else {
505 continue;
506 };
507 if let FeatureValue::DepFeature {
508 dep_name,
509 dep_feature,
510 weak,
511 } = parsed_value
512 {
513 if dep_name == dep_key && weak {
514 *value = format!("{dep_name}/{dep_feature}").into();
515 }
516 }
517 }
518 }
519
520 feature_values.fmt();
521 }
522
523 pub fn str_or_1_len_table(item: &toml_edit::Item) -> bool {
524 item.is_str() || item.as_table_like().map(|t| t.len() == 1).unwrap_or(false)
525 }
526
527 fn parse_manifest_err() -> anyhow::Error {
528 anyhow::format_err!("unable to parse external Cargo.toml")
529 }
530
531 fn non_existent_table_err(table: impl std::fmt::Display) -> anyhow::Error {
532 anyhow::format_err!("the table `{table}` could not be found.")
533 }
534
535 fn non_existent_dependency_err(
536 name: impl std::fmt::Display,
537 table: impl std::fmt::Display,
538 ) -> anyhow::Error {
539 anyhow::format_err!("the dependency `{name}` could not be found in `{table}`.")
540 }