1use 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#[derive(Debug, PartialEq, Eq, Clone)]
24#[non_exhaustive]
25pub struct Dependency {
26 pub name: String,
29 pub optional: Option<bool>,
31
32 pub public: Option<bool>,
34
35 pub features: Option<IndexSet<String>>,
37 pub default_features: Option<bool>,
39 pub inherited_features: Option<IndexSet<String>>,
41
42 pub source: Option<Source>,
44 pub registry: Option<String>,
46
47 pub rename: Option<String>,
50}
51
52impl Dependency {
53 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 pub fn set_source(mut self, source: impl Into<Source>) -> Self {
70 self.source = Some(source.into());
71 self
72 }
73
74 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 #[allow(dead_code)]
94 pub fn set_optional(mut self, opt: bool) -> Self {
95 self.optional = Some(opt);
96 self
97 }
98
99 #[allow(dead_code)]
101 pub fn set_features(mut self, features: IndexSet<String>) -> Self {
102 self.features = Some(features);
103 self
104 }
105
106 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 #[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 pub fn set_rename(mut self, rename: &str) -> Self {
123 self.rename = Some(rename.into());
124 self
125 }
126
127 pub fn set_registry(mut self, registry: impl Into<String>) -> Self {
129 self.registry = Some(registry.into());
130 self
131 }
132
133 pub fn set_inherited_features(mut self, features: IndexSet<String>) -> Self {
135 self.inherited_features = Some(features);
136 self
137 }
138
139 pub fn source(&self) -> Option<&Source> {
141 self.source.as_ref()
142 }
143
144 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 pub fn registry(&self) -> Option<&str> {
156 self.registry.as_deref()
157 }
158
159 pub fn rename(&self) -> Option<&str> {
161 self.rename.as_deref()
162 }
163
164 pub fn default_features(&self) -> Option<bool> {
166 self.default_features
167 }
168
169 pub fn optional(&self) -> Option<bool> {
171 self.optional
172 }
173
174 pub fn public(&self) -> Option<bool> {
176 self.public
177 }
178
179 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 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
216pub enum MaybeWorkspace<T> {
218 Workspace(WorkspaceSource),
219 Other(T),
220}
221
222impl Dependency {
223 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 pub fn toml_key(&self) -> &str {
388 self.rename().unwrap_or(&self.name)
389 }
390
391 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 (
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 (_, _, _, _, _, _, _) => {
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 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 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#[derive(Debug, Hash, PartialEq, Eq, Clone)]
757pub enum Source {
758 Registry(RegistrySource),
760 Path(PathSource),
762 Git(GitSource),
764 Workspace(WorkspaceSource),
766}
767
768impl Source {
769 pub fn as_registry(&self) -> Option<&RegistrySource> {
771 match self {
772 Self::Registry(src) => Some(src),
773 _ => None,
774 }
775 }
776
777 #[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 #[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 #[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#[derive(Debug, Hash, PartialEq, Eq, Clone)]
848#[non_exhaustive]
849pub struct RegistrySource {
850 pub version: String,
852}
853
854impl RegistrySource {
855 pub fn new(version: impl AsRef<str>) -> Self {
857 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#[derive(Debug, Hash, PartialEq, Eq, Clone)]
875#[non_exhaustive]
876pub struct PathSource {
877 pub path: PathBuf,
879 pub base: Option<String>,
881 pub version: Option<String>,
883}
884
885impl PathSource {
886 pub fn new(path: impl Into<PathBuf>) -> Self {
888 Self {
889 path: path.into(),
890 base: None,
891 version: None,
892 }
893 }
894
895 pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
897 let version = version.as_ref().split('+').next().unwrap();
901 self.version = Some(version.to_owned());
902 self
903 }
904
905 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#[derive(Debug, Hash, PartialEq, Eq, Clone)]
919#[non_exhaustive]
920pub struct GitSource {
921 pub git: String,
923 pub branch: Option<String>,
925 pub tag: Option<String>,
927 pub rev: Option<String>,
929 pub version: Option<String>,
931}
932
933impl GitSource {
934 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 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 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 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 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 pub fn set_version(mut self, version: impl AsRef<str>) -> Self {
991 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#[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}