Skip to content

Commit 4e919d0

Browse files
author
Nichol Yip
committed
Added lints for InstallSpec
Signed-off-by: Nichol Yip <[email protected]>
1 parent 7aeda44 commit 4e919d0

File tree

4 files changed

+141
-36
lines changed

4 files changed

+141
-36
lines changed

crates/spk-cli/group4/src/cmd_lint.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ impl Run for Lint {
4141
Ok(s) => match s.lints.is_empty() {
4242
true => println!("{} {}", "OK".green(), spec.display()),
4343
false => {
44+
println!("{} {}:", "Failed".red(), spec.display());
4445
for lint in s.lints {
45-
tracing::error!(lint);
46+
println!("{} {lint}", "----->".red());
4647
}
4748
out = 1;
4849
}
Lines changed: 128 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// Copyright (c) Sony Pictures Imageworks, et al.
22
// SPDX-License-Identifier: Apache-2.0
33
// https://github.com/imageworks/spk
4+
use std::marker::PhantomData;
5+
6+
use ngrammatic::CorpusBuilder;
47
use serde::{Deserialize, Serialize};
8+
use spk_schema_foundation::option_map::Stringified;
59
use spk_schema_ident::BuildIdent;
610

711
use super::{ComponentSpecList, EmbeddedPackagesList, EnvOp, OpKind, RequirementsList};
@@ -13,21 +17,66 @@ use crate::Result;
1317
mod install_spec_test;
1418

1519
/// A set of structured installation parameters for a package.
16-
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
20+
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1721
pub struct InstallSpec {
1822
#[serde(default, skip_serializing_if = "Vec::is_empty")]
1923
pub requirements: RequirementsList,
2024
#[serde(default, skip_serializing_if = "Vec::is_empty")]
2125
pub embedded: EmbeddedPackagesList,
2226
#[serde(default)]
2327
pub components: ComponentSpecList,
24-
#[serde(
25-
default,
26-
deserialize_with = "deserialize_env_conf",
27-
skip_serializing_if = "Vec::is_empty"
28-
)]
28+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
2929
pub environment: Vec<EnvOp>,
3030
}
31+
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
32+
pub struct LintedInstallSpec {
33+
pub install_spec: InstallSpec,
34+
pub lints: Vec<String>,
35+
}
36+
37+
#[derive(Default)]
38+
struct InstallSpecVisitor<D>
39+
where
40+
D: Default,
41+
{
42+
requirements: RequirementsList,
43+
embedded: EmbeddedPackagesList,
44+
components: ComponentSpecList,
45+
environment: Vec<EnvOp>,
46+
lints: Vec<String>,
47+
_phantom: PhantomData<D>,
48+
}
49+
50+
impl<D> From<InstallSpecVisitor<D>> for InstallSpec
51+
where
52+
D: Default,
53+
{
54+
fn from(value: InstallSpecVisitor<D>) -> Self {
55+
Self {
56+
requirements: value.requirements,
57+
embedded: value.embedded,
58+
components: value.components,
59+
environment: value.environment,
60+
}
61+
}
62+
}
63+
64+
impl<D> From<InstallSpecVisitor<D>> for LintedInstallSpec
65+
where
66+
D: Default,
67+
{
68+
fn from(value: InstallSpecVisitor<D>) -> Self {
69+
Self {
70+
install_spec: InstallSpec {
71+
requirements: value.requirements,
72+
embedded: value.embedded,
73+
components: value.components,
74+
environment: value.environment,
75+
},
76+
lints: value.lints,
77+
}
78+
}
79+
}
3180

