Skip to content

Commit b4f699e

Browse files
committed
feat: Support parse string as git/gitoxide features from ENV and Config
- pass 'all' or true to enable all pre-definded git/gitoxide features - support parse git/gitoxide as table in Config, if the field is tagged with #[serde(default)], then it can be skipped
1 parent 1d19e76 commit b4f699e

File tree

3 files changed

+400
-31
lines changed

3 files changed

+400
-31
lines changed

src/cargo/core/features.rs

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,9 @@ unstable_cli_options!(
759759
dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
760760
features: Option<Vec<String>>,
761761
gc: bool = ("Track cache usage and \"garbage collect\" unused files"),
762+
#[serde(deserialize_with = "deserialize_git_features")]
762763
git: Option<GitFeatures> = ("Enable support for shallow git fetch operations"),
764+
#[serde(deserialize_with = "deserialize_gitoxide_features")]
763765
gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
764766
host_config: bool = ("Enable the `[host]` section in the .cargo/config.toml file"),
765767
minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
@@ -866,7 +868,8 @@ where
866868
))
867869
}
868870

869-
#[derive(Debug, Copy, Clone, Default, Deserialize)]
871+
#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
872+
#[serde(default)]
870873
pub struct GitFeatures {
871874
/// When cloning the index, perform a shallow clone. Maintain shallowness upon subsequent fetches.
872875
pub shallow_index: bool,
@@ -875,12 +878,71 @@ pub struct GitFeatures {
875878
}
876879

877880
impl GitFeatures {
878-
fn all() -> Self {
881+
pub fn all() -> Self {
879882
GitFeatures {
880883
shallow_index: true,
881884
shallow_deps: true,
882885
}
883886
}
887+
888+
fn expecting() -> String {
889+
let fields = vec!["'all'", "'shallow-index'", "'shallow-deps'"];
890+
format!(
891+
"unstable 'git' only takes {} as valid inputs, your can use 'all' to turn out all git features",
892+
fields.join(" and ")
893+
)
894+
}
895+
}
896+
897+
fn deserialize_git_features<'de, D>(deserializer: D) -> Result<Option<GitFeatures>, D::Error>
898+
where
899+
D: serde::de::Deserializer<'de>,
900+
{
901+
struct GitFeaturesVisitor;
902+
903+
impl<'de> serde::de::Visitor<'de> for GitFeaturesVisitor {
904+
type Value = Option<GitFeatures>;
905+
906+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
907+
formatter.write_str(&GitFeatures::expecting())
908+
}
909+
910+
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
911+
where
912+
E: serde::de::Error,
913+
{
914+
if v {
915+
Ok(Some(GitFeatures::all()))
916+
} else {
917+
Ok(None)
918+
}
919+
}
920+
921+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
922+
where
923+
E: serde::de::Error,
924+
{
925+
Ok(parse_git(s.split(",")).map_err(serde::de::Error::custom)?)
926+
}
927+
928+
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
929+
where
930+
D: serde::de::Deserializer<'de>,
931+
{
932+
let git = GitFeatures::deserialize(deserializer)?;
933+
Ok(Some(git))
934+
}
935+
936+
fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
937+
where
938+
V: serde::de::MapAccess<'de>,
939+
{
940+
let mvd = serde::de::value::MapAccessDeserializer::new(map);
941+
Ok(Some(GitFeatures::deserialize(mvd)?))
942+
}
943+
}
944+
945+
deserializer.deserialize_any(GitFeaturesVisitor)
884946
}
885947

886948
fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<GitFeatures>> {
@@ -892,19 +954,19 @@ fn parse_git(it: impl Iterator<Item = impl AsRef<str>>) -> CargoResult<Option<Gi
892954

893955
for e in it {
894956
match e.as_ref() {
957+
"all" => return Ok(Some(GitFeatures::all())),
895958
"shallow-index" => *shallow_index = true,
896959
"shallow-deps" => *shallow_deps = true,
897960
_ => {
898-
bail!(
899-
"unstable 'git' only takes 'shallow-index' and 'shallow-deps' as valid inputs"
900-
)
961+
bail!(GitFeatures::expecting())
901962
}
902963
}
903964
}
904965
Ok(Some(out))
905966
}
906967

907-
#[derive(Debug, Copy, Clone, Default, Deserialize)]
968+
#[derive(Debug, Copy, Clone, Default, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
969+
#[serde(default)]
908970
pub struct GitoxideFeatures {
909971
/// All fetches are done with `gitoxide`, which includes git dependencies as well as the crates index.
910972
pub fetch: bool,
@@ -918,7 +980,7 @@ pub struct GitoxideFeatures {
918980
}
919981

920982
impl GitoxideFeatures {
921-
fn all() -> Self {
983+
pub fn all() -> Self {
922984
GitoxideFeatures {
923985
fetch: true,
924986
checkout: true,
@@ -935,6 +997,67 @@ impl GitoxideFeatures {
935997
internal_use_git2: false,
936998
}
937999
}
1000+
1001+
fn expecting() -> String {
1002+
let fields = vec!["'all'", "'fetch'", "'checkout'", "'internal-use-git2'"];
1003+
format!(
1004+
"unstable 'gitoxide' only takes {} as valid inputs, your can use 'all' to turn out all gitoxide features, for shallow fetches see `shallow-index,shallow-deps`",
1005+
fields.join(" and ")
1006+
)
1007+
}
1008+
}
1009+
1010+
fn deserialize_gitoxide_features<'de, D>(
1011+
deserializer: D,
1012+
) -> Result<Option<GitoxideFeatures>, D::Error>
1013+
where
1014+
D: serde::de::Deserializer<'de>,
1015+
{
1016+
struct GitoxideFeaturesVisitor;
1017+
1018+
impl<'de> serde::de::Visitor<'de> for GitoxideFeaturesVisitor {
1019+
type Value = Option<GitoxideFeatures>;
1020+
1021+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1022+
formatter.write_str(&GitoxideFeatures::expecting())
1023+
}
1024+
1025+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1026+
where
1027+
E: serde::de::Error,
1028+
{
1029+
Ok(parse_gitoxide(s.split(",")).map_err(serde::de::Error::custom)?)
1030+
}
1031+
1032+
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
1033+
where
1034+
E: serde::de::Error,
1035+
{
1036+
if v {
1037+
Ok(Some(GitoxideFeatures::all()))
1038+
} else {
1039+
Ok(None)
1040+
}
1041+
}
1042+
1043+
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1044+
where
1045+
D: serde::de::Deserializer<'de>,
1046+
{
1047+
let gitoxide = GitoxideFeatures::deserialize(deserializer)?;
1048+
Ok(Some(gitoxide))
1049+
}
1050+
1051+
fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
1052+
where
1053+
V: serde::de::MapAccess<'de>,
1054+
{
1055+
let mvd = serde::de::value::MapAccessDeserializer::new(map);
1056+
Ok(Some(GitoxideFeatures::deserialize(mvd)?))
1057+
}
1058+
}
1059+
1060+
deserializer.deserialize_any(GitoxideFeaturesVisitor)
9381061
}
9391062

9401063
fn parse_gitoxide(
@@ -949,11 +1072,12 @@ fn parse_gitoxide(
9491072

9501073
for e in it {
9511074
match e.as_ref() {
1075+
"all" => return Ok(Some(GitoxideFeatures::all())),
9521076
"fetch" => *fetch = true,
9531077
"checkout" => *checkout = true,
9541078
"internal-use-git2" => *internal_use_git2 = true,
9551079
_ => {
956-
bail!("unstable 'gitoxide' only takes `fetch` and 'checkout' as valid input, for shallow fetches see `-Zgit=shallow-index,shallow-deps`")
1080+
bail!(GitoxideFeatures::expecting())
9571081
}
9581082
}
9591083
}

src/cargo/util/context/de.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
6262
let (res, def) = res;
6363
return res.map_err(|e| e.with_key_context(&self.key, def));
6464
}
65+
66+
// The effect here is the same as in [`deserialize_option`].
67+
if self.gctx.has_key(&self.key, self.env_prefix_ok)? {
68+
return visitor.visit_some(self);
69+
}
70+
6571
Err(ConfigError::missing(&self.key))
6672
}
6773

@@ -265,8 +271,14 @@ impl<'gctx> ConfigMapAccess<'gctx> {
265271
let mut field_key = de.key.clone();
266272
field_key.push(field);
267273
for env_key in de.gctx.env_keys() {
268-
if env_key.starts_with(field_key.as_env_key()) {
274+
if env_key == field_key.as_env_key() {
269275
fields.insert(KeyKind::Normal(field.to_string()));
276+
} else {
277+
let mut env_key_prefix = field_key.as_env_key().to_string();
278+
env_key_prefix.push('_');
279+
if env_key.starts_with(&env_key_prefix) {
280+
fields.insert(KeyKind::Normal(field.to_string()));
281+
}
270282
}
271283
}
272284
}

0 commit comments

Comments
 (0)