Skip to content

Commit ea43485

Browse files
authored
Merge pull request #563 from imageworks/296-embedded-stub-trait
Remove v0::Opt from the Package/Recipe Traits
2 parents a326fbb + d07e319 commit ea43485

File tree

25 files changed

+765
-274
lines changed

25 files changed

+765
-274
lines changed

crates/progress_bar_derive_macro/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ fn impl_proc_macro_derive(ast: &syn::DeriveInput) -> TokenStream {
146146
// or nothing will be shown in the terminal
147147
let renderer = Some(std::thread::spawn(move || {
148148
if let Err(err) = bars.join() {
149-
tracing::error!("Failed to render commit progress: {err}");
149+
tracing::error!("Failed to render commit progress: {}", err);
150150
}
151151
}));
152152
Self {

crates/spk-build/src/build/binary.rs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ use spk_schema::foundation::ident_component::Component;
2525
use spk_schema::foundation::option_map::OptionMap;
2626
use spk_schema::foundation::version::VERSION_SEP;
2727
use spk_schema::ident::{PkgRequest, PreReleasePolicy, RangeIdent, RequestedBy, VersionIdent};
28+
use spk_schema::version::Compatibility;
2829
use spk_schema::{
2930
BuildIdent,
3031
ComponentFileMatchMode,
3132
ComponentSpecList,
3233
Package,
3334
PackageMut,
35+
RequirementsList,
3436
Variant,
3537
VariantExt,
3638
};
@@ -266,7 +268,7 @@ where
266268
};
267269

268270
tracing::debug!("Resolving build environment");
269-
let solution = self
271+
let (build_requirements, solution) = self
270272
.resolve_build_environment(&all_options, &variant)
271273
.await?;
272274
self.environment
@@ -362,6 +364,7 @@ where
362364
let package = self
363365
.recipe
364366
.generate_binary_build(&full_variant, &solution)?;
367+
self.validate_generated_package(&build_requirements, &solution, &package)?;
365368
let components = self
366369
.build_and_commit_artifacts(&package, full_variant.options())
367370
.await?;
@@ -430,7 +433,7 @@ where
430433
&mut self,
431434
options: &OptionMap,
432435
variant: &V,
433-
) -> Result<Solution>
436+
) -> Result<(RequirementsList, Solution)>
434437
where
435438
V: Variant,
436439
{
@@ -441,13 +444,56 @@ where
441444
self.solver.add_repository(repo);
442445
}
443446

444-
for request in self.recipe.get_build_requirements(variant)? {
445-
self.solver.add_request(request);
447+
let build_requirements = self.recipe.get_build_requirements(variant)?.into_owned();
448+
for request in build_requirements.iter() {
449+
self.solver.add_request(request.clone());
446450
}
447451

448452
let (solution, graph) = self.build_resolver.solve(&self.solver).await?;
449453
self.last_solve_graph = graph;
450-
Ok(solution)
454+
Ok((build_requirements, solution))
455+
}
456+
457+
fn validate_generated_package(
458+
&self,
459+
build_requirements: &RequirementsList,
460+
solution: &Solution,
461+
package: &Recipe::Output,
462+
) -> Result<()> {
463+
let runtime_requirements = package.runtime_requirements();
464+
let solved_packages = solution.items().map(|r| Arc::clone(&r.spec));
465+
let all_components = package.components();
466+
for spec in solved_packages {
467+
for component in all_components.names() {
468+
let downstream_build = spec.downstream_build_requirements([component]);
469+
for request in downstream_build.iter() {
470+
match build_requirements.contains_request(request) {
471+
Compatibility::Compatible => continue,
472+
Compatibility::Incompatible(reason) => {
473+
return Err(Error::MissingDownstreamBuildRequest {
474+
required_by: spec.ident().to_owned(),
475+
request: request.clone(),
476+
problem: reason.to_string(),
477+
})
478+
}
479+
}
480+
}
481+
let downstream_runtime = spec.downstream_build_requirements([component]);
482+
for request in downstream_runtime.iter() {
483+
match runtime_requirements.contains_request(request) {
484+
Compatibility::Compatible => continue,
485+
Compatibility::Incompatible(reason) => {
486+
return Err(Error::MissingDownstreamRuntimeRequest {
487+
required_by: spec.ident().to_owned(),
488+
request: request.clone(),
489+
problem: reason.to_string(),
490+
})
491+
}
492+
}
493+
}
494+
}
495+
}
496+
Ok(())
451497
}
452498