3281
impl InstallSpec {
3382
pub fn is_default(&self) -> bool {
@@ -44,37 +93,86 @@ impl InstallSpec {
4493
}
4594
}
4695

47-
fn deserialize_env_conf<'de, D>(deserializer: D) -> std::result::Result<Vec<EnvOp>, D::Error>
96+
impl<'de> Deserialize<'de> for InstallSpec {
97+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
98+
where
99+
D: serde::de::Deserializer<'de>,
100+
{
101+
Ok(std::convert::Into::<InstallSpec>::into(
102+
deserializer.deserialize_map(InstallSpecVisitor::<InstallSpec>::default())?,
103+
))
104+
}
105+
}
106+
107+
impl<'de> Deserialize<'de> for LintedInstallSpec {
108+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
109+
where
110+
D: serde::de::Deserializer<'de>,
111+
{
112+
Ok(std::convert::Into::<LintedInstallSpec>::into(
113+
deserializer.deserialize_map(InstallSpecVisitor::<LintedInstallSpec>::default())?,
114+
))
115+
}
116+
}
117+
118+
impl<'de, D> serde::de::Visitor<'de> for InstallSpecVisitor<D>
48119
where
49-
D: serde::Deserializer<'de>,
120+
D: Default + From<InstallSpecVisitor<D>>,
50121
{
51-
struct EnvConfVisitor;
122+
type Value = D;
52123

53-
impl<'de> serde::de::Visitor<'de> for EnvConfVisitor {
54-
type Value = Vec<EnvOp>;
124+
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
125+
f.write_str("a package specification")
126+
}
127+
128+
fn visit_map<A>(mut self, mut map: A) -> std::result::Result<Self::Value, A::Error>
129+
where
130+
A: serde::de::MapAccess<'de>,
131+
{
132+
while let Some(key) = map.next_key::<Stringified>()? {
133+
match key.as_str() {
134+
"requirements" => self.requirements = map.next_value::<RequirementsList>()?,
135+
"embedded" => self.embedded = map.next_value::<EmbeddedPackagesList>()?,
136+
"components" => self.components = map.next_value::<ComponentSpecList>()?,
137+
"environment" => self.environment = map.next_value::<Vec<EnvOp>>()?,
138+
unknown_config => {
139+
self.lints.push(format!("Unknown config: {unknown_config}"));
140+
let mut corpus = CorpusBuilder::new().finish();
141+
142+
corpus.add_text("requirements");
143+
corpus.add_text("embedded");
144+
corpus.add_text("components");
145+
corpus.add_text("environment");
146+
147+
let results = corpus.search(unknown_config, 0.6);
148+
let no_match = format!("No similar config found for: {}", unknown_config);
149+
let top_match = match results.first() {
150+
Some(s) => &s.text,
151+
None => &no_match,
152+
};
55153

56-
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
57-
f.write_str("an environment configuration")
154+
self.lints
155+
.push(format!("The most similar config is: {}", top_match));
156+
157+
// ignore any unrecognized field, but consume the value anyway
158+
// TODO: could we warn about fields that look like typos?
159+
map.next_value::<serde::de::IgnoredAny>()?;
160+
}
161+
}
58162
}
59163

60-
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
61-
where
62-
A: serde::de::SeqAccess<'de>,
164+
if self
165+
.environment
166+
.iter()
167+
.filter(|&e| e.kind() == OpKind::Priority)
168+
.count()
169+
> 1
63170
{
64-
let mut vec = Vec::new();
65-
66-
while let Some(elem) = seq.next_element::<EnvOp>()? {
67-
if vec.iter().any(|x: &EnvOp| x.kind() == OpKind::Priority)
68-
&& elem.kind() == OpKind::Priority
69-
{
70-
return Err(serde::de::Error::custom(
71-
"Multiple priority config cannot be set.",
72-
));
73-
};
74-
vec.push(elem);
75-
}
76-
Ok(vec)
171+
return Err(serde::de::Error::custom(
172+
"Multiple priority configs cannot be set",
173+
));
77174
}
175+
176+
Ok(self.into())
78177
}
79-
deserializer.deserialize_seq(EnvConfVisitor)
80178
}

crates/spk-schema/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub use deprecate::{Deprecate, DeprecateMut};
3434
pub use embedded_packages_list::EmbeddedPackagesList;
3535
pub use environ::{AppendEnv, EnvOp, OpKind, PrependEnv, SetEnv};
3636
pub use error::{Error, Result};
37-
pub use install_spec::InstallSpec;
37+
pub use install_spec::{InstallSpec, LintedInstallSpec};
3838
pub use option::{Inheritance, Opt};
3939
pub use package::{Package, PackageMut};
4040
pub use recipe::{BuildEnv, Recipe};

crates/spk-schema/src/v0/spec.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use crate::{
4646
Error,
4747
Inheritance,
4848
InstallSpec,
49+
LintedInstallSpec,
4950
LocalSource,
5051
Opt,
5152
Package,
@@ -840,7 +841,7 @@ struct SpecVisitor<B, T> {
840841
sources: Option<Vec<SourceSpec>>,
841842
build: Option<UncheckedBuildSpec>,
842843
tests: Option<Vec<TestSpec>>,
843-
install: Option<InstallSpec>,
844+
install: Option<LintedInstallSpec>,
844845
check_build_spec: bool,
845846
lints: Vec<String>,
846847
}
@@ -867,6 +868,11 @@ where
867868
Ident<B, T>: serde::de::DeserializeOwned,
868869
{
869870
fn from(mut value: SpecVisitor<B, T>) -> Self {
871+
match &value.install {
872+
Some(l) => value.lints.append(&mut l.lints.clone()),
873+
None => (),
874+
}
875+
870876
Self {
871877
spec: Spec {
872878
pkg: value.pkg.expect("Missing field: pkg"),
@@ -889,7 +895,7 @@ where
889895
None => Default::default(),
890896
},
891897
tests: value.tests.take().unwrap_or_default(),
892-
install: value.install.take().unwrap_or_default(),
898+
install: value.install.take().unwrap_or_default().install_spec,
893899
},
894900
lints: value.lints,
895901
}
@@ -922,7 +928,7 @@ where
922928
None => Default::default(),
923929
},
924930
tests: value.tests.take().unwrap_or_default(),
925-
install: value.install.take().unwrap_or_default(),
931+
install: value.install.take().unwrap_or_default().install_spec,
926932
}
927933
}
928934
}
@@ -970,7 +976,7 @@ where
970976
"sources" => self.sources = Some(map.next_value::<Vec<SourceSpec>>()?),
971977
"build" => self.build = Some(map.next_value::<UncheckedBuildSpec>()?),
972978
"tests" => self.tests = Some(map.next_value::<Vec<TestSpec>>()?),
973-
"install" => self.install = Some(map.next_value::<InstallSpec>()?),
979+
"install" => self.install = Some(map.next_value::<LintedInstallSpec>()?),
974980
"api" => {
975981
map.next_value::<serde::de::IgnoredAny>()?;
976982
}

0 commit comments

Comments
 (0)