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

Report fully rendered recipe to stdout #5344

Merged
merged 11 commits into from
Jun 7, 2024
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this old syntax because this module will be injected in the workdir to be executed by a potentially older Python (e.g. 3.5).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this more like a ValueError or RuntimeError (or FileNotFoundError)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 I didn't put it there :/


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 @@ -64,6 +64,7 @@
execute_download_actions,
expand_outputs,
output_yaml,
render_metadata_tuples,
render_recipe,
reparse,
try_download,
Expand Down Expand Up @@ -2433,6 +2434,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