453499
/// Helper for constructing more useful error messages from schema validator errors

crates/spk-build/src/build/binary_test.rs

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,14 @@ use spk_schema::foundation::fixtures::*;
1010
use spk_schema::foundation::ident_component::Component;
1111
use spk_schema::foundation::{opt_name, option_map};
1212
use spk_schema::ident::{PkgRequest, RangeIdent, Request};
13-
use spk_schema::{
14-
recipe,
15-
ComponentSpecList,
16-
FromYaml,
17-
Inheritance,
18-
Opt,
19-
Package,
20-
Recipe,
21-
SpecRecipe,
22-
};
13+
use spk_schema::{recipe, ComponentSpecList, FromYaml, Package, Recipe, SpecRecipe};
2314
use spk_solve::Solution;
2415
use spk_storage::fixtures::*;
2516
use spk_storage::{self as storage, Repository};
2617

2718
use super::{BinaryPackageBuilder, BuildSource};
2819
use crate::build::SourcePackageBuilder;
20+
use crate::Error;
2921

3022
#[rstest]
3123
fn test_split_manifest_permissions() {
@@ -317,14 +309,14 @@ async fn test_build_var_pinning() {
317309
.unwrap();
318310

319311
let spec = rt.tmprepo.read_package(spec.ident()).await.unwrap();
320-
let top_req = spec.runtime_requirements().get(0).unwrap();
312+
let top_req = spec.runtime_requirements().get(0).unwrap().clone();
321313
match top_req {
322-
Request::Var(r) => assert_eq!(&r.value, "topvalue"),
314+
Request::Var(r) => assert_eq!(r.value.as_pinned(), Some("topvalue")),
323315
_ => panic!("expected var request"),
324316
}
325-
let depreq = spec.runtime_requirements().get(1).unwrap();
317+
let depreq = spec.runtime_requirements().get(1).unwrap().clone();
326318
match depreq {
327-
Request::Var(r) => assert_eq!(&r.value, "depvalue"),
319+
Request::Var(r) => assert_eq!(r.value.as_pinned(), Some("depvalue")),
328320
_ => panic!("expected var request"),
329321
}
330322
}
@@ -519,14 +511,14 @@ async fn test_build_filters_unmodified_files() {
519511

520512
#[rstest]
521513
#[tokio::test]
522-
async fn test_build_package_requirement_propagation() {
514+
async fn test_build_package_downstream_build_requests() {
523515
let rt = spfs_runtime().await;
524516
let base_spec = recipe!(
525517
{
526518
"pkg": "base/1.0.0",
527519
"sources": [],
528520
"build": {
529-
"options": [{"var": "inherited/val", "inheritance": "Strong"}],
521+
"options": [{"var": "inherited/val", "inheritance": "StrongForBuildOnly"}],
530522
"script": "echo building...",
531523
},
532524
}
@@ -538,14 +530,14 @@ async fn test_build_package_requirement_propagation() {
538530
"build": {"options": [{"pkg": "base"}], "script": "echo building..."},
539531
}
540532
);
541-
rt.tmprepo.publish_recipe(&base_spec).await.unwrap();
542533
rt.tmprepo.publish_recipe(&top_spec).await.unwrap();
534+
rt.tmprepo.publish_recipe(&base_spec).await.unwrap();
543535

544536
SourcePackageBuilder::from_recipe(base_spec.clone())
545537
.build_and_publish(".", &*rt.tmprepo)
546538
.await
547539
.unwrap();
548-
let _base_pkg = BinaryPackageBuilder::from_recipe(base_spec)
540+
let (base_pkg, _) = BinaryPackageBuilder::from_recipe(base_spec)
549541
.with_repository(rt.tmprepo.clone())
550542
.build_and_publish(option_map! {}, &*rt.tmprepo)
551543
.await
@@ -555,45 +547,85 @@ async fn test_build_package_requirement_propagation() {
555547
.build_and_publish(".", &*rt.tmprepo)
556548
.await
557549
.unwrap();
558-
let (top_pkg, _) = BinaryPackageBuilder::from_recipe(top_spec)
550+
let result = BinaryPackageBuilder::from_recipe(top_spec)
559551
.with_repository(rt.tmprepo.clone())
560552
.build_and_publish(option_map! {}, &*rt.tmprepo)
561-
.await
562-
.unwrap();
553+
.await;
563554

564-
assert_eq!(top_pkg.options().len(), 2, "should get option added");
565-
let opt = top_pkg.options().get(1).unwrap();
566-
match opt {
567-
Opt::Var(opt) => {
568-
assert_eq!(
569-
&*opt.var, "base.inherited",
570-
"should be inherited as package option"
571-
);
572-
assert_eq!(
573-
opt.inheritance,
574-
Inheritance::Weak,
575-
"inherited option should have weak inheritance"
555+
match result {
556+
Err(Error::MissingDownstreamBuildRequest {
557+
required_by,
558+
request,
559+
..
560+
}) => {
561+
assert_eq!(&required_by, base_pkg.ident());
562+
assert!(
563+
matches!(&request, Request::Var(v) if v.var.as_str() == "base.inherited" && v.value.as_pinned() == Some("val")),
564+
"{request}"
576565
);
577566
}
578-
_ => panic!("should be given inherited option"),
567+
Err(err) => panic!("Expected Error::MissingDownstreamBuildRequest, got {err:?}"),
568+
Ok(_) => panic!("should error when downstream package does not define inherited opt"),
579569
}
570+
}
580571

581-
assert_eq!(
582-
top_pkg.runtime_requirements().len(),
583-
1,
584-
"should get install requirement"
572+
#[rstest]
573+
#[tokio::test]
574+
async fn test_build_package_downstream_runtime_request() {
575+
let rt = spfs_runtime().await;
576+
let base_spec = recipe!(
577+
{
578+
"pkg": "base/1.0.0",
579+
"sources": [],
580+
"build": {
581+
"options": [{"var": "inherited/val", "inheritance": "Strong"}],
582+
"script": "echo building...",
583+
},
584+
}
585585
);
586-
let req = top_pkg.runtime_requirements().get(0).unwrap();
587-
match req {
588-
Request::Var(req) => {
589-
assert_eq!(
590-
&*req.var, "base.inherited",
591-
"should be inherited with package namespace"
586+
let top_spec = recipe!(
587+
{
588+
"pkg": "top/1.0.0",
589+
"sources": [],
590+
"build": {"options": [{"pkg": "base"}, {"var": "inherited/val"}], "script": "echo building..."},
591+
}
592+
);
593+
rt.tmprepo.publish_recipe(&top_spec).await.unwrap();
594+
rt.tmprepo.publish_recipe(&base_spec).await.unwrap();
595+
596+
SourcePackageBuilder::from_recipe(base_spec.clone())
597+
.build_and_publish(".", &*rt.tmprepo)
598+
.await
599+
.unwrap();
600+
let (base_pkg, _) = BinaryPackageBuilder::from_recipe(base_spec)
601+
.with_repository(rt.tmprepo.clone())
602+
.build_and_publish(&option_map! {}, &*rt.tmprepo)
603+
.await
604+
.unwrap();
605+
606+
SourcePackageBuilder::from_recipe(top_spec.clone())
607+
.build_and_publish(".", &*rt.tmprepo)
608+
.await
609+
.unwrap();
610+
let result = BinaryPackageBuilder::from_recipe(top_spec)
611+
.with_repository(rt.tmprepo.clone())
612+
.build_and_publish(&option_map! {}, &*rt.tmprepo)
613+
.await;
614+
615+
match result {
616+
Err(Error::MissingDownstreamRuntimeRequest {
617+
required_by,
618+
request,
619+
..
620+
}) => {
621+
assert_eq!(&required_by, base_pkg.ident());
622+
assert!(
623+
matches!(&request, Request::Var(v) if v.var.as_str() == "base.inherited" && v.value.as_pinned() == Some("val")),
624+
"{request}"
592625
);
593-
assert!(!req.pin, "should not be pinned after build");
594-
assert_eq!(req.value, "val", "should be rendered to build time var");
595626
}
596-
_ => panic!("should be given var request"),
627+
Err(err) => panic!("Expected Error::MissingDownstreamRuntimeRequest, got {err}"),
628+
Ok(_) => panic!("should error when downstream package does not define inherited opt"),
597629
}
598630
}
599631

crates/spk-build/src/error.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// https://github.com/imageworks/spk
44

5+
use spk_schema::BuildIdent;
6+
use spk_solve::Request;
57
use thiserror::Error;
68

79
pub type Result<T> = std::result::Result<T, Error>;
@@ -20,6 +22,24 @@ pub enum Error {
2022
FileWriteError(std::path::PathBuf, #[source] std::io::Error),
2123
#[error(transparent)]
2224
ProcessSpawnError(spfs::Error),
25+
#[error("Package must include a build requirement for {request}, because it's being built against {required_by}, but {problem}")]
26+
MissingDownstreamBuildRequest {
27+
/// The package that was in the build environment and created the need for this request
28+
required_by: BuildIdent,
29+
/// The minimum request that is required downstream
30+
request: Request,
31+
/// Additional reasoning why an existing request was not sufficient
32+
problem: String,
33+
},
34+
#[error("Package must include a runtime requirement for {request}, because it's being built against {required_by}, but {problem}")]
35+
MissingDownstreamRuntimeRequest {
36+
/// The package that was in the build environment and created the need for this request
37+
required_by: BuildIdent,
38+
/// The minimum request that is required downstream
39+
request: Request,
40+
/// Additional reasoning why an existing request was not sufficient
41+
problem: String,
42+
},
2343
#[error(transparent)]
2444
SPFS(#[from] spfs::Error),
2545
#[error(transparent)]

crates/spk-cli/common/src/flags.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ impl Requests {
415415
}
416416
None => recipe.get_build_requirements(&options)?,
417417
};
418-
out.extend(requirements);
418+
out.extend(requirements.into_owned());
419419
}
420420

421421
TestStage::Install => {

crates/spk-schema/crates/foundation/src/version/compat.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl Ord for CompatRule {
8787
}
8888

8989
/// Denotes whether or not something is compatible.
90-
#[derive(Clone, Debug)]
90+
#[derive(Clone, Debug, Eq, PartialEq)]
9191
pub enum IncompatibleReason {
9292
ConflictingEmbeddedPackage(PkgNameBuf),
9393
Other(String),
@@ -109,7 +109,7 @@ impl std::fmt::Display for IncompatibleReason {
109109

110110
/// Denotes whether or not something is compatible.
111111
#[must_use = "this `Compatibility` may be an `Incompatible` variant, which should be handled"]
112-
#[derive(Clone, Debug)]
112+
#[derive(Clone, Debug, Eq, PartialEq)]
113113
pub enum Compatibility {
114114
Compatible,
115115
Incompatible(IncompatibleReason),
@@ -129,8 +129,8 @@ impl std::ops::Not for &'_ Compatibility {
129129

130130
fn not(self) -> Self::Output {
131131
match self {
132-
super::Compatibility::Compatible => false,
133-
super::Compatibility::Incompatible(_) => true,
132+
Compatibility::Compatible => false,
133+
Compatibility::Incompatible(_) => true,
134134
}
135135
}
136136
}
@@ -142,6 +142,8 @@ impl Compatibility {
142142
))
143143
}
144144

145+
/// Creates a compatibility instance denoting incompatibility
146+
/// for the provided reason
145147
pub fn incompatible(message: impl ToString) -> Self {
146148
Compatibility::Incompatible(IncompatibleReason::Other(message.to_string()))
147149
}

crates/spk-schema/crates/ident/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub use request::{
2828
is_false,
2929
InclusionPolicy,
3030
NameAndValue,
31+
PinnableValue,
3132
PkgRequest,
3233
PreReleasePolicy,
3334
Request,

0 commit comments

Comments
 (0)