#
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::core::SourceId;
    14use crate::core::Summary;
    15use crate::core::{Features, GitReference};
    16use crate::util::toml::lookup_path_base;
    17use crate::CargoResult;
    18use crate::GlobalContext;
    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!("Use of `default_features` in `{key}` is unsupported, please switch to `default-features`");
    347            }
    348
    349            let features = if let Some(value) = table.get("features") {
    350                Some(
    351                    value
    352                        .as_array()
    353                        .ok_or_else(|| invalid_type(key, "features", value.type_name(), "array"))?
    354                        .iter()
    355                        .map(|v| {
    356                            v.as_str().map(|s| s.to_owned()).ok_or_else(|| {
    357                                invalid_type(key, "features", v.type_name(), "string")
    358                            })
    359                        })
    360                        .collect::<CargoResult<IndexSet<String>>>()?,
    361                )
    362            } else {
    363                None
    364            };
    365
    366            let optional = table.get("optional").and_then(|v| v.as_bool());
    367            let public = table.get("public").and_then(|v| v.as_bool());
    368
    369            let dep = Self {
    370                name,
    371                optional,
    372                public,
    373                features,
    374                default_features,
    375                inherited_features: None,
    376                source: Some(source),
    377                registry,
    378                rename,
    379            };
    380            Ok(dep)
    381        } else {
    382            anyhow::bail!("Unrecognized` dependency entry format for `{key}");
    383        }
    384    }
    385
    386    /// Get the dependency name as defined in the manifest,
    387    /// that is, either the alias (rename field if Some),
    388    /// or the official package name (name field).
    389    pub fn toml_key(&self) -> &str {
    390        self.rename().unwrap_or(&self.name)
    391    }
    392
    393    /// Convert dependency to TOML.
    394    ///
    395    /// Returns a tuple with the dependency's name and either the version as a
    396    /// `String` or the path/git repository as an `InlineTable`.
    397    /// (If the dependency is set as `optional` or `default-features` is set to
    398    /// `false`, an `InlineTable` is returned in any case.)
    399    ///
    400    /// # Panic
    401    ///
    402    /// Panics if the path is relative
    403    pub fn to_toml<'a>(
    404        &self,
    405        gctx: &GlobalContext,
    406        workspace_root: &Path,
    407        crate_root: &Path,
    408        unstable_features: &Features,
    409    ) -> CargoResult<toml_edit::Item> {
    410        assert!(
    411            crate_root.is_absolute(),
    412            "Absolute path needed, got: {}",
    413            crate_root.display()
    414        );
    415        let table: toml_edit::Item = match (
    416            self.public.unwrap_or(false),
    417            self.optional.unwrap_or(false),
    418            self.features.as_ref(),
    419            self.default_features.unwrap_or(true),
    420            self.source.as_ref(),
    421            self.registry.as_ref(),
    422            self.rename.as_ref(),
    423        ) {
    424            // Extra short when version flag only
    425            (
    426                false,
    427                false,
    428                None,
    429                true,
    430                Some(Source::Registry(RegistrySource { version: v })),
    431                None,
    432                None,
    433            ) => toml_edit::value(v),
    434            (false, false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
    435                let mut table = toml_edit::InlineTable::default();
    436                table.set_dotted(true);
    437                table.insert("workspace", true.into());
    438                toml_edit::value(toml_edit::Value::InlineTable(table))
    439            }
    440            // Other cases are represented as an inline table
    441            (_, _, _, _, _, _, _) => {
    442                let mut table = toml_edit::InlineTable::default();
    443
    444                match &self.source {
    445                    Some(Source::Registry(src)) => {
    446                        table.insert("version", src.version.as_str().into());
    447                    }
    448                    Some(Source::Path(src)) => {
    449                        let relpath =
    450                            path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
    451                        if let Some(r) = src.version.as_deref() {
    452                            table.insert("version", r.into());
    453                        }
    454                        if let Some(base) = &src.base {
    455                            table.insert("base", base.into());
    456                        }
    457                        table.insert("path", relpath.into());
    458                    }
    459                    Some(Source::Git(src)) => {
    460                        table.insert("git", src.git.as_str().into());
    461                        if let Some(branch) = src.branch.as_deref() {
    462                            table.insert("branch", branch.into());
    463                        }
    464                        if let Some(tag) = src.tag.as_deref() {
    465                            table.insert("tag", tag.into());
    466                        }
    467                        if let Some(rev) = src.rev.as_deref() {
    468                            table.insert("rev", rev.into());
    469                        }
    470                        if let Some(r) = src.version.as_deref() {
    471                            table.insert("version", r.into());
    472                        }
    473                    }
    474                    Some(Source::Workspace(_)) => {
    475                        table.insert("workspace", true.into());
    476                    }
    477                    None => {}
    478                }
    479                if table.contains_key("version") {
    480                    if let Some(r) = self.registry.as_deref() {
    481                        table.insert("registry", r.into());
    482                    }
    483                }
    484
    485                if self.rename.is_some() {
    486                    table.insert("package", self.name.as_str().into());
    487                }
    488                if let Some(v) = self.default_features {
    489                    table.insert("default-features", v.into());
    490                }
    491                if let Some(features) = self.features.as_ref() {
    492                    let features: toml_edit::Value = features.iter().cloned().collect();
    493                    table.insert("features", features);
    494                }
    495                if let Some(v) = self.optional {
    496                    table.insert("optional", v.into());
    497                }
    498                if let Some(v) = self.public {
    499                    table.insert("public", v.into());
    500                }
    501
    502                toml_edit::value(toml_edit::Value::InlineTable(table))
    503            }
    504        };
    505
    506        Ok(table)
    507    }
    508
    509    /// Modify existing entry to match this dependency.
    510    pub fn update_toml<'k, 'a>(
    511        &self,
    512        gctx: &GlobalContext,
    513        workspace_root: &Path,
    514        crate_root: &Path,
    515        unstable_features: &Features,
    516        key: &mut KeyMut<'k>,
    517        item: &mut toml_edit::Item,
    518    ) -> CargoResult<()> {
    519        if str_or_1_len_table(item) {
    520            // Little to preserve
    521            let mut new_item = self.to_toml(gctx, workspace_root, crate_root, unstable_features)?;
    522            match (&item, &mut new_item) {
    523                (toml_edit::Item::Value(old), toml_edit::Item::Value(new)) => {
    524                    *new.decor_mut() = old.decor().clone();
    525                }
    526                (toml_edit::Item::Table(old), toml_edit::Item::Table(new)) => {
    527                    *new.decor_mut() = old.decor().clone();
    528                }
    529                (_, _) => {}
    530            }
    531            *item = new_item;
    532        } else if let Some(table) = item.as_table_like_mut() {
    533            match &self.source {
    534                Some(Source::Registry(src)) => {
    535                    overwrite_value(table, "version", src.version.as_str());
    536
    537                    for key in ["path", "git", "branch", "tag", "rev", "workspace", "base"] {
    538                        table.remove(key);
    539                    }
    540                }
    541                Some(Source::Path(src)) => {
    542                    if let Some(base) = &src.base {
    543                        overwrite_value(table, "base", base);
    544                    } else {
    545                        table.remove("base");
    546                    }
    547                    let relpath =
    548                        path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
    549                    overwrite_value(table, "path", relpath);
    550                    if let Some(r) = src.version.as_deref() {
    551                        overwrite_value(table, "version", r);
    552                    } else {
    553                        table.remove("version");
    554                    }
    555
    556                    for key in ["git", "branch", "tag", "rev", "workspace"] {
    557                        table.remove(key);
    558                    }
    559                }
    560                Some(Source::Git(src)) => {
    561                    overwrite_value(table, "git", src.git.as_str());
    562                    if let Some(branch) = src.branch.as_deref() {
    563                        overwrite_value(table, "branch", branch);
    564                    } else {
    565                        table.remove("branch");
    566                    }
    567                    if let Some(tag) = src.tag.as_deref() {
    568                        overwrite_value(table, "tag", tag);
    569                    } else {
    570                        table.remove("tag");
    571                    }
    572                    if let Some(rev) = src.rev.as_deref() {
    573                        overwrite_value(table, "rev", rev);
    574                    } else {
    575                        table.remove("rev");
    576                    }
    577                    if let Some(r) = src.version.as_deref() {
    578                        overwrite_value(table, "version", r);
    579                    } else {
    580                        table.remove("version");
    581                    }
    582
    583                    for key in ["path", "workspace", "base"] {
    584                        table.remove(key);
    585                    }
    586                }
    587                Some(Source::Workspace(_)) => {
    588                    overwrite_value(table, "workspace", true);
    589                    table.set_dotted(true);
    590                    key.fmt();
    591                    for key in [
    592                        "version",
    593                        "registry",
    594                        "registry-index",
    595                        "path",
    596                        "git",
    597                        "branch",
    598                        "tag",
    599                        "rev",
    600                        "package",
    601                        "default-features",
    602                        "base",
    603                    ] {
    604                        table.remove(key);
    605                    }
    606                }
    607                None => {}
    608            }
    609            if table.contains_key("version") {
    610                if let Some(r) = self.registry.as_deref() {
    611                    overwrite_value(table, "registry", r);
    612                } else {
    613                    table.remove("registry");
    614                }
    615            } else {
    616                table.remove("registry");
    617            }
    618
    619            if self.rename.is_some() {
    620                overwrite_value(table, "package", self.name.as_str());
    621            }
    622            match self.default_features {
    623                Some(v) => {
    624                    overwrite_value(table, "default-features", v);
    625                }
    626                None => {
    627                    table.remove("default-features");
    628                }
    629            }
    630            if let Some(new_features) = self.features.as_ref() {
    631                let mut features = table
    632                    .get("features")
    633                    .and_then(|i| i.as_value())
    634                    .and_then(|v| v.as_array())
    635                    .and_then(|a| {
    636                        a.iter()
    637                            .map(|v| v.as_str())
    638                            .collect::<Option<IndexSet<_>>>()
    639                    })
    640                    .unwrap_or_default();
    641                let is_already_sorted = features.iter().is_sorted();
    642                features.extend(new_features.iter().map(|s| s.as_str()));
    643                let features = if is_already_sorted {
    644                    features.into_iter().sorted().collect::<toml_edit::Value>()
    645                } else {
    646                    features.into_iter().collect::<toml_edit::Value>()
    647                };
    648                table.set_dotted(false);
    649                overwrite_value(table, "features", features);
    650            } else {
    651                table.remove("features");
    652            }
    653            match self.optional {
    654                Some(v) => {
    655                    table.set_dotted(false);
    656                    overwrite_value(table, "optional", v);
    657                }
    658                None => {
    659                    table.remove("optional");
    660                }
    661            }
    662            match self.public {
    663                Some(v) => {
    664                    table.set_dotted(false);
    665                    overwrite_value(table, "public", v);
    666                }
    667                None => {
    668                    table.remove("public");
    669                }
    670            }
    671        } else {
    672            unreachable!("Invalid dependency type: {}", item.type_name());
    673        }
    674        Ok(())
    675    }
    676}
    677
    678fn overwrite_value(
    679    table: &mut dyn toml_edit::TableLike,
    680    key: &str,
    681    value: impl Into<toml_edit::Value>,
    682) {
    683    let mut value = value.into();
    684    let existing = table.entry(key).or_insert_with(|| Default::default());
    685    if let Some(existing_value) = existing.as_value() {
    686        *value.decor_mut() = existing_value.decor().clone();
    687    }
    688    *existing = toml_edit::Item::Value(value);
    689}
    690
    691fn invalid_type(dep: &str, key: &str, actual: &str, expected: &str) -> anyhow::Error {
    692    anyhow::format_err!("Found {actual} for {key} when {expected} was expected for {dep}")
    693}
    694
    695impl std::fmt::Display for Dependency {
    696    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    697        if let Some(source) = self.source() {
    698            write!(f, "{}@{}", self.name, source)
    699        } else {
    700            self.toml_key().fmt(f)
    701        }
    702    }
    703}
    704
    705impl<'s> From<&'s Summary> for Dependency {
    706    fn from(other: &'s Summary) -> Self {
    707        let source: Source = if let Some(path) = other.source_id().local_path() {
    708            PathSource::new(path)
    709                .set_version(other.version().to_string())
    710                .into()
    711        } else if let Some(git_ref) = other.source_id().git_reference() {
    712            let mut src = GitSource::new(other.source_id().url().to_string())
    713                .set_version(other.version().to_string());
    714            match git_ref {
    715                GitReference::Branch(branch) => src = src.set_branch(branch),
    716                GitReference::Tag(tag) => src = src.set_tag(tag),
    717                GitReference::Rev(rev) => src = src.set_rev(rev),
    718                GitReference::DefaultBranch => {}
    719            }
    720            src.into()
    721        } else {
    722            RegistrySource::new(other.version().to_string()).into()
    723        };
    724        Dependency::new(other.name().as_str()).set_source(source)
    725    }
    726}
    727
    728impl From<Summary> for Dependency {
    729    fn from(other: Summary) -> Self {
    730        (&other).into()
    731    }
    732}
    733
    734fn path_field<'a>(
    735    source: &PathSource,
    736    gctx: &GlobalContext,
    737    workspace_root: &Path,
    738    crate_root: &Path,
    739    unstable_features: &Features,
    740) -> CargoResult<String> {
    741    let relative_to = if let Some(base) = &source.base {
    742        Cow::Owned(lookup_path_base(
    743            &PathBaseName::new(base.clone())?,
    744            gctx,
    745            &|| Ok(workspace_root),
    746            unstable_features,
    747        )?)
    748    } else {
    749        Cow::Borrowed(crate_root)
    750    };
    751    let relpath = pathdiff::diff_paths(&source.path, relative_to)
    752        .expect("PathSource::path and workspace path must be absolute");
    753    let relpath = relpath.to_str().unwrap().replace('\\', "/");
    754    Ok(relpath)
    755}
    756
    757/// Primary location of a dependency.
    758#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    759pub enum Source {
    760    /// Dependency from a registry.
    761    Registry(RegistrySource),
    762    /// Dependency from a local path.
    763    Path(PathSource),
    764    /// Dependency from a git repo.
    765    Git(GitSource),
    766    /// Dependency from a workspace.
    767    Workspace(WorkspaceSource),
    768}
    769
    770impl Source {
    771    /// Access the registry source, if present.
    772    pub fn as_registry(&self) -> Option<&RegistrySource> {
    773        match self {
    774            Self::Registry(src) => Some(src),
    775            _ => None,
    776        }
    777    }
    778
    779    /// Access the path source, if present.
    780    #[allow(dead_code)]
    781    pub fn as_path(&self) -> Option<&PathSource> {
    782        match self {
    783            Self::Path(src) => Some(src),
    784            _ => None,
    785        }
    786    }
    787
    788    /// Access the git source, if present.
    789    #[allow(dead_code)]
    790    pub fn as_git(&self) -> Option<&GitSource> {
    791        match self {
    792            Self::Git(src) => Some(src),
    793            _ => None,
    794        }
    795    }
    796
    797    /// Access the workspace source, if present.
    798    #[allow(dead_code)]
    799    pub fn as_workspace(&self) -> Option<&WorkspaceSource> {
    800        match self {
    801            Self::Workspace(src) => Some(src),
    802            _ => None,
    803        }
    804    }
    805}
    806
    807impl std::fmt::Display for Source {
    808    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    809        match self {
    810            Self::Registry(src) => src.fmt(f),
    811            Self::Path(src) => src.fmt(f),
    812            Self::Git(src) => src.fmt(f),
    813            Self::Workspace(src) => src.fmt(f),
    814        }
    815    }
    816}
    817
    818impl<'s> From<&'s Source> for Source {
    819    fn from(inner: &'s Source) -> Self {
    820        inner.clone()
    821    }
    822}
    823
    824impl From<RegistrySource> for Source {
    825    fn from(inner: RegistrySource) -> Self {
    826        Self::Registry(inner)
    827    }
    828}
    829
    830impl From<PathSource> for Source {
    831    fn from(inner: PathSource) -> Self {
    832        Self::Path(inner)
    833    }
    834}
    835
    836impl From<GitSource> for Source {
    837    fn from(inner: GitSource) -> Self {
    838        Self::Git(inner)
    839    }
    840}
    841
    842impl From<WorkspaceSource> for Source {
    843    fn from(inner: WorkspaceSource) -> Self {
    844        Self::Workspace(inner)
    845    }
    846}
    847
    848/// Dependency from a registry.
    849#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    850#[non_exhaustive]
    851pub struct RegistrySource {
    852    /// Version requirement.
    853    pub version: String,
    854}
    855
    856impl RegistrySource {
    857    /// Specify dependency by version requirement.
    858    pub fn new(version: impl AsRef<str>) -> Self {
    859        // versions might have semver metadata appended which we do not want to
    860        // store in the cargo toml files.  This would cause a warning upon compilation
    861        // ("version requirement […] includes semver metadata which will be ignored")
    862        let version = version.as_ref().split('+').next().unwrap();
    863        Self {
    864            version: version.to_owned(),
    865        }
    866    }
    867}
    868
    869impl std::fmt::Display for RegistrySource {
    870    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    871        self.version.fmt(f)
    872    }
    873}
    874
    875/// Dependency from a local path.
    876#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    877#[non_exhaustive]
    878pub struct PathSource {
    879    /// Local, absolute path.
    880    pub path: PathBuf,
    881    /// The path base, if using one.
    882    pub base: Option<String>,
    883    /// Version requirement for when published.
    884    pub version: Option<String>,
    885}
    886
    887impl PathSource {
    888    /// Specify dependency from a path.
    889    pub fn new(path: impl Into<PathBuf>) -> Self {
    890        Self {
    891            path: path.into(),
    892            base: None,
    893            version: None,
    894        }
    895    }
    896
    897    /// Set an optional version requirement.
    898    pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
    899        // versions might have semver metadata appended which we do not want to
    900        // store in the cargo toml files.  This would cause a warning upon compilation
    901        // ("version requirement […] includes semver metadata which will be ignored")
    902        let version = version.as_ref().split('+').next().unwrap();
    903        self.version = Some(version.to_owned());
    904        self
    905    }
    906
    907    /// Get the `SourceID` for this dependency.
    908    pub fn source_id(&self) -> CargoResult<SourceId> {
    909        SourceId::for_path(&self.path)
    910    }
    911}
    912
    913impl std::fmt::Display for PathSource {
    914    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    915        self.path.display().fmt(f)
    916    }
    917}
    918
    919/// Dependency from a git repo.
    920#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    921#[non_exhaustive]
    922pub struct GitSource {
    923    /// Repository URL.
    924    pub git: String,
    925    /// Select specific branch.
    926    pub branch: Option<String>,
    927    /// Select specific tag.
    928    pub tag: Option<String>,
    929    /// Select specific rev.
    930    pub rev: Option<String>,
    931    /// Version requirement for when published.
    932    pub version: Option<String>,
    933}
    934
    935impl GitSource {
    936    /// Specify dependency from a git repo.
    937    pub fn new(git: impl Into<String>) -> Self {
    938        Self {
    939            git: git.into(),
    940            branch: None,
    941            tag: None,
    942            rev: None,
    943            version: None,
    944        }
    945    }
    946
    947    /// Specify an optional branch.
    948    pub fn set_branch(mut self, branch: impl Into<String>) -> Self {
    949        self.branch = Some(branch.into());
    950        self.tag = None;
    951        self.rev = None;
    952        self
    953    }
    954
    955    /// Specify an optional tag.
    956    pub fn set_tag(mut self, tag: impl Into<String>) -> Self {
    957        self.branch = None;
    958        self.tag = Some(tag.into());
    959        self.rev = None;
    960        self
    961    }
    962
    963    /// Specify an optional rev.
    964    pub fn set_rev(mut self, rev: impl Into<String>) -> Self {
    965        self.branch = None;
    966        self.tag = None;
    967        self.rev = Some(rev.into());
    968        self
    969    }
    970
    971    /// Get the `SourceID` for this dependency.
    972    pub fn source_id(&self) -> CargoResult<SourceId> {
    973        let git_url = self.git.parse::<url::Url>()?;
    974        let git_ref = self.git_ref();
    975        SourceId::for_git(&git_url, git_ref)
    976    }
    977
    978    fn git_ref(&self) -> GitReference {
    979        match (
    980            self.branch.as_deref(),
    981            self.tag.as_deref(),
    982            self.rev.as_deref(),
    983        ) {
    984            (Some(branch), _, _) => GitReference::Branch(branch.to_owned()),
    985            (_, Some(tag), _) => GitReference::Tag(tag.to_owned()),
    986            (_, _, Some(rev)) => GitReference::Rev(rev.to_owned()),
    987            _ => GitReference::DefaultBranch,
    988        }
    989    }
    990
    991    /// Set an optional version requirement.
    992    pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
    993        // versions might have semver metadata appended which we do not want to
    994        // store in the cargo toml files.  This would cause a warning upon compilation
    995        // ("version requirement […] includes semver metadata which will be ignored")
    996        let version = version.as_ref().split('+').next().unwrap();
    997        self.version = Some(version.to_owned());
    998        self
    999    }
    1000}
    1001
    1002impl std::fmt::Display for GitSource {
    1003    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    1004        let git_ref = self.git_ref();
    1005        if let Some(pretty_ref) = git_ref.pretty_ref(true) {
    1006            write!(f, "{}?{}", self.git, pretty_ref)
    1007        } else {
    1008            write!(f, "{}", self.git)
    1009        }
    1010    }
    1011}
    1012
    1013/// Dependency from a workspace.
    1014#[derive(Debug, Hash, PartialEq, Eq, Clone)]
    1015#[non_exhaustive]
    1016pub struct WorkspaceSource;
    1017
    1018impl WorkspaceSource {
    1019    pub fn new() -> Self {
    1020        Self
    1021    }
    1022}
    1023
    1024impl Display for WorkspaceSource {
    1025    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    1026        "workspace".fmt(f)
    1027    }
    1028}
    1029
    1030#[cfg(test)]
    1031mod tests {
    1032    use crate::util::toml_mut::manifest::LocalManifest;
    1033    use cargo_util::paths;
    1034
    1035    use super::*;
    1036
    1037    #[test]
    1038    fn to_toml_simple_dep() {
    1039        let crate_root =
    1040            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1041        let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
    1042        let key = dep.toml_key();
    1043        let gctx = GlobalContext::default().unwrap();
    1044        let item = dep
    1045            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1046            .unwrap();
    1047
    1048        assert_eq!(key, "dep".to_owned());
    1049
    1050        verify_roundtrip(&crate_root, &gctx, key, &item);
    1051    }
    1052
    1053    #[test]
    1054    fn to_toml_simple_dep_with_version() {
    1055        let crate_root =
    1056            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1057        let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
    1058        let key = dep.toml_key();
    1059        let gctx = GlobalContext::default().unwrap();
    1060        let item = dep
    1061            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1062            .unwrap();
    1063
    1064        assert_eq!(key, "dep".to_owned());
    1065        assert_eq!(item.as_str(), Some("1.0"));
    1066
    1067        verify_roundtrip(&crate_root, &gctx, key, &item);
    1068    }
    1069
    1070    #[test]
    1071    fn to_toml_optional_dep() {
    1072        let crate_root =
    1073            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1074        let dep = Dependency::new("dep")
    1075            .set_source(RegistrySource::new("1.0"))
    1076            .set_optional(true);
    1077        let key = dep.toml_key();
    1078        let gctx = GlobalContext::default().unwrap();
    1079        let item = dep
    1080            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1081            .unwrap();
    1082
    1083        assert_eq!(key, "dep".to_owned());
    1084        assert!(item.is_inline_table());
    1085
    1086        let dep = item.as_inline_table().unwrap();
    1087        assert_eq!(dep.get("optional").unwrap().as_bool(), Some(true));
    1088
    1089        verify_roundtrip(&crate_root, &gctx, key, &item);
    1090    }
    1091
    1092    #[test]
    1093    fn to_toml_dep_without_default_features() {
    1094        let crate_root =
    1095            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1096        let dep = Dependency::new("dep")
    1097            .set_source(RegistrySource::new("1.0"))
    1098            .set_default_features(false);
    1099        let key = dep.toml_key();
    1100        let gctx = GlobalContext::default().unwrap();
    1101        let item = dep
    1102            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1103            .unwrap();
    1104
    1105        assert_eq!(key, "dep".to_owned());
    1106        assert!(item.is_inline_table());
    1107
    1108        let dep = item.as_inline_table().unwrap();
    1109        assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
    1110
    1111        verify_roundtrip(&crate_root, &gctx, key, &item);
    1112    }
    1113
    1114    #[test]
    1115    fn to_toml_dep_with_path_source() {
    1116        let root = paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1117        let crate_root = root.join("foo");
    1118        let dep = Dependency::new("dep").set_source(PathSource::new(root.join("bar")));
    1119        let key = dep.toml_key();
    1120        let gctx = GlobalContext::default().unwrap();
    1121        let item = dep
    1122            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1123            .unwrap();
    1124
    1125        assert_eq!(key, "dep".to_owned());
    1126        assert!(item.is_inline_table());
    1127
    1128        let dep = item.as_inline_table().unwrap();
    1129        assert_eq!(dep.get("path").unwrap().as_str(), Some("../bar"));
    1130
    1131        verify_roundtrip(&crate_root, &gctx, key, &item);
    1132    }
    1133
    1134    #[test]
    1135    fn to_toml_dep_with_git_source() {
    1136        let crate_root =
    1137            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1138        let dep = Dependency::new("dep").set_source(GitSource::new("https://foor/bar.git"));
    1139        let key = dep.toml_key();
    1140        let gctx = GlobalContext::default().unwrap();
    1141        let item = dep
    1142            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1143            .unwrap();
    1144
    1145        assert_eq!(key, "dep".to_owned());
    1146        assert!(item.is_inline_table());
    1147
    1148        let dep = item.as_inline_table().unwrap();
    1149        assert_eq!(
    1150            dep.get("git").unwrap().as_str(),
    1151            Some("https://foor/bar.git")
    1152        );
    1153
    1154        verify_roundtrip(&crate_root, &gctx, key, &item);
    1155    }
    1156
    1157    #[test]
    1158    fn to_toml_renamed_dep() {
    1159        let crate_root =
    1160            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1161        let dep = Dependency::new("dep")
    1162            .set_source(RegistrySource::new("1.0"))
    1163            .set_rename("d");
    1164        let key = dep.toml_key();
    1165        let gctx = GlobalContext::default().unwrap();
    1166        let item = dep
    1167            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1168            .unwrap();
    1169
    1170        assert_eq!(key, "d".to_owned());
    1171        assert!(item.is_inline_table());
    1172
    1173        let dep = item.as_inline_table().unwrap();
    1174        assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
    1175
    1176        verify_roundtrip(&crate_root, &gctx, key, &item);
    1177    }
    1178
    1179    #[test]
    1180    fn to_toml_dep_from_alt_registry() {
    1181        let crate_root =
    1182            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1183        let dep = Dependency::new("dep")
    1184            .set_source(RegistrySource::new("1.0"))
    1185            .set_registry("alternative");
    1186        let key = dep.toml_key();
    1187        let gctx = GlobalContext::default().unwrap();
    1188        let item = dep
    1189            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1190            .unwrap();
    1191
    1192        assert_eq!(key, "dep".to_owned());
    1193        assert!(item.is_inline_table());
    1194
    1195        let dep = item.as_inline_table().unwrap();
    1196        assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
    1197
    1198        verify_roundtrip(&crate_root, &gctx, key, &item);
    1199    }
    1200
    1201    #[test]
    1202    fn to_toml_complex_dep() {
    1203        let crate_root =
    1204            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1205        let dep = Dependency::new("dep")
    1206            .set_source(RegistrySource::new("1.0"))
    1207            .set_default_features(false)
    1208            .set_rename("d");
    1209        let key = dep.toml_key();
    1210        let gctx = GlobalContext::default().unwrap();
    1211        let item = dep
    1212            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1213            .unwrap();
    1214
    1215        assert_eq!(key, "d".to_owned());
    1216        assert!(item.is_inline_table());
    1217
    1218        let dep = item.as_inline_table().unwrap();
    1219        assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
    1220        assert_eq!(dep.get("version").unwrap().as_str(), Some("1.0"));
    1221        assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
    1222
    1223        verify_roundtrip(&crate_root, &gctx, key, &item);
    1224    }
    1225
    1226    #[test]
    1227    fn paths_with_forward_slashes_are_left_as_is() {
    1228        let crate_root =
    1229            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1230        let path = crate_root.join("sibling/crate");
    1231        let relpath = "sibling/crate";
    1232        let dep = Dependency::new("dep").set_source(PathSource::new(path));
    1233        let key = dep.toml_key();
    1234        let gctx = GlobalContext::default().unwrap();
    1235        let item = dep
    1236            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1237            .unwrap();
    1238
    1239        let table = item.as_inline_table().unwrap();
    1240        let got = table.get("path").unwrap().as_str().unwrap();
    1241        assert_eq!(got, relpath);
    1242
    1243        verify_roundtrip(&crate_root, &gctx, key, &item);
    1244    }
    1245
    1246    #[test]
    1247    fn overwrite_with_workspace_source_fmt_key() {
    1248        let crate_root =
    1249            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("./")));
    1250        let toml = "dep = \"1.0\"\n";
    1251        let manifest = toml.parse().unwrap();
    1252        let mut local = LocalManifest {
    1253            path: crate_root.clone(),
    1254            manifest,
    1255            embedded: None,
    1256            raw: toml.to_owned(),
    1257        };
    1258        assert_eq!(local.manifest.to_string(), toml);
    1259        let gctx = GlobalContext::default().unwrap();
    1260        for (key, item) in local.data.clone().iter() {
    1261            let dep = Dependency::from_toml(
    1262                &gctx,
    1263                &crate_root,
    1264                &crate_root,
    1265                &Features::default(),
    1266                key,
    1267                item,
    1268            )
    1269            .unwrap();
    1270            let dep = dep.set_source(WorkspaceSource::new());
    1271            local
    1272                .insert_into_table(&vec![], &dep, &gctx, &crate_root, &Features::default())
    1273                .unwrap();
    1274            assert_eq!(local.data.to_string(), "dep.workspace = true\n");
    1275        }
    1276    }
    1277
    1278    #[test]
    1279    #[cfg(windows)]
    1280    fn normalise_windows_style_paths() {
    1281        let crate_root =
    1282            paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
    1283        let original = crate_root.join(r"sibling\crate");
    1284        let should_be = "sibling/crate";
    1285        let dep = Dependency::new("dep").set_source(PathSource::new(original));
    1286        let key = dep.toml_key();
    1287        let gctx = GlobalContext::default().unwrap();
    1288        let item = dep
    1289            .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
    1290            .unwrap();
    1291
    1292        let table = item.as_inline_table().unwrap();
    1293        let got = table.get("path").unwrap().as_str().unwrap();
    1294        assert_eq!(got, should_be);
    1295
    1296        verify_roundtrip(&crate_root, &gctx, key, &item);
    1297    }
    1298
    1299    #[track_caller]
    1300    fn verify_roundtrip(
    1301        crate_root: &Path,
    1302        gctx: &GlobalContext,
    1303        key: &str,
    1304        item: &toml_edit::Item,
    1305    ) {
    1306        let roundtrip = Dependency::from_toml(
    1307            gctx,
    1308            crate_root,
    1309            crate_root,
    1310            &Features::default(),
    1311            key,
    1312            item,
    1313        )
    1314        .unwrap();
    1315        let round_key = roundtrip.toml_key();
    1316        let round_item = roundtrip
    1317            .to_toml(gctx, crate_root, crate_root, &Features::default())
    1318            .unwrap();
    1319        assert_eq!(key, round_key);
    1320        assert_eq!(item.to_string(), round_item.to_string());
    1321    }
    1322}