#
cargo/util/toml_mut/
dependency.rs

    1//! Information about dependencies in a manifest.
    2
    3use std::borrow::Cow;
    4use std::fmt::{Display, Formatter};
    5use std::path::{Path, PathBuf};
    6
    7use cargo_util_schemas::manifest::PathBaseName;
    8use indexmap::IndexSet;
    9use itertools::Itertools;
    10use toml_edit::KeyMut;
    11
    12use super::manifest::str_or_1_len_table;
    13use crate::CargoResult;
    14use crate::GlobalContext;
    15use crate::core::SourceId;
    16use crate::core::Summary;
    17use crate::core::{Features, GitReference};
    18use crate::util::toml::lookup_path_base;
    19
    20/// A dependency handled by Cargo.
    21///
    22/// `None` means the field will be blank in TOML.
    23#[derive(Debug, PartialEq, Eq, Clone)]
    24#[non_exhaustive]
    25pub struct Dependency {
    26    /// The name of the dependency (as it is set in its `Cargo.toml` and known
    27    /// to crates.io).
    28    pub name: String,
    29    /// Whether the dependency is opted-in with a feature flag.
    30    pub optional: Option<bool>,
    31
    32    /// Whether the dependency is marked as public.
    33    pub public: Option<bool>,
    34
    35    /// List of features to add (or None to keep features unchanged).
    36    pub features: Option<IndexSet<String>>,
    37    /// Whether default features are enabled.
    38    pub default_features: Option<bool>,
    39    /// List of features inherited from a workspace dependency.
    40    pub inherited_features: Option<IndexSet<String>>,
    41
    42    /// Where the dependency comes from.
    43    pub source: Option<Source>,
    44    /// Non-default registry.
    45    pub registry: Option<String>,
    46
    47    /// If the dependency is renamed, this is the new name for the dependency
    48    /// as a string.  None if it is not renamed.
    49    pub rename: Option<String>,
    50}
    51
    52impl Dependency {
    53    /// Create a new dependency with a name.
    54    pub fn new(name: &str) -> Self {
    55        Self {
    56            name: name.into(),
    57            optional: None,
    58            public: None,
    59            features: None,
    60            default_features: None,
    61            inherited_features: None,
    62            source: None,
    63            registry: None,
    64            rename: None,
    65        }
    66    }
    67
    68    /// Set dependency to a given version.
    69    pub fn set_source(mut self, source: impl Into<Source>) -> Self {
    70        self.source = Some(source.into());
    71        self
    72    }
    73
    74    /// Remove the existing version requirement.
    75    pub fn clear_version(mut self) -> Self {
    76        match &mut self.source {
    77            Some(Source::Registry(_)) => {
    78                self.source = None;
    79            }
    80            Some(Source::Path(path)) => {
    81                path.version = None;
    82            }
    83            Some(Source::Git(git)) => {
    84                git.version = None;
    85            }
    86            Some(Source::Workspace(_workspace)) => {}
    87            None => {}
    88        }
    89        self
    90    }
    91
    92    /// Set whether the dependency is optional.
    93    #[allow(dead_code)]
    94    pub fn set_optional(mut self, opt: bool) -> Self {
    95        self.optional = Some(opt);
    96        self
    97    }
    98
    99    /// Set features as an array of string (does some basic parsing).
    100    #[allow(dead_code)]
    101    pub fn set_features(mut self, features: IndexSet<String>) -> Self {
    102        self.features = Some(features);
    103        self
    104    }
    105
    106    /// Set features as an array of string (does some basic parsing).
    107    pub fn extend_features(mut self, features: impl IntoIterator<Item = String>) -> Self {
    108        self.features
    109            .get_or_insert_with(Default::default)
    110            .extend(features);
    111        self
    112    }
    113
    114    /// Set the value of default-features for the dependency.
    115    #[allow(dead_code)]
    116    pub fn set_default_features(mut self, default_features: bool) -> Self {
    117        self.default_features = Some(default_features);
    118        self
    119    }
    120
    121    /// Set the alias for the dependency.
    122    pub fn set_rename(mut self, rename: &str) -> Self {
    123        self.rename = Some(rename.into());
    124        self
    125    }
    126
    127    /// Set the value of registry for the dependency.
    128    pub fn set_registry(mut self, registry: impl Into<String>) -> Self {
    129        self.registry = Some(registry.into());
    130        self
    131    }
    132
    133    /// Set features as an array of string (does some basic parsing).
    134    pub fn set_inherited_features(mut self, features: IndexSet<String>) -> Self {
    135        self.inherited_features = Some(features);
    136        self
    137    }
    138
    139    /// Get the dependency source.
    140    pub fn source(&self) -> Option<&Source> {
    141        self.source.as_ref()
    142    }
    143
    144    /// Get version of dependency.
    145    pub fn version(&self) -> Option<&str> {
    146        match self.source()? {
    147            Source::Registry(src) => Some(src.version.as_str()),
    148            Source::Path(src) => src.version.as_deref(),
    149            Source::Git(src) => src.version.as_deref(),
    150            Source::Workspace(_) => None,
    151        }
    152    }
    153
    154    /// Get registry of the dependency.
    155    pub fn registry(&self) -> Option<&str> {
    156        self.registry.as_deref()
    157    }
    158
    159    /// Get the alias for the dependency (if any).
    160    pub fn rename(&self) -> Option<&str> {
    161        self.rename.as_deref()
    162    }
    163
    164    /// Whether default features are activated.
    165    pub fn default_features(&self) -> Option<bool> {
    166        self.default_features
    167    }
    168
    169    /// Get whether the dep is optional.
    170    pub fn optional(&self) -> Option<bool> {
    171        self.optional
    172    }
    173
    174    /// Get whether the dep is public.
    175    pub fn public(&self) -> Option<bool> {
    176        self.public
    177    }
    178
    179    /// Get the `SourceID` for this dependency.
    180    pub fn source_id(&self, gctx: &GlobalContext) -> CargoResult<MaybeWorkspace<SourceId>> {
    181        match &self.source.as_ref() {
    182            Some(Source::Registry(_)) | None => {
    183                if let Some(r) = self.registry() {
    184                    let source_id = SourceId::alt_registry(gctx, r)?;
    185                    Ok(MaybeWorkspace::Other(source_id))
    186                } else {
    187                    let source_id = SourceId::crates_io(gctx)?;
    188                    Ok(MaybeWorkspace::Other(source_id))
    189                }
    190            }
    191            Some(Source::Path(source)) => Ok(MaybeWorkspace::Other(source.source_id()?)),
    192            Some(Source::Git(source)) => Ok(MaybeWorkspace::Other(source.source_id()?)),
    193            Some(Source::Workspace(workspace)) => Ok(MaybeWorkspace::Workspace(workspace.clone())),
    194        }
    195    }
    196
    197    /// Query to find this dependency.
    198    pub fn query(
    199        &self,
    200        gctx: &GlobalContext,
    201    ) -> CargoResult<MaybeWorkspace<crate::core::dependency::Dependency>> {
    202        let source_id = self.source_id(gctx)?;
    203        match source_id {
    204            MaybeWorkspace::Workspace(workspace) => Ok(MaybeWorkspace::Workspace(workspace)),
    205            MaybeWorkspace::Other(source_id) => Ok(MaybeWorkspace::Other(
    206                crate::core::dependency::Dependency::parse(
    207                    self.name.as_str(),
    208                    self.version(),
    209                    source_id,
    210                )?,
    211            )),
    212        }
    213    }
    214}
    215
    216/// Either a workspace or another type.
    217pub enum MaybeWorkspace<T> {
    218    Workspace(WorkspaceSource),
    219    Other(T),
    220}
    221
    222impl Dependency {
    223    /// Create a dependency from a TOML table entry.
    224    pub fn from_toml(
    225        gctx: &GlobalContext,
    226        workspace_root: &Path,
    227        crate_root: &Path,
    228        unstable_features: &Features,
    229        key: &str,
    230        item: &toml_edit::Item,
    231    ) -> CargoResult<Self> {
    232        if let Some(version) = item.as_str() {
    233            let dep = Self::new(key).set_source(RegistrySource::new(version));
    234            Ok(dep)
    235        } else if let Some(table) = item.as_table_like() {
    236            let (name, rename) = if let Some(value) = table.get("package") {
    237                (
    238                    value
    239                        .as_str()
    240                        .ok_or_else(|| invalid_type(key, "package", value.type_name(), "string"))?
    241                        .to_owned(),
    242                    Some(key.to_owned()),
    243                )
    244            } else {
    245                (key.to_owned(), None)
    246            };
    247
    248            let source: Source = if let Some(git) = table.get("git") {
    249                let mut src = GitSource::new(
    250                    git.as_str()
    251                        .ok_or_else(|| invalid_type(key, "git", git.type_name(), "string"))?,
    252                );
    253                if let Some(value) = table.get("branch") {
    254                    src =
    255                        src.set_branch(value.as_str().ok_or_else(|| {
    256                            invalid_type(key, "branch", value.type_name(), "string")
    257                        })?);
    258                }
    259                if let Some(value) = table.get("tag") {
    260                    src =
    261                        src.set_tag(value.as_str().ok_or_else(|| {
    262                            invalid_type(key, "tag", value.type_name(), "string")
    263                        })?);
    264                }
    265                if let Some(value) = table.get("rev") {
    266                    src =
    267                        src.set_rev(value.as_str().ok_or_else(|| {
    268                            invalid_type(key, "rev", value.type_name(), "string")
    269                        })?);
    270                }
    271                if let Some(value) = table.get("version") {
    272                    src = src.set_version(value.as_str().ok_or_else(|| {
    273                        invalid_type(key, "version", value.type_name(), "string")
    274                    })?);
    275                }
    276                src.into()
    277            } else if let Some(path) = table.get("path") {
    278                let base = table
    279                    .get("base")
    280                    .map(|base| {
    281                        base.as_str()
    282                            .ok_or_else(|| invalid_type(key, "base", base.type_name(), "string"))
    283                            .map(|s| s.to_owned())
    284                    })
    285                    .transpose()?;
    286                let relative_to = if let Some(base) = &base {
    287                    Cow::Owned(lookup_path_base(
    288                        &PathBaseName::new(base.clone())?,
    289                        gctx,
    290                        &|| Ok(workspace_root),
    291                        unstable_features,
    292                    )?)
    293                } else {
    294                    Cow::Borrowed(crate_root)
    295                };
    296                let path =
    297                    relative_to
    298                        .join(path.as_str().ok_or_else(|| {
    299                            invalid_type(key, "path", path.type_name(), "string")
    300                        })?);
    301                let mut src = PathSource::new(path);
    302                src.base = base;
    303                if let Some(value) = table.get("version") {
    304                    src = src.set_version(value.as_str().ok_or_else(|| {
    305                        invalid_type(key, "version", value.type_name(), "string")
    306                    })?);
    307                }
    308                src.into()
    309            } else if let Some(version) = table.get("version") {
    310                let src =
    311                    RegistrySource::new(version.as_str().ok_or_else(|| {
    312                        invalid_type(key, "version", version.type_name(), "string")
    313                    })?);
    314                src.into()
    315            } else if let Some(workspace) = table.get("workspace") {
    316                let workspace_bool = workspace
    317                    .as_bool()
    318                    .ok_or_else(|| invalid_type(key, "workspace", workspace.type_name(), "bool"))?;
    319                if !workspace_bool {
    320                    anyhow::bail!("`{key}.workspace = false` is unsupported")
    321                }
    322                let src = WorkspaceSource::new();
    323                src.into()
    324            } else {
    325                let mut msg = format!("unrecognized dependency source for `{key}`");
    326                if table.is_empty() {
    327                    msg.push_str(
    328                        ", expected a local path, Git repository, version, or workspace dependency to be specified",
    329                    );
    330                }
    331                anyhow::bail!(msg);
    332            };
    333            let registry = if let Some(value) = table.get("registry") {
    334                Some(
    335                    value
    336                        .as_str()
    337                        .ok_or_else(|| invalid_type(key, "registry", value.type_name(), "string"))?
    338                        .to_owned(),
    339                )
    340            } else {
    341                None
    342            };
    343
    344            let default_features = table.get("default-features").and_then(|v| v.as_bool());
    345            if table.contains_key("default_features") {
    346                anyhow::bail!(
    347                    "Use of `default_features` in `{key}` is unsupported, please switch to `default-features`"
    348                );
    349            }
    350
    351            let features = if let Some(value) = table.get("features") {
    352                Some(
    353                    value
    354                        .as_array()
    355                        .ok_or_else(|| invalid_type(key, "features", value.type_name(), "array"))?
    356                        .iter()
    357                        .map(|v| {
    358                            v.as_str().map(|s| s.to_owned()).ok_or_else(|| {
    359                                invalid_type(key, "features", v.type_name(), "string")
    360                            })
    361                        })
    362                        .collect::<CargoResult<IndexSet<String>>>()?,
    363                )
    364            } else {
    365                None
    366            };
    367
    368            let optional = table.get("optional").and_then(|v| v.as_bool());
    369            let public = table.get("public").and_then(|v| v.as_bool());
    370
    371            let dep = Self {
    372                name,
    373                optional,
    374                public,
    375                features,
    376                default_features,
    377                inherited_features: None,
    378                source: Some(source),
    379                registry,
    380                rename,
    381            };
    382            Ok(dep)
    383        } else {
    384            anyhow::bail!("Unrecognized` dependency entry format for `{key}");
    385        }
    386    }
    387
    388    /// Get the dependency name as defined in the manifest,
    389    /// that is, either the alias (rename field if Some),
    390    /// or the official package name (name field).
    391    pub fn toml_key(&self) -> &str {
    392        self.rename().unwrap_or(&self.name)
    393    }
    394
    395    /// Convert dependency to TOML.
    396    ///
    397    /// Returns a tuple with the dependency's name and either the version as a
    398    /// `String` or the path/git repository as an `InlineTable`.
    399    /// (If the dependency is set as `optional` or `default-features` is set to
    400    /// `false`, an `InlineTable` is returned in any case.)
    401    ///
    402    /// # Panic
    403    ///
    404    /// Panics if the path is relative
    405    pub fn to_toml<'a>(
    406        &self,
    407        gctx: &GlobalContext,
    408        workspace_root: &Path,
    409        crate_root: &Path,
    410        unstable_features: &Features,
    411    ) -> CargoResult<toml_edit::Item> {
    412        assert!(
    413            crate_root.is_absolute(),
    414            "Absolute path needed, got: {}",
    415            crate_root.display()
    416        );
    417        let table: toml_edit::Item = match (
    418            self.public.unwrap_or(false),
    419            self.optional.unwrap_or(false),
    420            self.features.as_ref(),
    421            self.default_features.unwrap_or(true),
    422            self.source.as_ref(),
    423            self.registry.as_ref(),
    424            self.rename.as_ref(),
    425        ) {
    426            // Extra short when version flag only
    427            (
    428                false,
    429                false,
    430                None,
    431                true,
    432                Some(Source::Registry(RegistrySource { version: v })),
    433                None,
    434                None,
    435            ) => toml_edit::value(v),
    436            (false, false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
    437                let mut table = toml_edit::InlineTable::default();
    438                table.set_dotted(true);
    439                table.insert("workspace", true.into());
    440                toml_edit::value(toml_edit::Value::InlineTable(table))
    441            }
    442            // Other cases are represented as an inline table
    443            (_, _, _, _, _, _, _) => {
    444                let mut table = toml_edit::InlineTable::default();
    445
    446                match &self.source {
    447                    Some(Source::Registry(src)) => {
    448                        table.insert("version", src.version.as_str().into());
    449                    }
    450                    Some(Source::Path(src)) => {
    451                        let relpath =
    452                            path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
    453                        if let Some(r) = src.version.as_deref() {
    454                            table.insert("version", r.into());
    455                        }
    456                        if let Some(base) = &src.base {
    457                            table.insert("base", base.into());
    458                        }
    459                        table.insert("path", relpath.into());
    460                    }
    461                    Some(Source::Git(src)) => {
    462                        table.insert("git", src.git.as_str().into());
    463                        if let Some(branch) = src.branch.as_deref() {
    464                            table.insert("branch", branch.into());
    465                        }
    466                        if let Some(tag) = src.tag.as_deref() {
    467                            table.insert("tag", tag.into());
    468                        }
    469                        if let Some(rev) = src.rev.as_deref() {
    470                            table.insert("rev", rev.into());
    471                        }
    472                        if let Some(r) = src.version.as_deref() {
    473                            table.insert("version", r.into());
    474                        }
    475                    }
    476                    Some(Source::Workspace(_)) => {
    477                        table.insert("workspace", true.into());
    478                    }
    479                    None => {}
    480                }
    481                if table.contains_key("version") {
    482                    if let Some(r) = self.registry.as_deref() {
    483                        table.insert("registry", r.into());
    484                    }
    485                }
    486
    487                if self.rename.is_some() {
    488                    table.insert("package", self.name.as_str().into());
    489                }
    490                if let Some(v) = self.default_features {
    491                    table.insert("default-features", v.into());
    492                }
    493                if let Some(features) = self.features.as_ref() {
    494                    let features: toml_edit::Value = features.iter().cloned().collect();
    495                    table.insert("features", features);
    496                }
    497                if let Some(v) = self.optional {
    498                    table.insert("optional", v.into());
    499                }
    500                if let Some(v) = self.public {
    501                    table.insert("public", v.into());
    502                }
    503
    504                toml_edit::value(toml_edit::Value::InlineTable(table))
    505            }
    506        };
    507
    508        Ok(table)
    509    }
    510
    511    /// Modify existing entry to match this dependency.
    512    pub fn update_toml<'k, 'a>(
    513        &self,
    514        gctx: &GlobalContext,
    515        workspace_root: &Path,
    516        crate_root: &Path,
    517        unstable_features: &Features,
    518        key: &mut KeyMut<'k>,
    519        item: &mut toml_edit::Item,
    520    ) -> CargoResult<()> {
    521        if str_or_1_len_table(item) {
    522            // Little to preserve
    523            let mut new_item = self.to_toml(gctx, workspace_root, crate_root, unstable_features)?;
    524            match (&item, &mut new_item) {
    525                (toml_edit::Item::Value(old), toml_edit::Item::Value(new)) => {
    526                    *new.decor_mut() = old.decor().clone();
    527                }
    528                (toml_edit::Item::Table(old), toml_edit::Item::Table(new)) => {
    529                    *new.decor_mut() = old.decor().clone();
    530                }
    531                (_, _) => {}
    532            }
    533            *item = new_item;
    534        } else if let Some(table) = item.as_table_like_mut() {
    535            match &self.source {
    536                Some(Source::Registry(src)) => {
    537                    overwrite_value(table, "version", src.version.as_str());
    538
    539                    for key in ["path", "git", "branch", "tag", "rev", "workspace", "base"] {
    540                        table.remove(key);
    541                    }
    542                }
    543                Some(Source::Path(src)) => {
    544                    if let Some(base) = &src.base {
    545                        overwrite_value(table, "base", base);
    546                    } else {
    547                        table.remove("base");
    548                    }
    549                    let relpath =
    550                        path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
    551                    overwrite_value(table, "path", relpath);
    552                    if let Some(r) = src.version.as_deref() {
    553                        overwrite_value(table, "version", r);
    554                    } else {
    555                        table.remove("version");
    556                    }
    557
    558                    for key in ["git", "branch", "tag", "rev", "workspace"] {
    559                        table.remove(key);
    560                    }
    561                }
    562                Some(Source::Git(src)) => {
    563                    overwrite_value(table, "git", src.git.as_str());
    564                    if let Some(branch) = src.branch.as_deref() {
    565                        overwrite_value(table, "branch", branch);
    566                    } else {
    567                        table.remove("branch");
    568                    }
    569                    if let Some(tag) = src.tag.as_deref() {
    570                        overwrite_value(table, "tag", tag);
    571                    } else {
    572                        table.remove("tag");
    573                    }
    574                    if let Some(rev) = src.rev.as_deref() {
    575                        overwrite_value(table, "rev", rev);
    576                    } else {
    577                        table.remove("rev");
    578                    }
    579                    if let Some(r) = src.version.as_deref() {
    580                        overwrite_value(table, "version", r);
    581                    } else {
    582                        table.remove("version");
    583                    }
    584
    585                    for key in ["path", "workspace", "base"] {
    586                        table.remove(key);
    587                    }
    588                }
    589                Some(Source::Workspace(_)) => {
    590                    overwrite_value(table, "workspace", true);
    591                    table.set_dotted(true);
    592                    key.fmt();
    593                    for key in [
    594                        "version",
    595                        "registry",
    596                        "registry-index",
    597                        "path",
    598                        "git",
    599                        "branch",
    600                        "tag",
    601                        "rev",
    602                        "package",
    603                        "default-features",
    604                        "base",
    605                    ] {
    606                        table.remove(key);
    607                    }
    608                }
    609                None => {}
    610            }
    611            if table.contains_key("version") {
    612                if let Some(r) = self.registry.as_deref() {
    613                    overwrite_value(table, "registry", r);
    614                } else {
    615                    table.remove("registry");
    616                }
    617            } else {
    618                table.remove("registry");
    619            }
    620
    621            if self.rename.is_some() {
    622                overwrite_value(table, "package", self.name.as_str());
    623            }
    624            match self.default_features {
    625                Some(v) => {
    626                    overwrite_value(table, "default-features", v);
    627                }
    628                None => {
    629                    table.remove("default-features");
    630                }
    631            }
    632            if let Some(new_features) = self.features.as_ref() {
    633                let mut features = table
    634                    .get("features")
    635                    .and_then(|i| i.as_value())
    636                    .and_then(|v| v.as_array())
    637                    .and_then(|a| {
    638                        a.iter()
    639                            .map(|v| v.as_str())
    640                            .collect::<Option<IndexSet<_>>>()
    641                    })
    642                    .unwrap_or_default();
    643                let is_already_sorted = features.iter().is_sorted();
    644                features.extend(new_features.iter().map(|s| s.as_str()));
    645                let features = if is_already_sorted {
    646                    features.into_iter().sorted().collect::<toml_edit::Value>()
    647                } else {
    648                    features.into_iter().collect::<toml_edit::Value>()
    649                };
    650                table.set_dotted(false);
    651                overwrite_value(table, "features", features);
    652            } else {
    653                table.remove("features");
    654            }
    655            match self.optional {
    656                Some(v) => {
    657                    table.set_dotted(false);
    658                    overwrite_value(table, "optional", v);
    659                }
    660                None => {
    661                    table.remove("optional");
    662                }
    663            }
    664            match self.public {
    665                Some(v) => {
    666                    table.set_dotted(false);
    667                    overwrite_value(table, "public", v);
    668                }
    669                None => {
    670                    table.remove("public");
    671                }
    672            }
    673        } else {
    674            unreachable!("Invalid dependency type: {}", item.type_name());
    675        }
    676        Ok(())
    677    }
    678}
    679
    680fn overwrite_value(
    681    table: &mut dyn toml_edit::TableLike,
    682    key: &str,
    683    value: impl Into<toml_edit::Value>,
    684) {
    685    let mut value = value.into();
    686    let existing = table.entry(key).or_insert_with(|| Default::default());
    687    if let Some(existing_value) = existing.as_value() {
    688        *value.decor_mut() = existing_value.decor().clone();
    689    }
    690    *existing = toml_edit::Item::Value(value);
    691}
    692
    693fn invalid_type(dep: &str, key: &str, actual: &str, expected: &str) -> anyhow::Error {
    694    anyhow::format_err!("Found {actual} for {key} when {expected} was expected for {dep}")
    695}
    696
    697impl std::fmt::Display for Dependency {
    698    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    699        if let Some(source) = self.source() {
    700            write!(f, "{}@{}", self.name, source)
    701        } else {
    702            self.toml_key().fmt(f)
    703        }
    704    }
    705}
    706
    707impl<'s> From<&'s Summary> for Dependency {
    708    fn from(other: &'s Summary) -> Self {
    709        let source: Source = if let Some(path) = other.source_id().local_path() {
    710            PathSource::new(path)
    711                .set_version(other.version().to_string())
    712                .into()
    713        } else if let Some(git_ref) = other.source_id().git_reference() {
    714            let mut src = GitSource::new(other.source_id().url().to_string())
    715                .set_version(other.version().to_string());
    716            match git_ref {
    717                GitReference::Branch(branch) => src = src.set_branch(branch),
    718                GitReference::Tag(tag) => src = src.set_tag(tag),
    719                GitReference::Rev(rev) => src = src.set_rev(rev),
    720                GitReference::DefaultBranch => {}
    721            }
    722            src.into()
    723        } else {
    724            RegistrySource::new(other.version().to_string()).into()
    725        };
    726        Dependency::new(other.name().as_str()).set_source(source)
    727    }
    728}
    729
    730impl From<Summary> for Dependency {
    731    fn from(other: Summary) -> Self {
    732        (&other).into()
    733    }
    734}
    735
    736fn path_field<'a>(
    737    source: &PathSource,
    738    gctx: &GlobalContext,
    739    workspace_root: &Path,
    740    crate_root: &Path,
    741    unstable_features: &Features,
    742) -> CargoResult<String> {
    743    let relative_to = if let Some(base) = &source.base {
    744        Cow::Owned(lookup_path_base(
    745            &PathBaseName::new(base.clone())?,
    746            gctx,
    747            &|| Ok(workspace_root),
    748            unstable_features,
    749        )?)
    750    } else {
    751        Cow::Borrowed(crate_root)
    752    };
    753    let relpath = pathdiff::diff_paths(&source.path, relative_to)
    754        .expect("PathSource::path and workspace path must be absolute");
    755    let relpath = relpath.to_str().unwrap().replace('\\', "/");
    756    Ok(relpath)
    757}
    758
    759/// Primary location of a dependency.
    760#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    761pub enum Source {
    762    /// Dependency from a registry.
    763    Registry(RegistrySource),
    764    /// Dependency from a local path.
    765    Path(PathSource),
    766    /// Dependency from a git repo.
    767    Git(GitSource),
    768    /// Dependency from a workspace.
    769    Workspace(WorkspaceSource),
    770}
    771
    772impl Source {
    773    /// Access the registry source, if present.
    774    pub fn as_registry(&self) -> Option<&RegistrySource> {
    775        match self {
    776            Self::Registry(src) => Some(src),
    777            _ => None,
    778        }
    779    }
    780
    781    /// Access the path source, if present.
    782    #[allow(dead_code)]
    783    pub fn as_path(&self) -> Option<&PathSource> {
    784        match self {
    785            Self::Path(src) => Some(src),
    786            _ => None,
    787        }
    788    }
    789
    790    /// Access the git source, if present.
    791    #[allow(dead_code)]
    792    pub fn as_git(&self) -> Option<&GitSource> {
    793        match self {
    794            Self::Git(src) => Some(src),
    795            _ => None,
    796        }
    797    }
    798
    799    /// Access the workspace source, if present.
    800    #[allow(dead_code)]
    801    pub fn as_workspace(&self) -> Option<&WorkspaceSource> {
    802        match self {
    803            Self::Workspace(src) => Some(src),
    804            _ => None,
    805        }
    806    }
    807}
    808
    809impl std::fmt::Display for Source {
    810    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    811        match self {
    812            Self::Registry(src) => src.fmt(f),
    813            Self::Path(src) => src.fmt(f),
    814            Self::Git(src) => src.fmt(f),
    815            Self::Workspace(src) => src.fmt(f),
    816        }
    817    }
    818}
    819
    820impl<'s> From<&'s Source> for Source {
    821    fn from(inner: &'s Source) -> Self {
    822        inner.clone()
    823    }
    824}
    825
    826impl From<RegistrySource> for Source {
    827    fn from(inner: RegistrySource) -> Self {
    828        Self::Registry(inner)
    829    }
    830}
    831
    832impl From<PathSource> for Source {
    833    fn from(inner: PathSource) -> Self {
    834        Self::Path(inner)
    835    }
    836}
    837
    838impl From<GitSource> for Source {
    839    fn from(inner: GitSource) -> Self {
    840        Self::Git(inner)
    841    }
    842}
    843
    844impl From<WorkspaceSource> for Source {
    845    fn from(inner: WorkspaceSource) -> Self {
    846        Self::Workspace(inner)
    847    }
    848}
    849
    850/// Dependency from a registry.
    851#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    852#[non_exhaustive]
    853pub struct RegistrySource {
    854    /// Version requirement.
    855    pub version: String,
    856}
    857
    858impl RegistrySource {
    859    /// Specify dependency by version requirement.
    860    pub fn new(version: impl AsRef<str>) -> Self {
    861        // versions might have semver metadata appended which we do not want to
    862        // store in the cargo toml files.  This would cause a warning upon compilation
    863        // ("version requirement […] includes semver metadata which will be ignored")
    864        let version = version.as_ref().split('+').next().unwrap();
    865        Self {
    866            version: version.to_owned(),
    867        }
    868    }
    869}
    870
    871impl std::fmt::Display for RegistrySource {
    872    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    873        self.version.fmt(f)
    874    }
    875}
    876
    877/// Dependency from a local path.
    878#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    879#[non_exhaustive]
    880pub struct PathSource {
    881    /// Local, absolute path.
    882    pub path: PathBuf,
    883    /// The path base, if using one.
    884    pub base: Option<String>,
    885    /// Version requirement for when published.
    886    pub version: Option<String>,
    887}
    888
    889impl PathSource {
    890    /// Specify dependency from a path.
    891    pub fn new(path: impl Into<PathBuf>) -> Self {
    892        Self {
    893            path: path.into(),
    894            base: None,
    895            version: None,
    896        }
    897    }
    898
    899    /// Set an optional version requirement.
    900    pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
    901        // versions might have semver metadata appended which we do not want to
    902        // store in the cargo toml files.  This would cause a warning upon compilation
    903        // ("version requirement […] includes semver metadata which will be ignored")
    904        let version = version.as_ref().split('+').next().unwrap();
    905        self.version = Some(version.to_owned());
    906        self
    907    }
    908
    909    /// Get the `SourceID` for this dependency.
    910    pub fn source_id(&self) -> CargoResult<SourceId> {
    911        SourceId::for_path(&self.path)
    912    }
    913}
    914
    915impl std::fmt::Display for PathSource {
    916    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    917        self.path.display().fmt(f)
    918    }
    919}
    920
    921/// Dependency from a git repo.
    922#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    923#[non_exhaustive]
    924pub struct GitSource {
    925    /// Repository URL.
    926    pub git: String,
    927    /// Select specific branch.
    928    pub branch: Option<String>,
    929    /// Select specific tag.
    930    pub tag: Option<String>,
    931    /// Select specific rev.
    932    pub rev: Option<String>,
    933    /// Version requirement for when published.
    934    pub version: Option<String>,
    935}
    936
    937impl GitSource {
    938    /// Specify dependency from a git repo.
    939    pub fn new(git: impl Into<String>) -> Self {
    940        Self {
    941            git: git.into(),
    942            branch: None,
    943            tag: None,
    944            rev: None,
    945            version: None,
    946        }
    947    }
    948
    949    /// Specify an optional branch.
    950    pub fn set_branch(mut self, branch: impl Into<String>) -> Self {
    951        self.branch = Some(branch.into());
    952        self.tag = None;
    953        self.rev = None;
    954        self
    955    }
    956
    957    /// Specify an optional tag.
    958    pub fn set_tag(mut self, tag: impl Into<String>) -> Self {
    959        self.branch = None;
    960        self.tag = Some(tag.into());
    961        self.rev = None;
    962        self
    963    }
    964
    965    /// Specify an optional rev.
    966    pub fn set_rev(mut self, rev: impl Into<String>) -> Self {
    967        self.branch = None;
    968        self.tag = None;
    969        self.rev = Some(rev.into());
    970        self
    971    }
    972
    973    /// Get the `SourceID` for this dependency.
    974    pub fn source_id(&self) -> CargoResult<SourceId> {
    975        let git_url = self.git.parse::<url::Url>()?;
    976        let git_ref = self.git_ref();
    977        SourceId::for_git(&git_url, git_ref)
    978    }
    979
    980    fn git_ref(&self) -> GitReference {
    981        match (
    982            self.branch.as_deref(),
    983            self.tag.as_deref(),
    984            self.rev.as_deref(),
    985        ) {
    986            (Some(branch), _, _) => GitReference::Branch(branch.to_owned()),
    987            (_, Some(tag), _) => GitReference::Tag(tag.to_owned()),
    988            (_, _, Some(rev)) => GitReference::Rev(rev.to_owned()),
    989            _ => GitReference::DefaultBranch,
    990        }
    991    }
    992
    993    /// Set an optional version requirement.
    994    pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
    995        // versions might have semver metadata appended which we do not want to
    996        // store in the cargo toml files.  This would cause a warning upon compilation
    997        // ("version requirement […] includes semver metadata which will be ignored")
    998        let version = version.as_ref().split('+').next().unwrap();
    999        self.version = Some(version.to_owned());
    1000        self
    1001    }
    1002}
    1003
    1004impl std::fmt::Display for GitSource {
    1005    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    1006        let git_ref = self.git_ref();
    1007        if let Some(pretty_ref) = git_ref.pretty_ref(true) {
    1008            write!(f, "{}?{}", self.git, pretty_ref)
    1009        } else {
    1010            write!(f, "{}", self.git)
    1011        }
    1012    }
    1013}
    1014
    1015/// Dependency from a workspace.
    1016#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    1017#[non_exhaustive]
    1018pub struct WorkspaceSource;
    1019
    1020impl WorkspaceSource {
    1021    pub fn new() -> Self {
    1022        Self
    1023    }
    1024}
    1025
    1026impl Display for WorkspaceSource {
    1027    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    1028        "workspace".fmt(f)
    1029    }
    1030}
    1031
    1032#[cfg(test)]
    1033mod tests {
    1034    use crate::util::toml_mut::manifest::LocalManifest;
    1035    use cargo_util::paths;
    1036
    1037    use super::*;
    1038
    1039    #[test]
    1040    fn to_toml_simple_dep() {
    1041        let crate_root =
    1042            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1043        let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
    1044        let key = dep.toml_key();
    1045        let gctx = GlobalContext::default().unwrap();
    1046        let item = dep
    1047            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1048            .unwrap();
    1049
    1050        assert_eq!(key, "dep".to_owned());
    1051
    1052        verify_roundtrip(&crate_root, &gctx, key, &item);
    1053    }
    1054
    1055    #[test]
    1056    fn to_toml_simple_dep_with_version() {
    1057        let crate_root =
    1058            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1059        let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
    1060        let key = dep.toml_key();
    1061        let gctx = GlobalContext::default().unwrap();
    1062        let item = dep
    1063            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1064            .unwrap();
    1065
    1066        assert_eq!(key, "dep".to_owned());
    1067        assert_eq!(item.as_str(), Some("1.0"));
    1068
    1069        verify_roundtrip(&crate_root, &gctx, key, &item);
    1070    }
    1071
    1072    #[test]
    1073    fn to_toml_optional_dep() {
    1074        let crate_root =
    1075            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1076        let dep = Dependency::new("dep")
    1077            .set_source(RegistrySource::new("1.0"))
    1078            .set_optional(true);
    1079        let key = dep.toml_key();
    1080        let gctx = GlobalContext::default().unwrap();
    1081        let item = dep
    1082            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1083            .unwrap();
    1084
    1085        assert_eq!(key, "dep".to_owned());
    1086        assert!(item.is_inline_table());
    1087
    1088        let dep = item.as_inline_table().unwrap();
    1089        assert_eq!(dep.get("optional").unwrap().as_bool(), Some(true));
    1090
    1091        verify_roundtrip(&crate_root, &gctx, key, &item);
    1092    }
    1093
    1094    #[test]
    1095    fn to_toml_dep_without_default_features() {
    1096        let crate_root =
    1097            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1098        let dep = Dependency::new("dep")
    1099            .set_source(RegistrySource::new("1.0"))
    1100            .set_default_features(false);
    1101        let key = dep.toml_key();
    1102        let gctx = GlobalContext::default().unwrap();
    1103        let item = dep
    1104            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1105            .unwrap();
    1106
    1107        assert_eq!(key, "dep".to_owned());
    1108        assert!(item.is_inline_table());
    1109
    1110        let dep = item.as_inline_table().unwrap();
    1111        assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
    1112
    1113        verify_roundtrip(&crate_root, &gctx, key, &item);
    1114    }
    1115
    1116    #[test]
    1117    fn to_toml_dep_with_path_source() {
    1118        let root = paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1119        let crate_root = root.join("foo");
    1120        let dep = Dependency::new("dep").set_source(PathSource::new(root.join("bar")));
    1121        let key = dep.toml_key();
    1122        let gctx = GlobalContext::default().unwrap();
    1123        let item = dep
    1124            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1125            .unwrap();
    1126
    1127        assert_eq!(key, "dep".to_owned());
    1128        assert!(item.is_inline_table());
    1129
    1130        let dep = item.as_inline_table().unwrap();
    1131        assert_eq!(dep.get("path").unwrap().as_str(), Some("../bar"));
    1132
    1133        verify_roundtrip(&crate_root, &gctx, key, &item);
    1134    }
    1135
    1136    #[test]
    1137    fn to_toml_dep_with_git_source() {
    1138        let crate_root =
    1139            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1140        let dep = Dependency::new("dep").set_source(GitSource::new("https://foor/bar.git"));
    1141        let key = dep.toml_key();
    1142        let gctx = GlobalContext::default().unwrap();
    1143        let item = dep
    1144            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1145            .unwrap();
    1146
    1147        assert_eq!(key, "dep".to_owned());
    1148        assert!(item.is_inline_table());
    1149
    1150        let dep = item.as_inline_table().unwrap();
    1151        assert_eq!(
    1152            dep.get("git").unwrap().as_str(),
    1153            Some("https://foor/bar.git")
    1154        );
    1155
    1156        verify_roundtrip(&crate_root, &gctx, key, &item);
    1157    }
    1158
    1159    #[test]
    1160    fn to_toml_renamed_dep() {
    1161        let crate_root =
    1162            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1163        let dep = Dependency::new("dep")
    1164            .set_source(RegistrySource::new("1.0"))
    1165            .set_rename("d");
    1166        let key = dep.toml_key();
    1167        let gctx = GlobalContext::default().unwrap();
    1168        let item = dep
    1169            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1170            .unwrap();
    1171
    1172        assert_eq!(key, "d".to_owned());
    1173        assert!(item.is_inline_table());
    1174
    1175        let dep = item.as_inline_table().unwrap();
    1176        assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
    1177
    1178        verify_roundtrip(&crate_root, &gctx, key, &item);
    1179    }
    1180
    1181    #[test]
    1182    fn to_toml_dep_from_alt_registry() {
    1183        let crate_root =
    1184            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1185        let dep = Dependency::new("dep")
    1186            .set_source(RegistrySource::new("1.0"))
    1187            .set_registry("alternative");
    1188        let key = dep.toml_key();
    1189        let gctx = GlobalContext::default().unwrap();
    1190        let item = dep
    1191            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1192            .unwrap();
    1193
    1194        assert_eq!(key, "dep".to_owned());
    1195        assert!(item.is_inline_table());
    1196
    1197        let dep = item.as_inline_table().unwrap();
    1198        assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
    1199
    1200        verify_roundtrip(&crate_root, &gctx, key, &item);
    1201    }
    1202
    1203    #[test]
    1204    fn to_toml_complex_dep() {
    1205        let crate_root =
    1206            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1207        let dep = Dependency::new("dep")
    1208            .set_source(RegistrySource::new("1.0"))
    1209            .set_default_features(false)
    1210            .set_rename("d");
    1211        let key = dep.toml_key();
    1212        let gctx = GlobalContext::default().unwrap();
    1213        let item = dep
    1214            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1215            .unwrap();
    1216
    1217        assert_eq!(key, "d".to_owned());
    1218        assert!(item.is_inline_table());
    1219
    1220        let dep = item.as_inline_table().unwrap();
    1221        assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
    1222        assert_eq!(dep.get("version").unwrap().as_str(), Some("1.0"));
    1223        assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
    1224
    1225        verify_roundtrip(&crate_root, &gctx, key, &item);
    1226    }
    1227
    1228    #[test]
    1229    fn paths_with_forward_slashes_are_left_as_is() {
    1230        let crate_root =
    1231            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1232        let path = crate_root.join("sibling/crate");
    1233        let relpath = "sibling/crate";
    1234        let dep = Dependency::new("dep").set_source(PathSource::new(path));
    1235        let key = dep.toml_key();
    1236        let gctx = GlobalContext::default().unwrap();
    1237        let item = dep
    1238            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1239            .unwrap();
    1240
    1241        let table = item.as_inline_table().unwrap();
    1242        let got = table.get("path").unwrap().as_str().unwrap();
    1243        assert_eq!(got, relpath);
    1244
    1245        verify_roundtrip(&crate_root, &gctx, key, &item);
    1246    }
    1247
    1248    #[test]
    1249    fn overwrite_with_workspace_source_fmt_key() {
    1250        let crate_root =
    1251            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("./")));
    1252        let toml = "dep = \"1.0\"\n";
    1253        let manifest = toml.parse().unwrap();
    1254        let mut local = LocalManifest {
    1255            path: crate_root.clone(),
    1256            manifest,
    1257            embedded: None,
    1258            raw: toml.to_owned(),
    1259        };
    1260        assert_eq!(local.manifest.to_string(), toml);
    1261        let gctx = GlobalContext::default().unwrap();
    1262        for (key, item) in local.data.clone().iter() {
    1263            let dep = Dependency::from_toml(
    1264                &gctx,
    1265                &crate_root,
    1266                &crate_root,
    1267                &Features::default(),
    1268                key,
    1269                item,
    1270            )
    1271            .unwrap();
    1272            let dep = dep.set_source(WorkspaceSource::new());
    1273            local
    1274                .insert_into_table(&[], &dep, &gctx, &crate_root, &Features::default())
    1275                .unwrap();
    1276            assert_eq!(local.data.to_string(), "dep.workspace = true\n");
    1277        }
    1278    }
    1279
    1280    #[test]
    1281    #[cfg(windows)]
    1282    fn normalise_windows_style_paths() {
    1283        let crate_root =
    1284            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1285        let original = crate_root.join(r"sibling\crate");
    1286        let should_be = "sibling/crate";
    1287        let dep = Dependency::new("dep").set_source(PathSource::new(original));
    1288        let key = dep.toml_key();
    1289        let gctx = GlobalContext::default().unwrap();
    1290        let item = dep
    1291            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1292            .unwrap();
    1293
    1294        let table = item.as_inline_table().unwrap();
    1295        let got = table.get("path").unwrap().as_str().unwrap();
    1296        assert_eq!(got, should_be);
    1297
    1298        verify_roundtrip(&crate_root, &gctx, key, &item);
    1299    }
    1300
    1301    #[track_caller]
    1302    fn verify_roundtrip(
    1303        crate_root: &Path,
    1304        gctx: &GlobalContext,
    1305        key: &str,
    1306        item: &toml_edit::Item,
    1307    ) {
    1308        let roundtrip = Dependency::from_toml(
    1309            gctx,
    1310            crate_root,
    1311            crate_root,
    1312            &Features::default(),
    1313            key,
    1314            item,
    1315        )
    1316        .unwrap();
    1317        let round_key = roundtrip.toml_key();
    1318        let round_item = roundtrip
    1319            .to_toml(gctx, crate_root, crate_root, &Features::default())
    1320            .unwrap();
    1321        assert_eq!(key, round_key);
    1322        assert_eq!(item.to_string(), round_item.to_string());
    1323    }
    1324}