1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use std::{
fmt,
hash::{Hash, Hasher},
path::PathBuf,
time::SystemTime,
};
use serde::{Deserialize, Serialize};
/// Used to specify from which particular revision of a repository.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum GitTarget {
/// The components is pointing to a specific revision in the repository.
#[serde(untagged)]
Revision {
#[serde(rename = "revision")]
hash: String,
},
/// The components is pointing to a specific tag in the repository.
#[serde(untagged)]
Tag {
#[serde(rename = "tag")]
name: String,
},
/// The components is pointing to a specific *branch* in the repository.
///
/// NOTE: When an update is issued, these type of components will trigger an update if the
/// branch they were pointing to had new commits since the time the component was installed.
/// This means that these components are _not_ deterministic and their behavior could change
/// in-between updates.
#[serde(untagged)]
Branch {
/// This is the name of the branch being tracked.
#[serde(rename = "branch")]
name: String,
/// This field represents the revision hash that is currently presently installed.
///
/// This is only meant to be used in the local manifest in order to check for updates.
latest_revision: Option<String>,
},
}
impl Default for GitTarget {
fn default() -> Self {
GitTarget::Branch {
name: String::from("main"),
latest_revision: None,
}
}
}
impl Eq for GitTarget {}
impl PartialEq for GitTarget {
fn eq(&self, other: &Self) -> bool {
match (&self, other) {
(Self::Revision { hash: hasha }, Self::Revision { hash: hashb }) => hasha == hashb,
(Self::Tag { name: taga }, Self::Tag { name: tagb }) => taga == tagb,
// Two components are "equal" if they are pointing to the same branch.
//
// Comparison between latest available commit is done ad-hoc
(Self::Branch { name: name_a, .. }, Self::Branch { name: name_b, .. }) => {
name_a == name_b
},
_ => false,
}
}
}
impl Hash for GitTarget {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
match &self {
Self::Revision { hash: hash_a } => hash_a.hash(state),
Self::Tag { name: tag_a } => tag_a.hash(state),
Self::Branch { name, .. } => name.hash(state),
}
}
}
impl fmt::Display for GitTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
GitTarget::Branch { name, .. } => write!(f, "branch = \"{name}"),
GitTarget::Revision { hash } => write!(f, "rev = \"{hash}"),
GitTarget::Tag { name: tag } => write!(f, "tag = \"{tag}"),
}
}
}
impl GitTarget {
pub fn to_cargo_flag(&self) -> [String; 2] {
match &self {
GitTarget::Branch { name, .. } => [String::from("--branch"), String::from(name)],
GitTarget::Revision { hash } => [String::from("--rev"), String::from(hash)],
GitTarget::Tag { name: tag } => [String::from("--tag"), String::from(tag)],
}
}
}
/// Represents the canonical versioning authority for a tool or toolchain
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Authority {
/// The authority for this tool/toolchain is a local filesystem path
#[serde(untagged)]
Path {
/// The path to the crate.
path: PathBuf,
/// This is the name of the crate that holds the executable we're going to install.
///
/// This has to be specified because cargo needs the name of the crate to handle
/// uninstallation.
crate_name: String,
/// Represents the latest modification done inside this directory.
last_modification: Option<SystemTime>,
},
/// The authority for this tool/toolchain is a git repository.
#[serde(untagged)]
Git {
/// Points to the git repository containting the [crate::channel::Component].
repository_url: String,
/// This is the name of the crate that holds the executable we're going to install.
///
/// This has to be specified because some repositories hold multiple crates inside them.
crate_name: String,
/// If the target is missing from the [crate::manifest::Manifest], then we assume that it
/// is pointing to the tip of the `main` branch
#[serde(default)]
#[serde(flatten)]
target: GitTarget,
},
/// The authority for this tool/toolchain is crates.io
#[serde(untagged)]
Cargo {
/// The name of the crates.io package under which this tool is provided.
///
/// If `None`, then the package name is the same as the component
#[serde(skip_serializing_if = "Option::is_none")]
package: Option<String>,
/// The semantic versioning string for the package to fetch
version: semver::Version,
},
}
impl fmt::Display for Authority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Authority::Cargo { version, .. } => write!(f, "{version}"),
Authority::Git { repository_url, target, .. } => {
write!(f, "{repository_url}:{target}")
},
Authority::Path { path, .. } => write!(f, "{}", path.display()),
}
}
}