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