Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 119 additions & 2 deletions crates/spk-build/src/build/binary_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use spfstest::spfstest;
use spk_schema::foundation::env::data_path;
use spk_schema::foundation::fixtures::*;
use spk_schema::foundation::ident_component::Component;
use spk_schema::foundation::spec_ops::HasVersion;
use spk_schema::foundation::{opt_name, option_map, version_ident};
use spk_schema::ident::{PkgRequest, PkgRequestWithOptions, RangeIdent, RequestWithOptions};
use spk_schema::{
Expand All @@ -24,8 +25,8 @@ use spk_schema::{
SpecRecipe,
recipe,
};
use spk_solve::{Solution, SolverImpl};
use spk_solve_macros::make_repo;
use spk_solve::{Solution, SolverExt, SolverImpl, SolverMut};
use spk_solve_macros::{make_repo, pinned_request};
use spk_storage::fixtures::*;
use spk_storage::{self as storage, Repository};

Expand Down Expand Up @@ -274,6 +275,122 @@ async fn test_build_package_pinning(
}
}

#[spfstest]
#[rstest]
#[case::step(step_solver())]
#[case::resolvo(resolvo_solver())]
#[tokio::test]
async fn test_build_package_with_v1_platform_pins_frombuildenv_and_solves_binary_compatible_runtime(
#[case] solver: SolverImpl,
) {
let rt = spfs_runtime().await;
let dep_1_0_0 = recipe!(
{
"pkg": "dep/1.0.0",
"build": {"script": "touch /spfs/dep-file"},
}
);
let dep_1_0_1 = recipe!(
{
"pkg": "dep/1.0.1",
"build": {"script": "touch /spfs/dep-file"},
}
);
let platform = SpecRecipe::from_yaml(
r#"{
api: "v1/platform",
platform: "dep-platform/1.0.0",
requirements: [
{
pkg: "dep",
atBuild: "=1.0.0",
atRuntime: "Binary:1.0.0",
}
],
}"#,
)
.unwrap();
let downstream = recipe!(
{
"pkg": "consumer/1.0.0",
"build": {
"script": ["touch /spfs/consumer-file"],
"options": [
{"pkg": "dep"},
{"pkg": "dep-platform/1.0.0"},
],
},
"install": {
"requirements": [
{"pkg": "dep", "fromBuildEnv": true},
]
},
}
);

for recipe in [&dep_1_0_0, &dep_1_0_1] {
rt.tmprepo.publish_recipe(recipe).await.unwrap();

BinaryPackageBuilder::from_recipe_with_solver(recipe.clone(), solver.clone())
.with_source(BuildSource::LocalPath(".".into()))
.with_repository(rt.tmprepo.clone())
.build_and_publish(option_map! {}, &*rt.tmprepo)
.await
.unwrap();
}

rt.tmprepo.publish_recipe(&platform).await.unwrap();
let (_platform, _) = BinaryPackageBuilder::from_recipe_with_solver(platform, solver.clone())
.with_source(BuildSource::LocalPath(".".into()))
.with_repository(rt.tmprepo.clone())
.build_and_publish(option_map! {}, &*rt.tmprepo)
.await
.unwrap();

rt.tmprepo.publish_recipe(&downstream).await.unwrap();
let (downstream, _) =
BinaryPackageBuilder::from_recipe_with_solver(downstream.clone(), solver.clone())
.with_source(BuildSource::LocalPath(".".into()))
.with_repository(rt.tmprepo.clone())
.build_and_publish(option_map! {}, &*rt.tmprepo)
.await
.unwrap();

let downstream = rt.tmprepo.read_package(downstream.ident()).await.unwrap();
let req = downstream.runtime_requirements().first().unwrap().clone();
match req {
RequestWithOptions::Pkg(req) => {
assert_eq!(req.pkg.to_string(), "dep/Binary:1.0.0");
}
_ => panic!("expected a package request"),
}

let mut solver = solver;
solver.add_repository(rt.tmprepo.clone());
solver.add_request(pinned_request!("dep/=1.0.1"));
solver.add_request(pinned_request!("dep-platform"));
solver.add_request(pinned_request!("consumer"));

