Skip to content

Commit

Permalink
feat: add build argument for pin expressions (#1086)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfv authored Oct 7, 2024
1 parent 60a33d2 commit 369ef81
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 8 deletions.
9 changes: 9 additions & 0 deletions docs/reference/jinja.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ following input:
Both `lower_bound` and `upper_bound` expect a valid version string (e.g.
`1.2.3`).

To add an build-string matching expression, you can use the `build` argument:

- `build` (default: `None`): This will add a build string matching expression to
the pin. The build string matching expression is a string that is used to
match the build string with the match spec. For example, if the build string is
`py38_0`, the build string matching expression could be `py38*` or to match
exactly `py38_0`. The `build` and `exact` options are mutually exclusive.

#### The `pin_subpackage` function

- `${{ pin_subpackage("mypkg", lower_bound="x.x", upper_bound="x.x") }}` creates a pin
Expand All @@ -137,6 +145,7 @@ Both `lower_bound` and `upper_bound` expect a valid version string (e.g.
}}` creates a pin to another output in the recipe with a lower bound of
`1.2.3` and an upper bound of `1.2.4`. This is equivalent to writing
`other_output >=1.2.3,<1.2.4`.
- `${{ pin_subpackage("foo", build="py38*") }}` creates a matchspec like `foo >=3.1,<3.2.0a0 py38*`.

#### The `pin_compatible` function

Expand Down
11 changes: 11 additions & 0 deletions src/recipe/jinja.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ fn jinja_pin_function(
));
}

if let Ok(build) = kwargs.get::<String>("build") {
// if exact & build are set this is an error
if pin.args.exact {
return Err(minijinja::Error::new(
minijinja::ErrorKind::SyntaxError,
"Cannot set `exact` and `build` at the same time.".to_string(),
));
}
pin.args.build = Some(build);
}

kwargs.assert_all_used()?;

Ok(internal_repr.to_json(&pin))
Expand Down
5 changes: 3 additions & 2 deletions src/recipe/parser/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ mod test {
args: PinArgs {
lower_bound: Some("x.x.x.x".parse().unwrap()),
upper_bound: Some("x.x".parse().unwrap()),
exact: false,
..Default::default()
},
},
};
Expand All @@ -552,7 +552,7 @@ mod test {
args: PinArgs {
lower_bound: Some("x.x".parse().unwrap()),
upper_bound: Some("x.x.x".parse().unwrap()),
exact: false,
..Default::default()
},
},
};
Expand All @@ -564,6 +564,7 @@ mod test {
lower_bound: Some("x.x".parse().unwrap()),
upper_bound: None,
exact: true,
..Default::default()
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ requirements:
run:
- python >=3.11
- perl
- pin_subpackage:
name: test
exact: true
- pin_subpackage:
name: test
lower_bound: x.x.x.x.x.x
upper_bound: x
build: foo*
run_constraints:
- python * *cpython
run_exports:
Expand Down
39 changes: 34 additions & 5 deletions src/render/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ pub struct PinArgs {
/// If an exact pin is given, we pin the exact version & hash
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub exact: bool,

#[serde(default, skip_serializing_if = "Option::is_none")]
pub build: Option<String>,
}

impl Default for PinArgs {
Expand All @@ -96,6 +99,7 @@ impl Default for PinArgs {
lower_bound: Some("x.x.x.x.x.x".parse().unwrap()),
upper_bound: Some("x".parse().unwrap()),
exact: false,
build: None,
}
}
}
Expand All @@ -113,6 +117,9 @@ pub enum PinError {

#[error("Could not increment version: {0}")]
VersionBump(#[from] VersionBumpError),

#[error("Build specifier and exact=True are not supported together")]
BuildSpecifierWithExact,
}

pub fn increment(version: &Version, segments: i32) -> Result<Version, VersionBumpError> {
Expand All @@ -136,6 +143,10 @@ impl Pin {
/// Apply the pin to a version and hash of a resolved package. If a max_pin, min_pin or exact pin
/// are given, the pin is applied to the version accordingly.
pub fn apply(&self, version: &Version, build_string: &str) -> Result<MatchSpec, PinError> {
if self.args.build.is_some() && self.args.exact {
return Err(PinError::BuildSpecifierWithExact);
}

if self.args.exact {
return Ok(MatchSpec::from_str(
&format!("{} {} {}", self.name.as_normalized(), version, build_string),
Expand Down Expand Up @@ -196,8 +207,14 @@ impl Pin {
}

let name = self.name.as_normalized().to_string();
let build = self
.args
.build
.as_ref()
.map(|b| format!(" {}", b))
.unwrap_or_default();
Ok(MatchSpec::from_str(
format!("{name} {pin_str}").as_str().trim(),
format!("{name} {pin_str}{build}").as_str().trim(),
ParseStrictness::Strict,
)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?)
Expand Down Expand Up @@ -244,7 +261,7 @@ mod test {
args: PinArgs {
lower_bound: Some("x.x.x".parse().unwrap()),
upper_bound: Some("x.x.x".parse().unwrap()),
exact: false,
..Default::default()
},
};

Expand All @@ -262,7 +279,7 @@ mod test {
args: PinArgs {
upper_bound: Some("x.x.x".parse().unwrap()),
lower_bound: None,
exact: false,
..Default::default()
},
};

Expand All @@ -274,12 +291,23 @@ mod test {
args: PinArgs {
lower_bound: Some("x.x.x".parse().unwrap()),
upper_bound: None,
exact: false,
..Default::default()
},
};

let spec = pin.apply(&version, hash).unwrap();
assert_eq!(spec.to_string(), "foo >=1.2.3");

let pin = Pin {
name: "foo".parse().unwrap(),
args: PinArgs {
build: Some("foo*".to_string()),
..Default::default()
},
};

let spec = pin.apply(&version, hash).unwrap();
assert_eq!(spec.to_string(), "foo >=1.2.3,<2.0a0 foo*");
}

#[test]
Expand All @@ -290,6 +318,7 @@ mod test {
lower_bound: Some("x.x.x".parse().unwrap()),
upper_bound: Some("x.x.x".parse().unwrap()),
exact: true,
..Default::default()
},
};

Expand All @@ -306,7 +335,7 @@ mod test {
args: PinArgs {
lower_bound: Some("x.x.x".parse().unwrap()),
upper_bound: Some("2.4".parse().unwrap()),
exact: false,
..Default::default()
},
};

Expand Down
2 changes: 2 additions & 0 deletions src/render/resolved_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,7 @@ mod tests {
upper_bound: Some("x.x".parse().unwrap()),
lower_bound: Some("x.x.x".parse().unwrap()),
exact: true,
..Default::default()
},
}
.into(),
Expand All @@ -1046,6 +1047,7 @@ mod tests {
upper_bound: Some("x.x".parse().unwrap()),
lower_bound: Some("x.x.x".parse().unwrap()),
exact: true,
..Default::default()
},
}
.into(),
Expand Down
2 changes: 2 additions & 0 deletions test-data/recipes/test-parsing/single_output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ requirements:
run:
- python >=3.11
- perl
- ${{ pin_subpackage('test', exact=True) }}
- ${{ pin_subpackage('test', build="foo*") }}
run_constraints:
- python * *cpython
run_exports:
Expand Down
2 changes: 1 addition & 1 deletion test/end-to-end/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,4 +968,4 @@ def test_cache_install(
pkg1 = get_extracted_package(tmp_path, "check-1")
pkg2 = get_extracted_package(tmp_path, "check-2")
assert (pkg1 / "info/index.json").exists()
assert (pkg2 / "info/index.json").exists()
assert (pkg2 / "info/index.json").exists()

0 comments on commit 369ef81

Please sign in to comment.