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