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