Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change license metadata semantics #1073

Merged
merged 2 commits into from
Jul 18, 2024
Merged
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
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ serde_json = "1.0"
serde_yaml = "0.9.25"
serial_test = "3.1"
shellexpand = "3.1.0"
spdx = "0.10"
spfs = { path = "crates/spfs" }
spfs-cli-common = { path = "crates/spfs-cli/common" }
spfs-encoding = { path = "crates/spfs-encoding" }
Expand Down
1 change: 1 addition & 0 deletions crates/spk-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ spk-exec = { workspace = true }
spk-solve = { workspace = true }
spk-schema = { workspace = true }
spk-storage = { workspace = true }
spdx = { workspace = true }
strum = { workspace = true }
thiserror = { workspace = true }
miette = { workspace = true }
Expand Down
11 changes: 11 additions & 0 deletions crates/spk-build/src/validation/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use thiserror::Error;

pub type Result<T> = std::result::Result<T, Error>;

/// Errors returned by failed package validation rules.
#[derive(Diagnostic, Debug, Error, Clone, PartialEq, Eq)]
#[diagnostic(
url(
Expand Down Expand Up @@ -162,4 +163,14 @@ pub enum Error {
code(spk::build::validation::strong_inheritance_var_description)
)]
StrongInheritanceVarDescriptionRequired,

#[error("A valid SPDX license required, nothing specified")]
#[diagnostic(severity(warning), code(spk::build::validation::spdx_license))]
SpdxLicenseMissing,
#[error("A valid SPDX license required, got {given:?}")]
#[diagnostic(severity(warning), code(spk::build::validation::spdx_license))]
SpdxLicenseInvalid { given: String },
#[error("Package should not have a license specified")]
#[diagnostic(severity(warning), code(spk::build::validation::spdx_license))]
SpdxLicenseDenied,
}
2 changes: 2 additions & 0 deletions crates/spk-build/src/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod error;
mod inherit_requirements;
mod long_var_description;
mod recursive_build;
mod spdx_license;
mod strong_inheritance_var_desc;
mod validator;

Expand All @@ -21,5 +22,6 @@ pub use error::{Error, Result};
pub use inherit_requirements::InheritRequirementsValidator;
pub use long_var_description::LongVarDescriptionValidator;
pub use recursive_build::RecursiveBuildValidator;
pub use spdx_license::SpdxLicenseValidator;
pub use strong_inheritance_var_desc::StrongInheritanceVarDescriptionValidator;
pub use validator::{Outcome, Report, Status, Subject, Validator};
75 changes: 75 additions & 0 deletions crates/spk-build/src/validation/spdx_license.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Sony Pictures Imageworks, et al.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/imageworks/spk

use spk_schema::validation::{
ValidationMatcherDiscriminants,
ValidationRuleDiscriminants as RuleKind,
};
use spk_schema::{Package, Variant};

use super::{Error, Outcome, Report, Status, Subject};
use crate::report::{BuildReport, BuildSetupReport};

#[cfg(test)]
#[path = "./spdx_license_test.rs"]
mod spdx_license_test;

pub struct SpdxLicenseValidator {
pub kind: RuleKind,
}

impl super::validator::sealed::Sealed for SpdxLicenseValidator {}

#[async_trait::async_trait]
impl super::Validator for SpdxLicenseValidator {
async fn validate_setup<P, V>(&self, setup: &BuildSetupReport<P, V>) -> Report
where
P: Package,
V: Variant + Send + Sync,
{
let meta = setup.package.metadata();
let exists = meta.license.is_some();
let is_valid = match meta.license.as_ref() {
Some(value) => spdx::license_id(value).is_some(),
None => true,
};
let status = match self.kind {
RuleKind::Require if !exists => Status::Required(Error::SpdxLicenseMissing),
RuleKind::Allow | RuleKind::Require if !is_valid => {
Status::Required(Error::SpdxLicenseInvalid {
given: meta.license.clone().unwrap_or_default(),
})
}
RuleKind::Deny if exists && is_valid => Status::Denied(Error::SpdxLicenseDenied),
_ => Status::Allowed,
};
Outcome {
locality: String::new(),
subject: Subject::Everything,
status,
condition: ValidationMatcherDiscriminants::SpdxLicense,
}
.into()
}

async fn validate_build<P, V>(&self, report: &BuildReport<P, V>) -> Report
where
P: Package,
V: Variant + Send + Sync,
{
let is_empty = report.output.collected_changes.is_empty();
let status = match self.kind {
RuleKind::Deny if is_empty => Status::Denied(Error::EmptyPackageDenied),
RuleKind::Require if !is_empty => Status::Required(Error::EmptyPackageRequired),
_ => Status::Allowed,
};
Outcome {
locality: String::new(),
subject: Subject::Everything,
status,
condition: ValidationMatcherDiscriminants::EmptyPackage,
}
.into()
}
}
192 changes: 192 additions & 0 deletions crates/spk-build/src/validation/spdx_license_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright (c) Sony Pictures Imageworks, et al.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/imageworks/spk

use std::sync::Arc;

use spfs::tracking::Manifest;
use spk_schema::foundation::option_map;
use spk_schema::validation::ValidationMatcher;
use spk_schema::{spec, Package, ValidationRule};
use spk_solve::Solution;

use crate::report::BuildSetupReport;
use crate::validation::Validator;

macro_rules! basic_setup {
($pkg:tt) => {{
let package = Arc::new(spec!($pkg));

let environment = Solution::default();
BuildSetupReport {
environment,
variant: option_map! {},
environment_filesystem: Manifest::new(
spfs::tracking::Entry::empty_dir_with_open_perms_with_data(package.ident().clone()),
),
package,
}
}};
}

#[tokio::test]
async fn test_license_allowed_empty() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Allow {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect("Should allow no license with default allow rule");
}

#[tokio::test]
async fn test_license_allowed_valid() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {
"license": "Apache-2.0" // from spdx license list
},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Allow {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect("Should allow a known license with default allow rule");
}

#[tokio::test]
async fn test_license_allowed_invalid() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {
"license": "unknown" // NOT from spdx license list
},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Allow {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect_err("Should fail with default allow rule and invalid license");
}

#[tokio::test]
async fn test_license_require_empty() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Require {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect_err("Should fail when no license and require rule");
}

#[tokio::test]
async fn test_license_deny_empty() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Deny {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect("Should allow empty license with deny rule");
}

#[tokio::test]
async fn test_license_deny_invalid() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {
"license": "unknown" // NOT from license list
},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Deny {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect("Should allow invalid license with deny rule");
}

#[tokio::test]
async fn test_license_deny_valid() {
let setup = basic_setup!(
{
"pkg": "base/1.0.0/3TCOOP2W",
"meta": {
"license": "Apache-2.0"
},
"sources": [],
"build": {
"script": "echo building...",
},
}
);

ValidationRule::Deny {
condition: ValidationMatcher::SpdxLicense,
}
.validate_setup(&setup)
.await
.into_result()
.expect_err("Should fail with valid license and deny rule");
}
4 changes: 4 additions & 0 deletions crates/spk-build/src/validation/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ macro_rules! rule_to_validator {
let kind = spk_schema::validation::ValidationRuleDiscriminants::from($rule);
#[allow(unused_braces)]
match $rule.condition() {
ValidationMatcher::SpdxLicense => {
let $bind = super::SpdxLicenseValidator { kind };
$op
}
ValidationMatcher::EmptyPackage => {
let $bind = super::EmptyPackageValidator { kind };
$op
Expand Down
Loading
Loading