Skip to content

Commit

Permalink
Merge pull request #1034 from spkenv/distro-compat-range
Browse files Browse the repository at this point in the history
Distro compat range
  • Loading branch information
jrray authored Aug 8, 2024
2 parents e2e33c0 + 0699fb0 commit 5ed89ee
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 21 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

16 changes: 16 additions & 0 deletions crates/spk-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk

use std::collections::HashMap;
use std::sync::{Arc, RwLock};

use config::Environment;
Expand Down Expand Up @@ -118,6 +119,20 @@ pub struct Cli {
pub ls: Ls,
}

#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct DistroRule {
/// The compat rule to set for the distro, e.g., "x.ab"
pub compat_rule: Option<String>,
}

#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct HostOptions {
/// A mapping of distro names to recognize and customizations for each.
pub distro_rules: HashMap<String, DistroRule>,
}

/// Configuration values for spk.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(default)]
Expand All @@ -130,6 +145,7 @@ pub struct Config {
pub statsd: Statsd,
pub metadata: Metadata,
pub cli: Cli,
pub host_options: HostOptions,
}

impl Config {
Expand Down
1 change: 1 addition & 0 deletions crates/spk-schema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ workspace = true
migration-to-components = ["spk-schema-foundation/migration-to-components"]

[dependencies]
config = { workspace = true }
data-encoding = "2.3"
dunce = { workspace = true }
enum_dispatch = "0.3.8"
Expand Down
43 changes: 31 additions & 12 deletions crates/spk-schema/src/build_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::str::FromStr;

use itertools::Itertools;
use serde::{Deserialize, Serialize};
use spk_config::get_config;
use spk_schema_foundation::ident_build::BuildId;
use spk_schema_foundation::name::PkgName;
use spk_schema_foundation::option_map::{OptionMap, Stringified, HOST_OPTIONS};
use spk_schema_foundation::version::Compat;
use strum::Display;

use super::{v0, Opt, ValidationSpec};
Expand Down Expand Up @@ -68,22 +71,46 @@ impl AutoHostVars {
pub fn host_options(&self) -> Result<Vec<Opt>> {
let all_host_options = HOST_OPTIONS.get()?;

let mut names_added = self.names_added();
let mut settings = Vec::new();
let names_to_add = self.names_added();
for (name, _value) in all_host_options.iter() {
if !names_to_add.contains(&OptName::new(name)?) {
continue;
}
let opt = Opt::Var(VarOpt::new(name)?);
settings.push(opt)
}

let distro_name;
let fallback_name: OptNameBuf;
if AutoHostVars::Distro == *self {
match all_host_options.get(OptName::distro()) {
Some(distro) => {
distro_name = distro.clone();
match OptName::new(&distro_name) {
Ok(name) => _ = names_added.insert(name),
Ok(name) => {
let mut var_opt = VarOpt::new(name.to_owned())?;
// Look for any configured compat rules for the
// distro
let config = get_config()?;
if let Some(rule) = config.host_options.distro_rules.get(&distro_name) {
if let Some(compat_rule) = &rule.compat_rule {
var_opt.compat = Some(
Compat::from_str(compat_rule).map_err(|err| {
Error::SpkConfigError(spk_config::Error::Config(config::ConfigError::Message(format!("Invalid compat rule found in config for distro {distro_name}: {err}"))))
})?,
);
}
}
settings.push(Opt::Var(var_opt));
}
Err(err) => {
fallback_name = OptNameBuf::new_lossy(&distro_name);
tracing::warn!("Reported distro id ({}) is not a valid var option name: {err}. A {} var will be used instead.",
distro_name.to_string(),
fallback_name);

_ = names_added.insert(&fallback_name);
settings.push(Opt::Var(VarOpt::new(&fallback_name)?));
}
}
}
Expand All @@ -92,19 +119,11 @@ impl AutoHostVars {
"No distro name set by host. A {}= will be used instead.",
OptName::unknown_distro()
);
_ = names_added.insert(OptName::unknown_distro());
settings.push(Opt::Var(VarOpt::new(OptName::unknown_distro().to_owned())?));
}
}
}

let mut settings = Vec::new();
for (name, _value) in all_host_options.iter() {
if names_added.contains(&OptName::new(name)?) {
let opt = Opt::Var(VarOpt::new(name)?);
settings.push(opt)
}
}

Ok(settings)
}
}
Expand Down
12 changes: 12 additions & 0 deletions crates/spk-schema/src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use indexmap::set::IndexSet;
use serde::{Deserialize, Serialize};
use spk_schema_foundation::ident_component::ComponentBTreeSetBuf;
use spk_schema_foundation::option_map::Stringified;
use spk_schema_foundation::version::Compat;
use spk_schema_ident::{NameAndValue, PinnableValue, RangeIdent};