let solution = solver.solve().await.unwrap();
assert_eq!(
solution.get("dep").unwrap().spec.version().to_string(),
"1.0.1"
);
assert_eq!(
solution
.get("dep-platform")
.unwrap()
.spec
.version()
.to_string(),
"1.0.0"
);
assert_eq!(
solution.get("consumer").unwrap().spec.version().to_string(),
"1.0.0"
);
}

#[spfstest]
#[rstest]
#[case::step(step_solver())]
Expand Down
49 changes: 49 additions & 0 deletions crates/spk-cli/cmd-make-binary/src/cmd_make_binary_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,52 @@ build:
.await
.expect("With override, build script should succeed.");
}

#[spfstest]
#[rstest]
#[case::v0("v0/platform", true)]
#[case::v1("v1/platform", true)]
#[tokio::test]
async fn build_a_platform(
tmpdir: tempfile::TempDir,
#[case] api: &str,
#[case] should_succeed: bool,
) {
let _rt = spfs_runtime().await;

let filename = tmpdir.path().join("simple.spk.yaml");
{
let mut file = File::create(&filename).unwrap();
file.write_all(
format!(
r#"
platform: demo-platform/1.0.0
api: {api}
requirements: []
"#
)
.as_bytes(),
)
.unwrap();
}

let filename_str = filename.as_os_str().to_str().unwrap();

let mut opt = Opt::try_parse_from([
"make-binary",
// Don't exec a new process to move into a new runtime, this confuses
// coverage testing.
"--no-runtime",
"--disable-repo=origin",
"--here",
filename_str,
])
.unwrap();
let res = opt.mkb.run().await;

if should_succeed {
res.expect("Build should succeed.");
} else {
assert!(res.is_err(), "Build should fail, got {res:?}.");
}
}
17 changes: 17 additions & 0 deletions crates/spk-schema/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,11 @@ impl FromYaml for SpecRecipe {
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
Ok(Self::V0Platform(inner))
}
ApiVersion::V1Platform => {
let inner = serde_yaml::from_str(&yaml)
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
Ok(Self::V1Platform(inner))
}
ApiVersion::V0Requirements => {
// Reading a list of requests/requirements file is not
// supported here. But it might be in future.
Expand Down Expand Up @@ -567,6 +572,11 @@ impl SpecFileData {
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
SpecFileData::Recipe(Arc::new(SpecRecipe::V0Platform(inner)))
}
ApiVersion::V1Platform => {
let inner = serde_yaml::from_value(value)
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
SpecFileData::Recipe(Arc::new(SpecRecipe::V1Platform(inner)))
}
ApiVersion::V0Requirements => {
let requests: v0::Requirements = serde_yaml::from_value(value)
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
Expand Down Expand Up @@ -885,6 +895,11 @@ impl FromYaml for Spec {
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
Ok(Self::V0Package(inner))
}
ApiVersion::V1Platform => {
let inner = serde_yaml::from_str(&yaml)
.map_err(|err| SerdeError::new(yaml, SerdeYamlError(err)))?;
Ok(Self::V0Package(inner))
}
ApiVersion::V0Requirements => {
// Reading a list of requests/requirement file is not
// supported here. But it might be in future.
Expand Down Expand Up @@ -925,6 +940,8 @@ pub enum ApiVersion {
V0Package,
#[serde(rename = "v0/platform")]
V0Platform,
#[serde(rename = "v1/platform")]
V1Platform,
#[serde(rename = "v0/requirements")]
V0Requirements,
}
24 changes: 24 additions & 0 deletions crates/spk-schema/src/spec_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,27 @@ fn test_template_namespace_options() {
let recipe = rendered_data.into_recipe().unwrap();
assert_eq!(recipe.version().to_string(), "1.0.0");
}

#[rstest]
fn test_template_render_supports_v1_platform_recipes() {
static SPEC: &str = r#"
api: v1/platform
platform: my-platform/{{ opt.version }}
requirements: []
"#;

let tpl = SpecTemplate {
name: Some(PkgName::new("my-platform").unwrap().to_owned()),
file_path: "my-platform.spk.yaml".into(),
versions: Default::default(),
template: SPEC.into(),
};
let options = option_map! {"version" => "1.0.0"};
let rendered_data = tpl
.render(&options)
.expect("template should render a v1/platform recipe");
let recipe = rendered_data.into_recipe().unwrap();

assert!(matches!(recipe.as_ref(), crate::SpecRecipe::V1Platform(_)));
assert_eq!(recipe.ident().to_string(), "my-platform/1.0.0");
}
21 changes: 18 additions & 3 deletions crates/spk-solve/crates/graph/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,24 @@ impl<'state> DecisionBuilder<'state, '_> {
// is buggy now
continue;
}
changes.extend(
self.requirements_to_changes(component.requirements_with_options(), &requested_by),
);
changes.extend(component.requirements_with_options().iter().flat_map(
|req| match req {
RequestWithOptions::Pkg(req) => {
let mut req = req.clone();
req.add_requester(requested_by.clone());
if component.name == Component::Build
&& req.pkg.components.is_empty()
&& !req.pkg.is_source()
{
req.pkg.components.insert(Component::default_for_build());
}
self.pkg_request_to_changes(&req)
}
RequestWithOptions::Var(req) => {
vec![Change::RequestVar(RequestVar::new(req.clone()))]
}
},
));
}
changes
}
Expand Down
51 changes: 33 additions & 18 deletions crates/spk-solve/src/solvers/resolvo/spk_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,19 +719,6 @@ impl SpkProvider {
.collect()
}

