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

fix: issues with new variant resolving and resolution #1250

Merged
merged 2 commits into from
Dec 10, 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
14 changes: 8 additions & 6 deletions src/variant_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
recipe::{
custom_yaml::{HasSpan, Node, RenderedMappingNode, RenderedNode, TryConvertNode},
error::{ErrorKind, ParsingError, PartialParsingError},
parser::BuildString,
Jinja, Render,
},
selectors::SelectorConfig,
Expand Down Expand Up @@ -441,24 +440,27 @@ impl VariantConfig {
// Now we need to convert the stage 1 renders to DiscoveredOutputs
let mut recipes = IndexSet::new();
for sx in stage_1 {
for (idx, ((node, recipe), variant)) in sx.outputs().enumerate() {
for ((node, recipe), variant) in sx.into_sorted_outputs()? {
let target_platform = if recipe.build().noarch().is_none() {
selector_config.target_platform
} else {
Platform::NoArch
};

let mut recipe = recipe.clone();
let build_string = sx.build_string_for_output(idx);
recipe.build.string = BuildString::Resolved(build_string.clone());
let build_string = recipe
.build()
.string()
.as_resolved()
.expect("Build string has to be resolved")
.to_string();

recipes.insert(DiscoveredOutput {
name: recipe.package().name.as_normalized().to_string(),
version: recipe.package().version.to_string(),
build_string,
noarch_type: *recipe.build().noarch(),
target_platform,
node: node.clone(),
node,
used_vars: variant.clone(),
recipe: recipe.clone(),
hash: HashInfo::from_variant(&variant, recipe.build().noarch()),
Expand Down
115 changes: 70 additions & 45 deletions src/variant_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
env_vars,
hash::HashInfo,
normalized_key::NormalizedKey,
recipe::{custom_yaml::Node, parser::Dependency, Jinja, ParsingError, Recipe},
recipe::{
custom_yaml::Node,
parser::{BuildString, Dependency},
Jinja, ParsingError, Recipe,
},
selectors::SelectorConfig,
used_variables::used_vars_from_expressions,
variant_config::{ParseErrors, VariantConfig, VariantError},
Expand Down Expand Up @@ -55,17 +59,15 @@
used_vars_from_expressions(output, recipe)
.map(|x| x.into_iter().map(Into::into).collect())
})
.collect::<Result<Vec<HashSet<NormalizedKey>>, Vec<ParsingError>>>();

// If there are any parsing errors, return them
if let Err(errors) = used_vars {
let err: ParseErrors = errors.into();
return Err(VariantError::RecipeParseErrors(err));
}
.collect::<Result<Vec<HashSet<NormalizedKey>>, Vec<ParsingError>>>()
.map_err(|errs| {
let errs: ParseErrors = errs.into();
VariantError::RecipeParseErrors(errs)
})?;

let raw_output_vec = RawOutputVec {
vec: outputs.to_vec(),
used_vars_jinja: used_vars.unwrap(),
used_vars_jinja: used_vars,
recipe: recipe.to_string(),
};

Expand All @@ -74,7 +76,10 @@
for output in outputs {
used_vars.extend(
used_vars_from_expressions(output, recipe)
.unwrap()
.map_err(|errs| {
let errs: ParseErrors = errs.into();
VariantError::RecipeParseErrors(errs)
})?
.into_iter()
.map(Into::into),
);
Expand Down Expand Up @@ -129,8 +134,6 @@
pub(crate) inner: Vec<Stage1Inner>,

pub(crate) stage_0_render: Stage0Render,

order: Vec<usize>,
}

impl Stage1Render {
Expand All @@ -140,8 +143,10 @@
.position(|x| x.recipe.package().name() == package_name)
}

pub fn variant_for_output(&self, idx: usize) -> BTreeMap<NormalizedKey, String> {
let idx = self.order[idx];
pub fn variant_for_output(
&self,
idx: usize,
) -> Result<BTreeMap<NormalizedKey, String>, VariantError> {
let inner = &self.inner[idx];
// combine jinja variables and the variables from the dependencies
let self_name = self.stage_0_render.rendered_outputs[idx].package().name();
Expand Down Expand Up @@ -175,9 +180,11 @@
if pin == self_name {
continue;
}
let other_idx = self.index_from_name(pin).unwrap();
let Some(other_idx) = self.index_from_name(pin) else {
return Err(VariantError::MissingOutput(pin.as_source().to_string()));
};
// find the referenced output
let build_string = self.build_string_for_output(other_idx);
let build_string = self.build_string_for_output(other_idx)?;
let version = self.inner[other_idx].recipe.package().version();
variant.insert(
pin.as_normalized().into(),
Expand All @@ -190,28 +197,28 @@
variant.insert("target_platform".into(), "noarch".into());
}

variant
Ok(variant)
}

pub fn build_string_for_output(&self, idx: usize) -> String {
let variant = self.variant_for_output(idx);
let recipe = &self.stage_0_render.rendered_outputs[self.order[idx]];
pub fn build_string_for_output(&self, idx: usize) -> Result<String, VariantError> {
let variant = self.variant_for_output(idx)?;
let recipe = &self.stage_0_render.rendered_outputs[idx];
let hash = HashInfo::from_variant(&variant, recipe.build().noarch());
let inner = &self.inner[self.order[idx]];
let inner = &self.inner[idx];

let mut selector_config = inner.selector_config.clone();
selector_config.hash = Some(hash.clone());
let jinja = Jinja::new(selector_config.clone()).with_context(&recipe.context);

recipe
Ok(recipe
.build()
.string()
.resolve(&hash, recipe.build().number, &jinja)
.into_owned()
.into_owned())
}

/// sort the outputs topologically
pub fn sort_outputs(self) -> Self {
pub fn sorted_indices(&self) -> Result<Vec<usize>, VariantError> {
// Create an empty directed graph
let mut graph = DiGraph::<_, ()>::new();
let mut node_indices = Vec::new();
Expand Down Expand Up @@ -249,35 +256,47 @@
}

// Sort the outputs topologically
let sorted_indices =
petgraph::algo::toposort(&graph, None).expect("Could not sort topologically.");
let sorted_indices = match petgraph::algo::toposort(&graph, None) {
Ok(sorted_indices) => sorted_indices,
Err(cycle) => {
let cycle = cycle.node_id();
let cycle_name = graph[cycle].package().name();
return Err(VariantError::CycleInRecipeOutputs(
cycle_name.as_source().to_string(),
));
}
};

let sorted_indices = sorted_indices
.into_iter()
.map(|x| x.index())
.collect::<Vec<usize>>();

// Update the order of the outputs
Stage1Render {
order: sorted_indices,
..self
}
Ok(sorted_indices)
}

pub fn outputs(
&self,
) -> impl Iterator<Item = ((&Node, &Recipe), BTreeMap<NormalizedKey, String>)> {
pub fn into_sorted_outputs(
self,
) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> {

Check failure on line 281 in src/variant_render.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

[clippy] reported by reviewdog 🐶 error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` Raw Output: src/variant_render.rs:281:10:e:error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` __END__

Check failure on line 281 in src/variant_render.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

[clippy] reported by reviewdog 🐶 error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` Raw Output: src/variant_render.rs:281:10:e:error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` __END__

Check failure on line 281 in src/variant_render.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest)

[clippy] reported by reviewdog 🐶 error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` Raw Output: src/variant_render.rs:281:10:e:error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` __END__

Check failure on line 281 in src/variant_render.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest)

[clippy] reported by reviewdog 🐶 error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` Raw Output: src/variant_render.rs:281:10:e:error: very complex type used. Consider factoring parts into `type` definitions --> src/variant_render.rs:281:10 | 281 | ) -> Result<Vec<((Node, Recipe), BTreeMap<NormalizedKey, String>)>, VariantError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]` __END__
// zip node from stage0 and final render output
let raw_nodes = self.stage_0_render.raw_outputs.vec.iter();
let outputs: Vec<&Recipe> = self.inner.iter().map(|i| &i.recipe).collect();
let sorted_indices = self.sorted_indices()?;

let raw_nodes = self.stage_0_render.raw_outputs.vec.clone().into_iter();
let outputs = self.inner.clone().into_iter().map(|i| i.recipe);

let zipped = raw_nodes.zip(outputs).collect::<Vec<_>>();
let mut result = Vec::new();
for idx in sorted_indices {
let mut recipe = zipped[idx].clone();
let build_string = self.build_string_for_output(idx)?;
// Resolve the build string and store the resolved one in the recipe
recipe.1.build.string = BuildString::Resolved(build_string);
let variant = self.variant_for_output(idx)?;
result.push((recipe, variant));
}

(0..zipped.len()).map(move |idx| {
let recipe = zipped[self.order[idx]];
let variant = self.variant_for_output(idx);
(recipe, variant)
})
Ok(result)
}
}

Expand Down Expand Up @@ -337,7 +356,7 @@
}

// special handling of CONDA_BUILD_SYSROOT
let jinja_variables = r.raw_outputs.used_vars_jinja.get(idx).unwrap();
let jinja_variables = &r.raw_outputs.used_vars_jinja[idx];
if jinja_variables.contains(&"c_compiler".into())
|| jinja_variables.contains(&"cxx_compiler".into())
{
Expand Down Expand Up @@ -383,7 +402,15 @@
let config_with_variant = selector_config
.with_variant(combination.clone(), selector_config.target_platform);

let parsed_recipe = Recipe::from_node(output, config_with_variant.clone()).unwrap();
let parsed_recipe = Recipe::from_node(output, config_with_variant.clone())
.map_err(|err| {
let errs: ParseErrors = err
.into_iter()
.map(|err| ParsingError::from_partial(&r.raw_outputs.recipe, err))
.collect::<Vec<ParsingError>>()
.into();
errs
})?;

inner.push(Stage1Inner {
used_vars_from_dependencies: extra_vars_per_output[idx].clone(),
Expand All @@ -397,9 +424,7 @@
inner,
variables: combination,
stage_0_render: r.clone(),
order: (0..r.rendered_outputs.len()).collect(),
}
.sort_outputs();
};

stage_1_renders.push(stage_1);
}
Expand Down
14 changes: 14 additions & 0 deletions test-data/recipes/race-condition/recipe-cycle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
recipe:
version: "1.2.3"

outputs:
- package:
name: foobar
requirements:
host:
- bazbus
- package:
name: bazbus
requirements:
host:
- foobar
19 changes: 19 additions & 0 deletions test-data/recipes/race-condition/recipe-pin-invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
recipe:
name: test-split
version: v1

build:
number: 0
noarch: python

source:
path: ./

outputs:
- package:
name: test.a
- package:
name: test.b
requirements:
run:
- ${{ pin_subpackage('test1', exact=True) }} # this package does not exist!
19 changes: 19 additions & 0 deletions test-data/recipes/race-condition/recipe-pin-subpackage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
recipe:
name: test-split
version: v1

build:
number: 0
noarch: python

source:
path: ./

outputs:
- package:
name: test2
requirements:
run:
- ${{ pin_subpackage('test1', exact=True) }}
- package:
name: test1
15 changes: 15 additions & 0 deletions test-data/recipes/race-condition/recipe-python-min.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package:
name: test-python-min
version: '1.0.0'

requirements:
host:
- python ${{ python_min }}
- python >=${{ python_min }}
- python ${{ python_min }}.*
- python ${{ python_min ~ ".*,<4.0a0" }}
run:
- python ${{ python_min }}
- python >=${{ python_min }}
- python ${{ python_min }}.*
- python ${{ python_min ~ ".*,<4.0a0" }}
3 changes: 3 additions & 0 deletions test-data/recipes/race-condition/variants.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
variant:
- a
- b

python_min:
- "3.8.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"host": [
"python ==3.8.0",
"python >=3.8.0",
"python 3.8.0.*",
"python 3.8.0.*,<4.0a0"
],
"run": [
"python ==3.8.0",
"python >=3.8.0",
"python 3.8.0.*",
"python 3.8.0.*,<4.0a0"
]
}
8 changes: 5 additions & 3 deletions test/end-to-end/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ def __call__(self, *args: Any, **kwds: Any) -> Any:
try:
return check_output([str(self.path), *args], **kwds).decode("utf-8")
except CalledProcessError as e:
print(e.output)
print(e.stderr)
if kwds.get("stderr") is None:
print(e.output)
print(e.stderr)
raise e

def build_args(
Expand Down Expand Up @@ -77,6 +78,7 @@ def render(
custom_channels: Optional[list[str]] = None,
extra_args: list[str] = None,
extra_meta: dict[str, Any] = None,
**kwargs: Any,
) -> Any:
args = self.build_args(
recipe_folder,
Expand All @@ -88,7 +90,7 @@ def render(
)
if with_solve:
args += ["--with-solve"]
output = self(*args, "--render-only")
output = self(*args, "--render-only", **kwargs)

return json.loads(output)

Expand Down
Loading
Loading