use crate::foundation::name::{OptName, OptNameBuf, PkgName, PkgNameBuf};
Expand Down Expand Up @@ -181,6 +182,7 @@ impl TryFrom<Request> for Opt {
choices: Default::default(),
inheritance: Default::default(),
description,
compat: None,
value: None,
})),
}
Expand Down Expand Up @@ -214,6 +216,7 @@ impl<'de> Deserialize<'de> for Opt {
var: Option<OptNameBuf>,
choices: Option<IndexSet<String>>,
inheritance: Option<Inheritance>,
compat: Option<Compat>,

// Both
default: Option<String>,
Expand Down Expand Up @@ -280,6 +283,9 @@ impl<'de> Deserialize<'de> for Opt {
"description" => {
self.description = Some(map.next_value::<Stringified>()?.0);
}
"compat" => {
self.compat = Some(map.next_value::<Compat>()?);
}
_ => {
// unrecognized fields are explicitly ignored in case
// they were added in a newer version of spk. We assume
Expand All @@ -305,6 +311,7 @@ impl<'de> Deserialize<'de> for Opt {
inheritance: self.inheritance.unwrap_or_default(),
default: self.default.unwrap_or_default(),
description: self.description,
compat: self.compat,
value: self.value,
})),
(Some(_), Some(_)) => Err(serde::de::Error::custom(
Expand All @@ -328,6 +335,7 @@ pub struct VarOpt {
pub choices: IndexSet<String>,
pub inheritance: Inheritance,
pub description: Option<String>,
pub compat: Option<Compat>,
value: Option<String>,
}

Expand Down Expand Up @@ -385,6 +393,7 @@ impl VarOpt {
choices: IndexSet::default(),
inheritance: Inheritance::default(),
description: None,
compat: None,
value: None,
})
}
Expand Down Expand Up @@ -464,6 +473,8 @@ struct VarOptSchema {
inheritance: Inheritance,
#[serde(skip_serializing_if = "String::is_empty")]
description: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
compat: Option<Compat>,
#[serde(rename = "static", skip_serializing_if = "String::is_empty")]
value: String,
}
Expand All @@ -478,6 +489,7 @@ impl Serialize for VarOpt {
choices: self.choices.iter().map(String::to_owned).collect(),
inheritance: self.inheritance,
description: self.description.clone().unwrap_or_default(),
compat: self.compat.clone(),
value: self.value.clone().unwrap_or_default(),
};
if !self.default.is_empty() {
Expand Down
58 changes: 49 additions & 9 deletions crates/spk-schema/src/v0/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ use std::borrow::Cow;
use std::collections::{BTreeSet, HashMap};
use std::convert::TryInto;
use std::path::Path;
use std::str::FromStr;

use itertools::Itertools;
use serde::{Deserialize, Serialize};
use spk_schema_foundation::ident_build::BuildId;
use spk_schema_foundation::ident_component::ComponentBTreeSet;
use spk_schema_foundation::name::PkgNameBuf;
use spk_schema_foundation::option_map::{OptFilter, Stringified};
use spk_schema_foundation::version::IncompatibleReason;
use spk_schema_ident::{AnyIdent, BuildIdent, Ident, RangeIdent, VersionIdent};

use super::variant_spec::VariantSpecEntryKey;
Expand Down Expand Up @@ -801,16 +803,54 @@ where
Some(Opt::Var(opt)) => {
let request_value = var_request.value.as_pinned();
let exact = opt.get_value(request_value);
if exact.as_deref() != request_value {
Compatibility::incompatible(format!(
"Incompatible build option '{}': '{}' != '{}'",
var_request.var,
exact.unwrap_or_else(|| "None".to_string()),
request_value.unwrap_or_default()
))
} else {
Compatibility::Compatible
if exact.as_deref() == request_value {
return Compatibility::Compatible;
}

// For values that aren't exact matches, if the option specifies
// a compat rule, try treating the values as version numbers
// and see if they satisfy the rule.
if let Some(compat) = &opt.compat {
let base_version = exact.clone();
let Ok(base_version) = Version::from_str(&base_version.unwrap_or_default())
else {
return Compatibility::incompatible(format!(
"Incompatible build option '{}': '{base}' != '{}' and '{base}' is not a valid version number",
var_request.var,
request_value.unwrap_or_default(),
base = exact.unwrap_or_default()
));
};

let Ok(request_version) = Version::from_str(request_value.unwrap_or_default())
else {
return Compatibility::incompatible(format!(
"Incompatible build option '{}': '{base}' != '{request}' and '{request}' is not a valid version number",
var_request.var,
request = request_value.unwrap_or_default(),
base = exact.unwrap_or_default()
));
};

let mut result = compat.is_binary_compatible(&base_version, &request_version);
if let Compatibility::Incompatible(IncompatibleReason::Other(msg)) = &mut result
{
*msg = format!(
"Incompatible build option '{}': '{}' != '{}' and {msg}",
var_request.var,
exact.unwrap_or_else(|| "None".to_string()),
request_value.unwrap_or_default()
);
}
return result;
}

Compatibility::incompatible(format!(
"Incompatible build option '{}': '{}' != '{}'",
var_request.var,
exact.unwrap_or_else(|| "None".to_string()),
request_value.unwrap_or_default()
))
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions docs/admin/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,10 @@ environment = ""
[cli.ls]
# Use all current host's host options by default for filtering in ls
host_filtering = false

# SPK supports some customization of the distro host options.
[host_options.distro_rules.rocky]
# Set a default compat rule for this distro. For example, on Rocky Linux
# packages built on 9.3 would be usable on 9.4.
compat_rule = "x.ab"
```

0 comments on commit 5ed89ee

Please sign in to comment.