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