1 //! Parsing and editing of manifest files.
3 use std
::ops
::{Deref, DerefMut}
;
4 use std
::path
::{Path, PathBuf}
;
7 use anyhow
::Context
as _
;
9 use super::dependency
::Dependency
;
10 use crate::core
::dependency
::DepKind
;
11 use crate::core
::FeatureValue
;
12 use crate::util
::interning
::InternedString
;
13 use crate::CargoResult
;
15 /// Dependency table to add deps to.
16 #[derive(Clone, Debug, PartialEq, Eq)]
19 target
: Option
<String
>,
23 const KINDS
: &'
static [Self] = &[
24 Self::new().set_kind(DepKind
::Normal
),
25 Self::new().set_kind(DepKind
::Development
),
26 Self::new().set_kind(DepKind
::Build
),
29 /// Reference to a Dependency Table.
30 pub const fn new() -> Self {
32 kind
: DepKind
::Normal
,
37 /// Choose the type of dependency.
38 pub const fn set_kind(mut self, kind
: DepKind
) -> Self {
43 /// Choose the platform for the dependency.
44 pub fn set_target(mut self, target
: impl Into
<String
>) -> Self {
45 self.target
= Some(target
.into());
49 /// Type of dependency.
50 pub fn kind(&self) -> DepKind
{
54 /// Platform for the dependency.
55 pub fn target(&self) -> Option
<&str> {
56 self.target
.as_deref()
59 /// Keys to the table.
60 pub fn to_table(&self) -> Vec
<&str> {
61 if let Some(target
) = &self.target
{
62 vec
!["target", target
, self.kind
.kind_table()]
64 vec
![self.kind
.kind_table()]
69 impl Default
for DepTable
{
70 fn default() -> Self {
75 impl From
<DepKind
> for DepTable
{
76 fn from(other
: DepKind
) -> Self {
77 Self::new().set_kind(other
)
81 /// An editable Cargo manifest.
82 #[derive(Debug, Clone)]
84 /// Manifest contents as TOML data.
85 pub data
: toml_edit
::Document
,
89 /// Get the manifest's package name.
90 pub fn package_name(&self) -> CargoResult
<&str> {
94 .and_then(|m
| m
.get("name"))
95 .and_then(|m
| m
.as_str())
96 .ok_or_else(parse_manifest_err
)
99 /// Get the specified table from the manifest.
100 pub fn get_table
<'a
>(&'a
self, table_path
: &[String
]) -> CargoResult
<&'a toml_edit
::Item
> {
101 /// Descend into a manifest until the required table is found.
103 input
: &'a toml_edit
::Item
,
105 ) -> CargoResult
<&'a toml_edit
::Item
> {
106 if let Some(segment
) = path
.get(0) {
109 .ok_or_else(|| non_existent_table_err(segment
))?
;
111 if value
.is_table_like() {
112 descend(value
, &path
[1..])
114 Err(non_existent_table_err(segment
))
121 descend(self.data
.as_item(), table_path
)
124 /// Get the specified table from the manifest.
125 pub fn get_table_mut
<'a
>(
127 table_path
: &[String
],
128 ) -> CargoResult
<&'a
mut toml_edit
::Item
> {
129 /// Descend into a manifest until the required table is found.
131 input
: &'a
mut toml_edit
::Item
,
133 ) -> CargoResult
<&'a
mut toml_edit
::Item
> {
134 if let Some(segment
) = path
.get(0) {
135 let mut default_table
= toml_edit
::Table
::new();
136 default_table
.set_implicit(true);
137 let value
= input
[&segment
].or_insert(toml_edit
::Item
::Table(default_table
));
139 if value
.is_table_like() {
140 descend(value
, &path
[1..])
142 Err(non_existent_table_err(segment
))
149 descend(self.data
.as_item_mut(), table_path
)
152 /// Get all sections in the manifest that exist and might contain
153 /// dependencies. The returned items are always `Table` or
155 pub fn get_sections(&self) -> Vec
<(DepTable
, toml_edit
::Item
)> {
156 let mut sections
= Vec
::new();
158 for table
in DepTable
::KINDS
{
159 let dependency_type
= table
.kind
.kind_table();
160 // Dependencies can be in the three standard sections...
163 .get(dependency_type
)
164 .map(|t
| t
.is_table_like())
167 sections
.push((table
.clone(), self.data
[dependency_type
].clone()))
170 // ... and in `target.<target>.(build-/dev-)dependencies`.
171 let target_sections
= self
175 .and_then(toml_edit
::Item
::as_table_like
)
177 .flat_map(toml_edit
::TableLike
::iter
)
178 .filter_map(|(target_name
, target_table
)| {
179 let dependency_table
= target_table
.get(dependency_type
)?
;
180 dependency_table
.as_table_like().map(|_
| {
182 table
.clone().set_target(target_name
),
183 dependency_table
.clone(),
188 sections
.extend(target_sections
);
194 pub fn get_legacy_sections(&self) -> Vec
<String
> {
195 let mut result
= Vec
::new();
197 for dependency_type
in ["dev_dependencies", "build_dependencies"] {
198 if self.data
.contains_key(dependency_type
) {
199 result
.push(dependency_type
.to_owned());
202 // ... and in `target.<target>.(build-/dev-)dependencies`.
207 .and_then(toml_edit
::Item
::as_table_like
)
209 .flat_map(toml_edit
::TableLike
::iter
)
210 .filter_map(|(target_name
, target_table
)| {
211 if target_table
.as_table_like()?
.contains_key(dependency_type
) {
212 Some(format
!("target.{target_name}.{dependency_type}"))
223 impl str::FromStr
for Manifest
{
224 type Err
= anyhow
::Error
;
226 /// Read manifest data from string
227 fn from_str(input
: &str) -> ::std
::result
::Result
<Self, Self::Err
> {
228 let d
: toml_edit
::Document
= input
.parse().context("Manifest not valid TOML")?
;
230 Ok(Manifest { data: d }
)
234 impl std
::fmt
::Display
for Manifest
{
235 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
240 /// An editable Cargo manifest that is available locally.
241 #[derive(Debug, Clone)]
242 pub struct LocalManifest
{
243 /// Path to the manifest.
245 /// Manifest contents.
246 pub manifest
: Manifest
,
249 impl Deref
for LocalManifest
{
250 type Target
= Manifest
;
252 fn deref(&self) -> &Manifest
{
257 impl DerefMut
for LocalManifest
{
258 fn deref_mut(&mut self) -> &mut Manifest
{
264 /// Construct the `LocalManifest` corresponding to the `Path` provided..
265 pub fn try_new(path
: &Path
) -> CargoResult
<Self> {
266 if !path
.is_absolute() {
267 anyhow
::bail
!("can only edit absolute paths, got {}", path
.display());
269 let data
= cargo_util
::paths
::read(&path
)?
;
270 let manifest
= data
.parse().context("Unable to parse Cargo.toml")?
;
273 path
: path
.to_owned(),
277 /// Write changes back to the file.
278 pub fn write(&self) -> CargoResult
<()> {
279 if !self.manifest
.data
.contains_key("package")
280 && !self.manifest
.data
.contains_key("project")
282 if self.manifest
.data
.contains_key("workspace") {
284 "found virtual manifest at {}, but this command requires running against an \
285 actual package in this workspace.",
290 "missing expected `package` or `project` fields in {}",
296 let s
= self.manifest
.data
.to_string();
297 let new_contents_bytes
= s
.as_bytes();
299 cargo_util
::paths
::write(&self.path
, new_contents_bytes
)
302 /// Lookup a dependency.
303 pub fn get_dependency_versions
<'s
>(
306 ) -> impl Iterator
<Item
= (DepTable
, CargoResult
<Dependency
>)> + 's
{
307 let crate_root
= self.path
.parent().expect("manifest path is absolute");
310 .filter_map(move |(table_path
, table
)| {
311 let table
= table
.into_table().ok()?
;
315 .filter_map(|(key
, item
)| {
316 if key
.as_str() == dep_key
{
317 Some((table_path
.clone(), key
, item
))
322 .collect
::<Vec
<_
>>(),
326 .map(move |(table_path
, dep_key
, dep_item
)| {
327 let dep
= Dependency
::from_toml(crate_root
, &dep_key
, &dep_item
);
332 /// Add entry to a Cargo.toml.
333 pub fn insert_into_table(
335 table_path
: &[String
],
337 ) -> CargoResult
<()> {
338 let crate_root
= self
341 .expect("manifest path is absolute")
343 let dep_key
= dep
.toml_key();
345 let table
= self.get_table_mut(table_path
)?
;
346 if let Some((mut dep_key
, dep_item
)) = table
349 .get_key_value_mut(dep_key
)
351 dep
.update_toml(&crate_root
, &mut dep_key
, dep_item
);
353 let new_dependency
= dep
.to_toml(&crate_root
);
354 table
[dep_key
] = new_dependency
;
356 if let Some(t
) = table
.as_inline_table_mut() {
363 /// Remove entry from a Cargo.toml.
364 pub fn remove_from_table(&mut self, table_path
: &[String
], name
: &str) -> CargoResult
<()> {
365 let parent_table
= self.get_table_mut(table_path
)?
;
367 let dep
= parent_table
369 .filter(|t
| !t
.is_none())
370 .ok_or_else(|| non_existent_dependency_err(name
, table_path
.join(".")))?
;
372 // remove the dependency
373 *dep
= toml_edit
::Item
::None
;
375 // remove table if empty
376 if parent_table
.as_table_like().unwrap().is_empty() {
377 *parent_table
= toml_edit
::Item
::None
;
383 /// Remove references to `dep_key` if its no longer present.
384 pub fn gc_dep(&mut self, dep_key
: &str) {
385 let explicit_dep_activation
= self.is_explicit_dep_activation(dep_key
);
386 let status
= self.dep_status(dep_key
);
388 if let Some(toml_edit
::Item
::Table(feature_table
)) =
389 self.data
.as_table_mut().get_mut("features")
391 for (_feature
, mut feature_values
) in feature_table
.iter_mut() {
392 if let toml_edit
::Item
::Value(toml_edit
::Value
::Array(feature_values
)) =
395 fix_feature_activations(
399 explicit_dep_activation
,
406 fn is_explicit_dep_activation(&self, dep_key
: &str) -> bool
{
407 if let Some(toml_edit
::Item
::Table(feature_table
)) = self.data
.as_table().get("features") {
408 for values
in feature_table
411 .filter_map(|i
| i
.as_value())
412 .filter_map(|v
| v
.as_array())
414 for value
in values
.iter().filter_map(|v
| v
.as_str()) {
415 let value
= FeatureValue
::new(InternedString
::new(value
));
416 if let FeatureValue
::Dep { dep_name }
= &value
{
417 if dep_name
.as_str() == dep_key
{
428 fn dep_status(&self, dep_key
: &str) -> DependencyStatus
{
429 let mut status
= DependencyStatus
::None
;
430 for (_
, tbl
) in self.get_sections() {
431 if let toml_edit
::Item
::Table(tbl
) = tbl
{
432 if let Some(dep_item
) = tbl
.get(dep_key
) {
433 let optional
= dep_item
435 .and_then(|i
| i
.as_value())
436 .and_then(|i
| i
.as_bool())
439 return DependencyStatus
::Optional
;
441 status
= DependencyStatus
::Required
;
450 impl std
::fmt
::Display
for LocalManifest
{
451 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
456 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
457 enum DependencyStatus
{
463 fn fix_feature_activations(
464 feature_values
: &mut toml_edit
::Array
,
466 status
: DependencyStatus
,
467 explicit_dep_activation
: bool
,
469 let remove_list
: Vec
<usize> = feature_values
472 .filter_map(|(idx
, value
)| value
.as_str().map(|s
| (idx
, s
)))
473 .filter_map(|(idx
, value
)| {
474 let parsed_value
= FeatureValue
::new(InternedString
::new(value
));
476 DependencyStatus
::None
=> match (parsed_value
, explicit_dep_activation
) {
477 (FeatureValue
::Feature(dep_name
), false)
478 | (FeatureValue
::Dep { dep_name }
, _
)
479 | (FeatureValue
::DepFeature { dep_name, .. }
, _
) => dep_name
== dep_key
,
482 DependencyStatus
::Optional
=> false,
483 DependencyStatus
::Required
=> match (parsed_value
, explicit_dep_activation
) {
484 (FeatureValue
::Feature(dep_name
), false)
485 | (FeatureValue
::Dep { dep_name }
, _
) => dep_name
== dep_key
,
486 (FeatureValue
::Feature(_
), true) | (FeatureValue
::DepFeature { .. }
, _
) => {
495 // Remove found idx in revers order so we don't invalidate the idx.
496 for idx
in remove_list
.iter().rev() {
497 feature_values
.remove(*idx
);
500 if status
== DependencyStatus
::Required
{
501 for value
in feature_values
.iter_mut() {
502 let parsed_value
= if let Some(value
) = value
.as_str() {
503 FeatureValue
::new(InternedString
::new(value
))
507 if let FeatureValue
::DepFeature
{
513 if dep_name
== dep_key
&& weak
{
514 *value
= format
!("{dep_name}/{dep_feature}").into();
520 feature_values
.fmt();
523 pub fn str_or_1_len_table(item
: &toml_edit
::Item
) -> bool
{
524 item
.is_str() || item
.as_table_like().map(|t
| t
.len() == 1).unwrap_or(false)
527 fn parse_manifest_err() -> anyhow
::Error
{
528 anyhow
::format_err
!("unable to parse external Cargo.toml")
531 fn non_existent_table_err(table
: impl std
::fmt
::Display
) -> anyhow
::Error
{
532 anyhow
::format_err
!("the table `{table}` could not be found.")
535 fn non_existent_dependency_err(
536 name
: impl std
::fmt
::Display
,
537 table
: impl std
::fmt
::Display
,
539 anyhow
::format_err
!("the dependency `{name}` could not be found in `{table}`.")