/// Return a list of requirements for all the package requests found in the
/// given requests.
fn dep_pkg_requirements(&self, requests: &[RequestWithOptions]) -> Vec<Requirement> {
requests
.iter()
.filter_map(|req| match req {
RequestWithOptions::Pkg(pkg) => Some(pkg),
_ => None,
})
.flat_map(|req| self.pkg_request_to_known_dependencies(req).requirements)
.collect()
}

pub fn is_canceled(&self) -> bool {
self.cancel_solving.borrow().is_some()
}
Expand Down Expand Up @@ -821,6 +808,27 @@ impl SpkProvider {
known_deps
}

/// Return known dependencies for a requirement as interpreted from the
/// given component context.
fn request_to_known_dependencies_in_component_context(
&self,
requirement: &RequestWithOptions,
component: &Component,
) -> KnownDependencies {
let mut requirement = requirement.clone();
if *component == Component::Build
&& let RequestWithOptions::Pkg(pkg_request) = &mut requirement
&& pkg_request.pkg.components.is_empty()
&& !pkg_request.pkg.is_source()
{
pkg_request
.pkg
.components
.insert(Component::default_for_build());
}
self.request_to_known_dependencies(&requirement)
}

/// Return a new provider to restart the solve, preserving what was learned
/// about global variables.
pub fn reset(&self) -> Self {
Expand Down Expand Up @@ -1437,9 +1445,14 @@ impl DependencyProvider for SpkProvider {
.into(),
);
});
known_deps.requirements.extend(
self.dep_pkg_requirements(component_spec.requirements_with_options()),
);
for requirement in component_spec.requirements_with_options().iter() {
let kd = self.request_to_known_dependencies_in_component_context(
requirement,
actual_component,
);
known_deps.requirements.extend(kd.requirements);
known_deps.constrains.extend(kd.constrains);
}
}
}
// Also add dependencies on any packages embedded in this
Expand Down Expand Up @@ -1557,8 +1570,10 @@ impl DependencyProvider for SpkProvider {
embedded_component.requirements_with_options().iter()
})
{
let kd =
self.request_to_known_dependencies(embedded_component_requirement);
let kd = self.request_to_known_dependencies_in_component_context(
embedded_component_requirement,
actual_component,
);
known_deps.requirements.extend(kd.requirements);
known_deps.constrains.extend(kd.constrains);
}
Expand Down
Loading