1use std::ops::{Deref, DerefMut};
4use std::path::{Path, PathBuf};
5use std::str;
6
7use anyhow::Context as _;
8
9use super::dependency::Dependency;
10use crate::core::dependency::DepKind;
11use crate::core::{FeatureValue, Features, Workspace};
12use crate::util::closest;
13use crate::util::frontmatter::ScriptSource;
14use crate::util::toml::is_embedded;
15use crate::{CargoResult, GlobalContext};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct DepTable {
20 kind: DepKind,
21 target: Option<String>,
22}
23
24impl DepTable {
25 const KINDS: &'static [Self] = &[
26 Self::new().set_kind(DepKind::Normal),
27 Self::new().set_kind(DepKind::Development),
28 Self::new().set_kind(DepKind::Build),
29 ];
30
31 pub const fn new() -> Self {
33 Self {
34 kind: DepKind::Normal,
35 target: None,
36 }
37 }
38
39 pub const fn set_kind(mut self, kind: DepKind) -> Self {
41 self.kind = kind;
42 self
43 }
44
45 pub fn set_target(mut self, target: impl Into<String>) -> Self {
47 self.target = Some(target.into());
48 self
49 }
50
51 pub fn kind(&self) -> DepKind {
53 self.kind
54 }
55
56 pub fn target(&self) -> Option<&str> {
58 self.target.as_deref()
59 }
60
61 pub fn to_table(&self) -> Vec<&str> {
63 if let Some(target) = &self.target {
64 vec!["target", target, self.kind.kind_table()]
65 } else {
66 vec![self.kind.kind_table()]
67 }
68 }
69}
70
71impl Default for DepTable {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl From<DepKind> for DepTable {
78 fn from(other: DepKind) -> Self {
79 Self::new().set_kind(other)
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct Manifest {
86 pub data: toml_edit::DocumentMut,
88}
89
90impl Manifest {
91 pub fn package_name(&self) -> CargoResult<&str> {
93 self.data
94 .as_table()
95 .get("package")
96 .and_then(|m| m.get("name"))
97 .and_then(|m| m.as_str())
98 .ok_or_else(parse_manifest_err)
99 }
100
101 pub fn get_table<'a>(&'a self, table_path: &[String]) -> Option<&'a toml_edit::Item> {
103 fn descend<'a>(input: &'a toml_edit::Item, path: &[String]) -> Option<&'a toml_edit::Item> {
105 if let Some(segment) = path.get(0) {
106 let value = input.get(&segment)?;
107
108 if value.is_table_like() {
109 descend(value, &path[1..])
110 } else {
111 None
112 }
113 } else {
114 Some(input)
115 }
116 }
117
118 descend(self.data.as_item(), table_path)
119 }
120
121 pub fn get_table_mut<'a>(
123 &'a mut self,
124 table_path: &[String],
125 ) -> Option<&'a mut toml_edit::Item> {
126 fn descend<'a>(
128 input: &'a mut toml_edit::Item,
129 path: &[String],
130 ) -> Option<&'a mut toml_edit::Item> {
131 if let Some(segment) = path.get(0) {
132 let mut default_table = toml_edit::Table::new();
133 default_table.set_implicit(true);
134 let value = input[&segment].or_insert(toml_edit::Item::Table(default_table));
135
136 if value.is_table_like() {
137 descend(value, &path[1..])
138 } else {
139 None
140 }
141 } else {
142 Some(input)
143 }
144 }
145
146 descend(self.data.as_item_mut(), table_path)
147 }
148
149 pub fn get_sections(&self) -> Vec<(DepTable, toml_edit::Item)> {
153 let mut sections = Vec::new();
154
155 for table in DepTable::KINDS {
156 let dependency_type = table.kind.kind_table();
157 if self
159 .data
160 .get(dependency_type)
161 .map(|t| t.is_table_like())
162 .unwrap_or(false)
163 {
164 sections.push((table.clone(), self.data[dependency_type].clone()))
165 }
166
167 let target_sections = self
169 .data
170 .as_table()
171 .get("target")
172 .and_then(toml_edit::Item::as_table_like)
173 .into_iter()
174 .flat_map(toml_edit::TableLike::iter)
175 .filter_map(|(target_name, target_table)| {
176 let dependency_table = target_table.get(dependency_type)?;
177 dependency_table.as_table_like().map(|_| {
178 (
179 table.clone().set_target(target_name),
180 dependency_table.clone(),
181 )
182 })
183 });
184
185 sections.extend(target_sections);
186 }
187
188 sections
189 }
190
191 pub fn get_legacy_sections(&self) -> Vec<String> {
192 let mut result = Vec::new();
193
194 for dependency_type in ["dev_dependencies", "build_dependencies"] {
195 if self.data.contains_key(dependency_type) {
196 result.push(dependency_type.to_owned());
197 }
198
199 result.extend(
201 self.data
202 .as_table()
203 .get("target")
204 .and_then(toml_edit::Item::as_table_like)
205 .into_iter()
206 .flat_map(toml_edit::TableLike::iter)
207 .filter_map(|(target_name, target_table)| {
208 if target_table.as_table_like()?.contains_key(dependency_type) {
209 Some(format!("target.{target_name}.{dependency_type}"))
210 } else {
211 None
212 }
213 }),
214 );
215 }
216 result
217 }
218}
219
220impl str::FromStr for Manifest {
221 type Err = anyhow::Error;
222
223 fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {
225 let d: toml_edit::DocumentMut = input.parse().context("Manifest not valid TOML")?;
226
227 Ok(Manifest { data: d })
228 }
229}
230
231impl std::fmt::Display for Manifest {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 self.data.fmt(f)
234 }
235}
236
237#[derive(Debug, Clone)]
239pub struct LocalManifest {
240 pub path: PathBuf,
242 pub manifest: Manifest,
244 pub raw: String,
246 pub embedded: Option<Embedded>,
248}
249
250impl Deref for LocalManifest {
251 type Target = Manifest;
252
253 fn deref(&self) -> &Manifest {
254 &self.manifest
255 }
256}
257
258impl DerefMut for LocalManifest {
259 fn deref_mut(&mut self) -> &mut Manifest {
260 &mut self.manifest
261 }
262}
263
264impl LocalManifest {
265 pub fn try_new(path: &Path) -> CargoResult<Self> {
267 if !path.is_absolute() {
268 anyhow::bail!("can only edit absolute paths, got {}", path.display());
269 }
270 let raw = cargo_util::paths::read(&path)?;
271 let mut data = raw.clone();
272 let mut embedded = None;
273 if is_embedded(path) {
274 let source = ScriptSource::parse(&data)?;
275 if let Some(frontmatter) = source.frontmatter_span() {
276 embedded = Some(Embedded::exists(frontmatter));
277 data = source.frontmatter().unwrap().to_owned();
278 } else if let Some(shebang) = source.shebang_span() {
279 embedded = Some(Embedded::after(shebang));
280 data = String::new();
281 } else {
282 embedded = Some(Embedded::start());
283 data = String::new();
284 }
285 }
286 let manifest = data.parse().context("Unable to parse Cargo.toml")?;
287 Ok(LocalManifest {
288 manifest,
289 path: path.to_owned(),
290 raw,
291 embedded,
292 })
293 }
294
295 pub fn write(&self) -> CargoResult<()> {
297 let mut manifest = self.manifest.data.to_string();
298 let raw = match self.embedded.as_ref() {
299 Some(Embedded::Implicit(start)) => {
300 if !manifest.ends_with("\n") {
301 manifest.push_str("\n");
302 }
303 let fence = "---\n";
304 let prefix = &self.raw[0..*start];
305 let suffix = &self.raw[*start..];
306 let empty_line = if prefix.is_empty() { "\n" } else { "" };
307 format!("{prefix}{fence}{manifest}{fence}{empty_line}{suffix}")
308 }
309 Some(Embedded::Explicit(span)) => {
310 if !manifest.ends_with("\n") {
311 manifest.push_str("\n");
312 }
313 let prefix = &self.raw[0..span.start];
314 let suffix = &self.raw[span.end..];
315 format!("{prefix}{manifest}{suffix}")
316 }
317 None => manifest,
318 };
319 let new_contents_bytes = raw.as_bytes();
320
321 cargo_util::paths::write_atomic(&self.path, new_contents_bytes)
322 }
323
324 pub fn get_dependencies<'s>(
326 &'s self,
327 ws: &'s Workspace<'_>,
328 unstable_features: &'s Features,
329 ) -> impl Iterator<Item = (String, DepTable, CargoResult<Dependency>)> + 's {
330 let crate_root = self.path.parent().expect("manifest path is absolute");
331 self.get_sections()
332 .into_iter()
333 .filter_map(move |(table_path, table)| {
334 let table = table.into_table().ok()?;
335 Some(
336 table
337 .into_iter()
338 .map(|(key, item)| (table_path.clone(), key, item))
339 .collect::<Vec<_>>(),
340 )
341 })
342 .flatten()
343 .map(move |(table_path, dep_key, dep_item)| {
344 let dep = Dependency::from_toml(
345 ws.gctx(),
346 ws.root(),
347 crate_root,
348 unstable_features,
349 &dep_key,
350 &dep_item,
351 );
352 (dep_key, table_path, dep)
353 })
354 }
355
356 pub fn insert_into_table(
358 &mut self,
359 table_path: &[String],
360 dep: &Dependency,
361 gctx: &GlobalContext,
362 workspace_root: &Path,
363 unstable_features: &Features,
364 ) -> CargoResult<()> {
365 let crate_root = self
366 .path
367 .parent()
368 .expect("manifest path is absolute")
369 .to_owned();
370 let dep_key = dep.toml_key();
371
372 let table = self
373 .get_table_mut(table_path)
374 .expect("manifest validated, path should be to a table");
375 if let Some((mut dep_key, dep_item)) = table
376 .as_table_like_mut()
377 .unwrap()
378 .get_key_value_mut(dep_key)
379 {
380 dep.update_toml(
381 gctx,
382 workspace_root,
383 &crate_root,
384 unstable_features,
385 &mut dep_key,
386 dep_item,
387 )?;
388 if let Some(table) = dep_item.as_inline_table_mut() {
389 table.fmt();
393 }
394 } else {
395 let new_dependency =
396 dep.to_toml(gctx, workspace_root, &crate_root, unstable_features)?;
397 table[dep_key] = new_dependency;
398 }
399
400 Ok(())
401 }
402
403 pub fn remove_from_table(
405 &mut self,
406 table_path: &[String],
407 name: &str,
408 ) -> Result<(), MissingDependencyError> {
409 let parent_table = self
410 .get_table_mut(table_path)
411 .expect("manifest validated, path should be to a table");
412
413 match parent_table.get_mut(name).filter(|t| !t.is_none()) {
414 Some(dep) => {
415 *dep = toml_edit::Item::None;
417
418 if parent_table.as_table_like().unwrap().is_empty() {
420 *parent_table = toml_edit::Item::None;
421 }
422 }
423 None => {
424 let names = parent_table
425 .as_table_like()
426 .map(|t| t.iter())
427 .into_iter()
428 .flatten();
429 let alt_name = closest(name, names.map(|(k, _)| k), |k| k).map(|n| n.to_owned());
430
431 let sections = self.get_sections();
433 let found_table_path = sections.iter().find_map(|(t, i)| {
434 let table_path: Vec<String> =
435 t.to_table().iter().map(|s| s.to_string()).collect();
436 i.get(name).is_some().then(|| table_path)
437 });
438
439 return Err(MissingDependencyError {
440 expected_name: name.to_owned(),
441 expected_path: table_path.to_owned(),
442 alt_name: alt_name,
443 alt_path: found_table_path,
444 });
445 }
446 }
447
448 Ok(())
449 }
450
451 pub fn get_dependency_tables_mut(
454 &mut self,
455 ) -> impl Iterator<Item = &mut dyn toml_edit::TableLike> + '_ {
456 let root = self.data.as_table_mut();
457 root.iter_mut().flat_map(|(k, v)| {
458 if DepTable::KINDS
459 .iter()
460 .any(|dt| dt.kind.kind_table() == k.get())
461 {
462 v.as_table_like_mut().into_iter().collect::<Vec<_>>()
463 } else if k == "workspace" {
464 v.as_table_like_mut()
465 .unwrap()
466 .iter_mut()
467 .filter_map(|(k, v)| {
468 if k.get() == "dependencies" {
469 v.as_table_like_mut()
470 } else {
471 None
472 }
473 })
474 .collect::<Vec<_>>()
475 } else if k == "target" {
476 v.as_table_like_mut()
477 .unwrap()
478 .iter_mut()
479 .flat_map(|(_, v)| {
480 v.as_table_like_mut().into_iter().flat_map(|v| {
481 v.iter_mut().filter_map(|(k, v)| {
482 if DepTable::KINDS
483 .iter()
484 .any(|dt| dt.kind.kind_table() == k.get())
485 {
486 v.as_table_like_mut()
487 } else {
488 None
489 }
490 })
491 })
492 })
493 .collect::<Vec<_>>()
494 } else {
495 Vec::new()
496 }
497 })
498 }
499
500 pub fn gc_dep(&mut self, dep_key: &str) {
502 let explicit_dep_activation = self.is_explicit_dep_activation(dep_key);
503 let status = self.dep_status(dep_key);
504
505 if let Some(toml_edit::Item::Table(feature_table)) =
506 self.data.as_table_mut().get_mut("features")
507 {
508 for (_feature, mut feature_values) in feature_table.iter_mut() {
509 if let toml_edit::Item::Value(toml_edit::Value::Array(feature_values)) =
510 &mut feature_values
511 {
512 fix_feature_activations(
513 feature_values,
514 dep_key,
515 status,
516 explicit_dep_activation,
517 );
518 }
519 }
520 }
521 }
522
523 pub fn is_explicit_dep_activation(&self, dep_key: &str) -> bool {
524 if let Some(toml_edit::Item::Table(feature_table)) = self.data.as_table().get("features") {
525 for values in feature_table
526 .iter()
527 .map(|(_, a)| a)
528 .filter_map(|i| i.as_value())
529 .filter_map(|v| v.as_array())
530 {
531 for value in values.iter().filter_map(|v| v.as_str()) {
532 let value = FeatureValue::new(value.into());
533 if let FeatureValue::Dep { dep_name } = &value {
534 if dep_name.as_str() == dep_key {
535 return true;
536 }
537 }
538 }
539 }
540 }
541
542 false
543 }
544
545 fn dep_status(&self, dep_key: &str) -> DependencyStatus {
546 let mut status = DependencyStatus::None;
547 for (_, tbl) in self.get_sections() {
548 if let toml_edit::Item::Table(tbl) = tbl {
549 if let Some(dep_item) = tbl.get(dep_key) {
550 let optional = dep_item
551 .get("optional")
552 .and_then(|i| i.as_value())
553 .and_then(|i| i.as_bool())
554 .unwrap_or(false);
555 if optional {
556 return DependencyStatus::Optional;
557 } else {
558 status = DependencyStatus::Required;
559 }
560 }
561 }
562 }
563 status
564 }
565}
566
567impl std::fmt::Display for LocalManifest {
568 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569 self.manifest.fmt(f)
570 }
571}
572
573#[derive(Clone, Debug)]
575pub enum Embedded {
576 Implicit(usize),
580 Explicit(std::ops::Range<usize>),
584}
585
586impl Embedded {
587 fn start() -> Self {
588 Self::Implicit(0)
589 }
590
591 fn after(after: std::ops::Range<usize>) -> Self {
592 Self::Implicit(after.end)
593 }
594
595 fn exists(exists: std::ops::Range<usize>) -> Self {
596 Self::Explicit(exists)
597 }
598}
599
600#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
601enum DependencyStatus {
602 None,
603 Optional,
604 Required,
605}
606
607fn fix_feature_activations(
608 feature_values: &mut toml_edit::Array,
609 dep_key: &str,
610 status: DependencyStatus,
611 explicit_dep_activation: bool,
612) {
613 let remove_list: Vec<usize> = feature_values
614 .iter()
615 .enumerate()
616 .filter_map(|(idx, value)| value.as_str().map(|s| (idx, s)))
617 .filter_map(|(idx, value)| {
618 let parsed_value = FeatureValue::new(value.into());
619 match status {
620 DependencyStatus::None => match (parsed_value, explicit_dep_activation) {
621 (FeatureValue::Feature(dep_name), false)
622 | (FeatureValue::Dep { dep_name }, _)
623 | (FeatureValue::DepFeature { dep_name, .. }, _) => dep_name == dep_key,
624 _ => false,
625 },
626 DependencyStatus::Optional => false,
627 DependencyStatus::Required => match (parsed_value, explicit_dep_activation) {
628 (FeatureValue::Feature(dep_name), false)
629 | (FeatureValue::Dep { dep_name }, _) => dep_name == dep_key,
630 (FeatureValue::Feature(_), true) | (FeatureValue::DepFeature { .. }, _) => {
631 false
632 }
633 },
634 }
635 .then(|| idx)
636 })
637 .collect();
638
639 for idx in remove_list.iter().rev() {
641 remove_array_index(feature_values, *idx);
642 }
643
644 if status == DependencyStatus::Required {
645 for value in feature_values.iter_mut() {
646 let parsed_value = if let Some(value) = value.as_str() {
647 FeatureValue::new(value.into())
648 } else {
649 continue;
650 };
651 if let FeatureValue::DepFeature {
652 dep_name,
653 dep_feature,
654 weak,
655 } = parsed_value
656 {
657 if dep_name == dep_key && weak {
658 let mut new_value = toml_edit::Value::from(format!("{dep_name}/{dep_feature}"));
659 *new_value.decor_mut() = value.decor().clone();
660 *value = new_value;
661 }
662 }
663 }
664 }
665}
666
667pub fn str_or_1_len_table(item: &toml_edit::Item) -> bool {
668 item.is_str() || item.as_table_like().map(|t| t.len() == 1).unwrap_or(false)
669}
670
671fn parse_manifest_err() -> anyhow::Error {
672 anyhow::format_err!("unable to parse external Cargo.toml")
673}
674
675#[derive(Debug)]
676pub struct MissingDependencyError {
677 pub expected_name: String,
678 pub expected_path: Vec<String>,
679 pub alt_path: Option<Vec<String>>,
680 pub alt_name: Option<String>,
681}
682
683impl std::fmt::Display for MissingDependencyError {
684 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685 let expected_name = &self.expected_name;
686 let expected_path = self.expected_path.join(".");
687 write!(
688 fmt,
689 "the dependency `{expected_name}` could not be found in `{expected_path}`"
690 )?;
691 if let Some(alt_path) = &self.alt_path {
692 let alt_path = alt_path.join(".");
693 write!(
694 fmt,
695 "\n\nhelp: a dependency with the same name exists in `{alt_path}`"
696 )?;
697 } else if let Some(alt_name) = &self.alt_name {
698 write!(
699 fmt,
700 "\n\nhelp: a dependency with a similar name exists: `{alt_name}`"
701 )?;
702 }
703 Ok(())
704 }
705}
706
707impl std::error::Error for MissingDependencyError {}
708
709fn remove_array_index(array: &mut toml_edit::Array, index: usize) {
710 let value = array.remove(index);
711
712 let prefix_lines = value
714 .decor()
715 .prefix()
716 .and_then(|p| p.as_str().expect("spans removed").rsplit_once('\n'))
717 .map(|(lines, _current)| lines);
718 let suffix_lines = value
720 .decor()
721 .suffix()
722 .and_then(|p| p.as_str().expect("spans removed").split_once('\n'))
723 .map(|(_current, lines)| lines);
724 let mut merged_lines = String::new();
725 if let Some(prefix_lines) = prefix_lines {
726 merged_lines.push_str(prefix_lines);
727 merged_lines.push('\n');
728 }
729 if let Some(suffix_lines) = suffix_lines {
730 merged_lines.push_str(suffix_lines);
731 merged_lines.push('\n');
732 }
733
734 let next_index = index; if let Some(next) = array.get_mut(next_index) {
736 let next_decor = next.decor_mut();
737 let next_prefix = next_decor
738 .prefix()
739 .map(|s| s.as_str().expect("spans removed"))
740 .unwrap_or_default();
741 merged_lines.push_str(next_prefix);
742 next_decor.set_prefix(merged_lines);
743 } else {
744 let trailing = array.trailing().as_str().expect("spans removed");
745 merged_lines.push_str(trailing);
746 array.set_trailing(merged_lines);
747 }
748}