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