Skip to content

Commit

Permalink
Report fully rendered recipe to stdout (#5344)
Browse files Browse the repository at this point in the history
* Report fully rendered recipe to stdout

* add test

* add news

* pre-commit

* Do not use f-strings here, it might be executed by older python versions

* ignore ruff

* catch exceptions here too

* format
  • Loading branch information
jaimergp authored Jun 7, 2024
1 parent 4ec4ac7 commit 4561699
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 53 deletions.
2 changes: 1 addition & 1 deletion conda_build/_load_setup_py_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def setup(**kw):
exec(code, ns, ns)
else:
if not permit_undefined_jinja:
raise TypeError(f"{setup_file} is not a file that can be read")
raise TypeError("%s is not a file that can be read" % setup_file) # noqa: UP031

sys.modules["versioneer"] = versioneer

Expand Down
57 changes: 8 additions & 49 deletions conda_build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,7 @@ def render(
templates evaluated.
Returns a list of (metadata, need_download, need_reparse in env) tuples"""

from conda.exceptions import NoPackagesFoundError

from .exceptions import DependencyNeedsBuildingError
from .render import finalize_metadata, render_recipe
from .render import render_metadata_tuples, render_recipe

config = get_or_merge_config(config, **kwargs)

Expand All @@ -68,50 +64,13 @@ def render(
variants=variants,
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
)
output_metas: dict[tuple[str, str, tuple[tuple[str, str], ...]], MetaDataTuple] = {}
for meta, download, render_in_env in metadata_tuples:
if not meta.skip() or not config.trim_skip:
for od, om in meta.get_output_metadata_set(
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
permit_undefined_jinja=not finalize,
bypass_env_check=bypass_env_check,
):
if not om.skip() or not config.trim_skip:
if "type" not in od or od["type"] == "conda":
if finalize and not om.final:
try:
om = finalize_metadata(
om,
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
)
except (DependencyNeedsBuildingError, NoPackagesFoundError):
if not permit_unsatisfiable_variants:
raise

# remove outputs section from output objects for simplicity
if not om.path and (outputs := om.get_section("outputs")):
om.parent_outputs = outputs
del om.meta["outputs"]

output_metas[
om.dist(),
om.config.variant.get("target_platform"),
tuple(
(var, om.config.variant[var])
for var in om.get_used_vars()
),
] = MetaDataTuple(om, download, render_in_env)
else:
output_metas[
f"{om.type}: {om.name()}",
om.config.variant.get("target_platform"),
tuple(
(var, om.config.variant[var])
for var in om.get_used_vars()
),
] = MetaDataTuple(om, download, render_in_env)

return list(output_metas.values())
return render_metadata_tuples(
metadata_tuples,
config=config,
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
finalize=finalize,
bypass_env_check=bypass_env_check,
)


def output_yaml(
Expand Down
19 changes: 19 additions & 0 deletions conda_build/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
execute_download_actions,
expand_outputs,
output_yaml,
render_metadata_tuples,
render_recipe,
reparse,
try_download,
Expand Down Expand Up @@ -2465,6 +2466,24 @@ def build(
# Write out metadata for `conda debug`, making it obvious that this is what it is, must be done
# after try_download()
output_yaml(m, os.path.join(m.config.work_dir, "metadata_conda_debug.yaml"))
if m.config.verbose:
m_copy = m.copy()
for om, _, _ in render_metadata_tuples(
[(m_copy, False, False)], m_copy.config
):
print(
"",
"Rendered as:",
"```yaml",
output_yaml(om).rstrip(),
"```",
"",
sep="\n",
)
# Each iteration returns the whole meta yaml, and then we are supposed to remove
# the outputs we don't want. Instead we just take the first and print it fully
break
del m_copy

# get_dir here might be just work, or it might be one level deeper,
# dependening on the source.
Expand Down
8 changes: 7 additions & 1 deletion conda_build/jinja_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import time
from functools import partial
from io import StringIO, TextIOBase
from subprocess import CalledProcessError
from typing import TYPE_CHECKING
from warnings import warn

Expand Down Expand Up @@ -165,7 +166,12 @@ def load_setup_py_data(
args.extend(["--recipe-dir", recipe_dir])
if permit_undefined_jinja:
args.append("--permit-undefined-jinja")
check_call_env(args, env=env)
try:
check_call_env(args, env=env)
except CalledProcessError as exc:
raise CondaBuildException(
"Could not run load_setup_py_data in subprocess"
) from exc
# this is a file that the subprocess will have written
with open(
os.path.join(m.config.work_dir, "conda_build_loaded_setup_py.json")
Expand Down
55 changes: 54 additions & 1 deletion conda_build/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from conda.base.context import context
from conda.cli.common import specs_from_url
from conda.core.package_cache_data import ProgressiveFetchExtract
from conda.exceptions import UnsatisfiableError
from conda.exceptions import NoPackagesFoundError, UnsatisfiableError
from conda.gateways.disk.create import TemporaryDirectory
from conda.models.records import PackageRecord
from conda.models.version import VersionOrder
Expand Down Expand Up @@ -1000,6 +1000,59 @@ def render_recipe(
)


def render_metadata_tuples(
metadata_tuples: Iterable[MetaDataTuple],
config: Config,
permit_unsatisfiable_variants: bool = True,
finalize: bool = True,
bypass_env_check: bool = False,
) -> list[MetaDataTuple]:
output_metas: dict[tuple[str, str, tuple[tuple[str, str], ...]], MetaDataTuple] = {}
for meta, download, render_in_env in metadata_tuples:
if not meta.skip() or not config.trim_skip:
for od, om in meta.get_output_metadata_set(
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
permit_undefined_jinja=not finalize,
bypass_env_check=bypass_env_check,
):
if not om.skip() or not config.trim_skip:
if "type" not in od or od["type"] == "conda":
if finalize and not om.final:
try:
om = finalize_metadata(
om,
permit_unsatisfiable_variants=permit_unsatisfiable_variants,
)
except (DependencyNeedsBuildingError, NoPackagesFoundError):
if not permit_unsatisfiable_variants:
raise

# remove outputs section from output objects for simplicity
if not om.path and (outputs := om.get_section("outputs")):
om.parent_outputs = outputs
del om.meta["outputs"]

output_metas[
om.dist(),
om.config.variant.get("target_platform"),
tuple(
(var, om.config.variant[var])
for var in om.get_used_vars()
),
] = MetaDataTuple(om, download, render_in_env)
else:
output_metas[
f"{om.type}: {om.name()}",
om.config.variant.get("target_platform"),
tuple(
(var, om.config.variant[var])
for var in om.get_used_vars()
),
] = MetaDataTuple(om, download, render_in_env)

return list(output_metas.values())


# Keep this out of the function below so it can be imported by other modules.
FIELDS = [
"package",
Expand Down
19 changes: 19 additions & 0 deletions news/5344-report-rendered
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* Report fully rendered recipe to stdout before the build process starts. (#3798 via #5344)

### Bug fixes

* <news item>

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
13 changes: 12 additions & 1 deletion tests/test_api_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1547,7 +1547,7 @@ def test_setup_py_data_in_env(testing_config):
# should pass with any modern python (just not 3.5)
api.build(recipe, config=testing_config)
# make sure it fails with our special python logic
with pytest.raises(BuildScriptException):
with pytest.raises((BuildScriptException, CondaBuildException)):
api.build(recipe, config=testing_config, python="3.5")


Expand Down Expand Up @@ -1978,6 +1978,17 @@ def test_add_pip_as_python_dependency_from_condarc_file(
api.build(testing_metadata)


def test_rendered_is_reported(testing_config, capsys):
recipe_dir = os.path.join(metadata_dir, "outputs_overwrite_base_file")
api.build(recipe_dir, config=testing_config)

captured = capsys.readouterr()
assert "Rendered as:" in captured.out
assert "name: base-outputs_overwrite_base_file" in captured.out
assert "- name: base-outputs_overwrite_base_file" in captured.out
assert "- base-outputs_overwrite_base_file >=1.0,<2.0a0" in captured.out


@pytest.mark.skipif(on_win, reason="Tests cross-compilation targeting Windows")
def test_cross_unix_windows_mingw(testing_config):
recipe = os.path.join(metadata_dir, "_cross_unix_windows_mingw")
Expand Down

0 comments on commit 4561699

Please sign in to comment.