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