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 ensure_edition(&mut self) -> bool {
357 if self.embedded.is_none() {
358 return false;
359 }
360
361 let root = self.data.as_table_mut();
362 let package = root.entry("package").or_insert_with(|| {
363 let mut t = toml_edit::Table::new();
364 t.set_position(Some(-1));
365 t.into()
366 });
367 let Some(package) = package.as_table_like_mut() else {
368 return false;
369 };
370
371 let mut changed = false;
372 package.entry("edition").or_insert_with(|| {
373 changed = true;
374 crate::core::features::Edition::LATEST_STABLE
375 .to_string()
376 .into()
377 });
378
379 changed
380 }
381
382 pub fn insert_into_table(
384 &mut self,
385 table_path: &[String],
386 dep: &Dependency,
387 gctx: &GlobalContext,
388 workspace_root: &Path,
389 unstable_features: &Features,
390 ) -> CargoResult<()> {
391 let crate_root = self
392 .path
393 .parent()
394 .expect("manifest path is absolute")
395 .to_owned();
396 let dep_key = dep.toml_key();
397
398 let table = self
399 .get_table_mut(table_path)
400 .expect("manifest validated, path should be to a table");
401 if let Some((mut dep_key, dep_item)) = table
402 .as_table_like_mut()
403 .unwrap()
404 .get_key_value_mut(dep_key)
405 {
406 dep.update_toml(
407 gctx,
408 workspace_root,
409 &crate_root,
410 unstable_features,
411 &mut dep_key,
412 dep_item,
413 )?;
414 if let Some(table) = dep_item.as_inline_table_mut() {
415 table.fmt();
419 }
420 } else {
421 let new_dependency =
422 dep.to_toml(gctx, workspace_root, &crate_root, unstable_features)?;
423 table[dep_key] = new_dependency;
424 }
425
426 Ok(())
427 }
428
429 pub fn remove_from_table(
431 &mut self,
432 table_path: &[String],
433 name: &str,
434 ) -> Result<(), MissingDependencyError> {
435 let parent_table = self
436 .get_table_mut(table_path)
437 .expect("manifest validated, path should be to a table");
438
439 match parent_table.get_mut(name).filter(|t| !t.is_none()) {
440 Some(dep) => {
441 *dep = toml_edit::Item::None;
443
444 if parent_table.as_table_like().unwrap().is_empty() {
446 *parent_table = toml_edit::Item::None;
447 }
448 }
449 None => {
450 let names = parent_table
451 .as_table_like()
452 .map(|t| t.iter())
453 .into_iter()
454 .flatten();
455 let alt_name = closest(name, names.map(|(k, _)| k), |k| k).map(|n| n.to_owned());
456
457 let sections = self.get_sections();
459 let found_table_path = sections.iter().find_map(|(t, i)| {
460 let table_path: Vec<String> =
461 t.to_table().iter().map(|s| s.to_string()).collect();
462 i.get(name).is_some().then(|| table_path)
463 });
464
465 return Err(MissingDependencyError {
466 expected_name: name.to_owned(),
467 expected_path: table_path.to_owned(),
468 alt_name: alt_name,
469 alt_path: found_table_path,
470 });
471 }
472 }
473
474 Ok(())
475 }
476
477 pub fn get_dependency_tables_mut(
480 &mut self,
481 ) -> impl Iterator<Item = &mut dyn toml_edit::TableLike> + '_ {
482 let root = self.data.as_table_mut();
483 root.iter_mut().flat_map(|(k, v)| {
484 if DepTable::KINDS
485 .iter()
486 .any(|dt| dt.kind.kind_table() == k.get())
487 {
488 v.as_table_like_mut().into_iter().collect::<Vec<_>>()
489 } else if k == "workspace" {
490 v.as_table_like_mut()
491 .unwrap()
492 .iter_mut()
493 .filter_map(|(k, v)| {
494 if k.get() == "dependencies" {
495 v.as_table_like_mut()
496 } else {
497 None
498 }
499 })
500 .collect::<Vec<_>>()
501 } else if k == "target" {
502 v.as_table_like_mut()
503 .unwrap()
504 .iter_mut()
505 .flat_map(|(_, v)| {
506 v.as_table_like_mut().into_iter().flat_map(|v| {
507 v.iter_mut().filter_map(|(k, v)| {
508 if DepTable::KINDS
509 .iter()
510 .any(|dt| dt.kind.kind_table() == k.get())
511 {
512 v.as_table_like_mut()
513 } else {
514 None
515 }
516 })
517 })
518 })
519 .collect::<Vec<_>>()
520 } else {
521 Vec::new()
522 }
523 })
524 }
525
526 pub fn gc_dep(&mut self, dep_key: &str) {
528 let explicit_dep_activation = self.is_explicit_dep_activation(dep_key);
529 let status = self.dep_status(dep_key);
530
531 if let Some(toml_edit::Item::Table(feature_table)) =
532 self.data.as_table_mut().get_mut("features")
533 {
534 for (_feature, mut feature_values) in feature_table.iter_mut() {
535 if let toml_edit::Item::Value(toml_edit::Value::Array(feature_values)) =
536 &mut feature_values
537 {
538 fix_feature_activations(
539 feature_values,
540 dep_key,
541 status,
542 explicit_dep_activation,
543 );
544 }
545 }
546 }
547 }
548
549 pub fn is_explicit_dep_activation(&self, dep_key: &str) -> bool {
550 if let Some(toml_edit::Item::Table(feature_table)) = self.data.as_table().get("features") {
551 for values in feature_table
552 .iter()
553 .map(|(_, a)| a)
554 .filter_map(|i| i.as_value())
555 .filter_map(|v| v.as_array())
556 {
557 for value in values.iter().filter_map(|v| v.as_str()) {
558 let value = FeatureValue::new(value.into());
559 if let FeatureValue::Dep { dep_name } = &value {
560 if dep_name.as_str() == dep_key {
561 return true;
562 }
563 }
564 }
565 }
566 }
567
568 false
569 }
570
571 fn dep_status(&self, dep_key: &str) -> DependencyStatus {
572 let mut status = DependencyStatus::None;
573 for (_, tbl) in self.get_sections() {
574 if let toml_edit::Item::Table(tbl) = tbl {
575 if let Some(dep_item) = tbl.get(dep_key) {
576 let optional = dep_item
577 .get("optional")
578 .and_then(|i| i.as_value())
579 .and_then(|i| i.as_bool())
580 .unwrap_or(false);
581 if optional {
582 return DependencyStatus::Optional;
583 } else {
584 status = DependencyStatus::Required;
585 }
586 }
587 }
588 }
589 status
590 }
591}
592
593impl std::fmt::Display for LocalManifest {
594 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
595 self.manifest.fmt(f)
596 }
597}
598
599#[derive(Clone, Debug)]
601pub enum Embedded {
602 Implicit(usize),
606 Explicit(std::ops::Range<usize>),
610}
611
612impl Embedded {
613 fn start() -> Self {
614 Self::Implicit(0)
615 }
616
617 fn after(after: std::ops::Range<usize>) -> Self {
618 Self::Implicit(after.end)
619 }
620
621 fn exists(exists: std::ops::Range<usize>) -> Self {
622 Self::Explicit(exists)
623 }
624}
625
626#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
627enum DependencyStatus {
628 None,
629 Optional,
630 Required,
631}
632
633fn fix_feature_activations(
634 feature_values: &mut toml_edit::Array,
635 dep_key: &str,
636 status: DependencyStatus,
637 explicit_dep_activation: bool,
638) {
639 let remove_list: Vec<usize> = feature_values
640 .iter()
641 .enumerate()
642 .filter_map(|(idx, value)| value.as_str().map(|s| (idx, s)))
643 .filter_map(|(idx, value)| {
644 let parsed_value = FeatureValue::new(value.into());
645 match status {
646 DependencyStatus::None => match (parsed_value, explicit_dep_activation) {
647 (FeatureValue::Feature(dep_name), false)
648 | (FeatureValue::Dep { dep_name }, _)
649 | (FeatureValue::DepFeature { dep_name, .. }, _) => dep_name == dep_key,
650 _ => false,
651 },
652 DependencyStatus::Optional => false,
653 DependencyStatus::Required => match (parsed_value, explicit_dep_activation) {
654 (FeatureValue::Feature(dep_name), false)
655 | (FeatureValue::Dep { dep_name }, _) => dep_name == dep_key,
656 (FeatureValue::Feature(_), true) | (FeatureValue::DepFeature { .. }, _) => {
657 false
658 }
659 },
660 }
661 .then(|| idx)
662 })
663 .collect();
664
665 for idx in remove_list.iter().rev() {
667 remove_array_index(feature_values, *idx);
668 }
669
670 if status == DependencyStatus::Required {
671 for value in feature_values.iter_mut() {
672 let parsed_value = if let Some(value) = value.as_str() {
673 FeatureValue::new(value.into())
674 } else {
675 continue;
676 };
677 if let FeatureValue::DepFeature {
678 dep_name,
679 dep_feature,
680 weak,
681 } = parsed_value
682 {
683 if dep_name == dep_key && weak {
684 let mut new_value = toml_edit::Value::from(format!("{dep_name}/{dep_feature}"));
685 *new_value.decor_mut() = value.decor().clone();
686 *value = new_value;
687 }
688 }
689 }
690 }
691}
692
693pub fn str_or_1_len_table(item: &toml_edit::Item) -> bool {
694 item.is_str() || item.as_table_like().map(|t| t.len() == 1).unwrap_or(false)
695}
696
697fn parse_manifest_err() -> anyhow::Error {
698 anyhow::format_err!("unable to parse external Cargo.toml")
699}
700
701#[derive(Debug)]
702pub struct MissingDependencyError {
703 pub expected_name: String,
704 pub expected_path: Vec<String>,
705 pub alt_path: Option<Vec<String>>,
706 pub alt_name: Option<String>,
707}
708
709impl std::fmt::Display for MissingDependencyError {
710 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
711 let expected_name = &self.expected_name;
712 let expected_path = self.expected_path.join(".");
713 write!(
714 fmt,
715 "the dependency `{expected_name}` could not be found in `{expected_path}`"
716 )?;
717 if let Some(alt_path) = &self.alt_path {
718 let alt_path = alt_path.join(".");
719 write!(
720 fmt,
721 "\n\nhelp: a dependency with the same name exists in `{alt_path}`"
722 )?;
723 } else if let Some(alt_name) = &self.alt_name {
724 write!(
725 fmt,
726 "\n\nhelp: a dependency with a similar name exists: `{alt_name}`"
727 )?;
728 }
729 Ok(())
730 }
731}
732
733impl std::error::Error for MissingDependencyError {}
734
735fn remove_array_index(array: &mut toml_edit::Array, index: usize) {
736 let value = array.remove(index);
737
738 let prefix_lines = value
740 .decor()
741 .prefix()
742 .and_then(|p| p.as_str().expect("spans removed").rsplit_once('\n'))
743 .map(|(lines, _current)| lines);
744 let suffix_lines = value
746 .decor()
747 .suffix()
748 .and_then(|p| p.as_str().expect("spans removed").split_once('\n'))
749 .map(|(_current, lines)| lines);
750 let mut merged_lines = String::new();
751 if let Some(prefix_lines) = prefix_lines {
752 merged_lines.push_str(prefix_lines);
753 merged_lines.push('\n');
754 }
755 if let Some(suffix_lines) = suffix_lines {
756 merged_lines.push_str(suffix_lines);
757 merged_lines.push('\n');
758 }
759
760 let next_index = index; if let Some(next) = array.get_mut(next_index) {
762 let next_decor = next.decor_mut();
763 let next_prefix = next_decor
764 .prefix()
765 .map(|s| s.as_str().expect("spans removed"))
766 .unwrap_or_default();
767 merged_lines.push_str(next_prefix);
768 next_decor.set_prefix(merged_lines);
769 } else {
770 let trailing = array.trailing().as_str().expect("spans removed");
771 merged_lines.push_str(trailing);
772 array.set_trailing(merged_lines);
773 }
774}