Skip to content

Commit

Permalink
Fix transitive subpackage dependency resolution (#5603)
Browse files Browse the repository at this point in the history
* Fix transitive subpackage dependency resolution

* Fix dependency chain

* Add render test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* add news

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
isuruf and pre-commit-ci[bot] authored Jan 28, 2025
1 parent cf35214 commit 49872a5
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 6 deletions.
2 changes: 1 addition & 1 deletion conda_build/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2433,7 +2433,7 @@ def build(
exclude_pattern = re.compile(
r"|".join(rf"(?:^{exc}(?:\s|$|\Z))" for exc in excludes)
)
add_upstream_pins(m, False, exclude_pattern)
add_upstream_pins(m, False, exclude_pattern, [])

create_build_envs(top_level_pkg, notest)

Expand Down
53 changes: 48 additions & 5 deletions conda_build/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def get_env_dependencies(
exclude_pattern=None,
permit_unsatisfiable_variants=False,
merge_build_host_on_same_platform=True,
extra_specs=None,
):
specs = m.get_depends_top_and_out(env)
# replace x.x with our variant's numpy version, or else conda tries to literally go get x.x
Expand All @@ -148,6 +149,8 @@ def get_env_dependencies(
)

dependencies = set(dependencies)
if extra_specs:
dependencies |= set(extra_specs)
unsat = None
random_string = "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(10)
Expand Down Expand Up @@ -183,7 +186,7 @@ def get_env_dependencies(
specs = [package_record_to_requirement(prec) for prec in precs]
return (
utils.ensure_list(
(specs + subpackages + pass_through_deps)
(specs + subpackages + pass_through_deps + (extra_specs or []))
or m.get_value(f"requirements/{env}", [])
),
precs,
Expand Down Expand Up @@ -439,13 +442,15 @@ def _read_upstream_pin_files(
env,
permit_unsatisfiable_variants,
exclude_pattern,
extra_specs,
):
deps, precs, unsat = get_env_dependencies(
m,
env,
m.config.variant,
exclude_pattern,
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
extra_specs=extra_specs,
)
# extend host deps with strong build run exports. This is important for things like
# vc feature activation to work correctly in the host env.
Expand All @@ -457,12 +462,18 @@ def _read_upstream_pin_files(
)


def add_upstream_pins(m: MetaData, permit_unsatisfiable_variants, exclude_pattern):
def add_upstream_pins(
m: MetaData, permit_unsatisfiable_variants, exclude_pattern, extra_specs
):
"""Applies run_exports from any build deps to host and run sections"""
# if we have host deps, they're more important than the build deps.
requirements = m.get_section("requirements")
build_deps, build_unsat, extra_run_specs_from_build = _read_upstream_pin_files(
m, "build", permit_unsatisfiable_variants, exclude_pattern
m,
"build",
permit_unsatisfiable_variants,
exclude_pattern,
[] if m.is_cross else extra_specs,
)

# is there a 'host' section?
Expand All @@ -488,7 +499,7 @@ def add_upstream_pins(m: MetaData, permit_unsatisfiable_variants, exclude_patter
host_reqs.extend(extra_run_specs_from_build.get("strong", []))

host_deps, host_unsat, extra_run_specs_from_host = _read_upstream_pin_files(
m, "host", permit_unsatisfiable_variants, exclude_pattern
m, "host", permit_unsatisfiable_variants, exclude_pattern, extra_specs
)
if m.noarch or m.noarch_python:
extra_run_specs = set(extra_run_specs_from_host.get("noarch", []))
Expand Down Expand Up @@ -645,9 +656,40 @@ def finalize_metadata(
utils.insert_variant_versions(requirements, m.config.variant, "build")
utils.insert_variant_versions(requirements, m.config.variant, "host")

host_requirements = requirements.get("host" if m.is_cross else "build", [])
host_requirement_names = [req.split(" ")[0] for req in host_requirements]
extra_specs = []
if output and output_excludes and not is_top_level and host_requirement_names:
reqs = {}

# we first make a mapping of output -> requirements
for (name, _), (_, other_meta) in m.other_outputs.items():
if name == m.name():
continue
other_meta_reqs = other_meta.meta.get("requirements", {}).get("run", [])
reqs[name] = set(other_meta_reqs)

seen = set()
# for each subpackage that is a dependency we add its dependencies
# and transitive dependencies if the dependency of the subpackage
# is a subpackage.
to_process = set(
name for (name, _) in m.other_outputs if name in host_requirement_names
)
while to_process:
name = to_process.pop()
if name == m.name():
continue
for req in reqs[name]:
req_name = req.split(" ")[0]
if req_name not in reqs:
extra_specs.append(req)
elif req_name not in seen:
to_process.add(req_name)

m = parent_metadata.get_output_metadata(m.get_rendered_output(m.name()))
build_unsat, host_unsat = add_upstream_pins(
m, permit_unsatisfiable_variants, exclude_pattern
m, permit_unsatisfiable_variants, exclude_pattern, extra_specs
)
# getting this AFTER add_upstream_pins is important, because that function adds deps
# to the metadata.
Expand Down Expand Up @@ -675,6 +717,7 @@ def finalize_metadata(
m.config.variant,
exclude_pattern=exclude_pattern,
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
extra_specs=extra_specs,
)
full_build_dep_versions = {
dep.split()[0]: " ".join(dep.split()[1:]) for dep in full_build_deps
Expand Down
19 changes: 19 additions & 0 deletions news/5603-transitive-subpackage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* <news item>

### Bug fixes

* Fixes transitive subpackage dependency resolution issue #3308. (#5603)

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
openssl:
- 1.0.2
python:
- 3.6
15 changes: 15 additions & 0 deletions tests/test-recipes/metadata/transitive_subpackage/meta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package:
name: foo_split
version: 1.0.0

outputs:
- name: libfoo
requirements:
host:
- openssl

- name: foo
requirements:
host:
- python
- {{ pin_subpackage('libfoo', exact=True) }}
10 changes: 10 additions & 0 deletions tests/test_api_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ def test_pin_compatible_semver(testing_config):
assert "zlib >=1.2.11,<2.0a0" in metadata.get_value("requirements/run")


def test_transitive_subpackage_dependency(testing_config):
recipe_dir = os.path.join(metadata_dir, "transitive_subpackage")
metadata = api.render(recipe_dir, config=testing_config)[1][0]
assert not metadata.get_value("requirements/run")
assert any(
req.startswith("openssl 1.0.2")
for req in metadata.get_value("requirements/host")
)


@pytest.mark.slow
@pytest.mark.xfail(on_win, reason="Defaults channel has conflicting vc packages")
def test_resolved_packages_recipe(testing_config):
Expand Down

0 comments on commit 49872a5

Please sign in to comment.