Skip to content

Commit ea716fe

Browse files
authoredJan 23, 2025
fix: make coverage work with bootstrap=script (bazel-contrib#2574)
The script based bootstrap wasn't expanding the coverage template variable, which prevented coverage from activating. This was introduced when it was switched to the venv layout. To fix, expand the `%coverage_tool%` template variable as done elsewhere. Tested manually, per repro instructions in bazel-contrib#2572. While I did devise a way to mostly test this without an integration test, it was thwarted by some other bugs. Along the way, improve some of the bootstrap debug output and fix a comment. Fixes bazel-contrib#2572
1 parent 626b03a commit ea716fe

File tree

4 files changed

+24
-14
lines changed

4 files changed

+24
-14
lines changed
 

‎CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ Unreleased changes template.
6565
fixes [#2554](https://github.com/bazelbuild/rules_python/issues/2554).
6666
* (runfiles) Runfile manifest and repository mapping files are now interpreted
6767
as UTF-8 on all platforms.
68+
* (coverage) Coverage with `--bootstrap_impl=script` is fixed
69+
([#2572](https://github.com/bazelbuild/rules_python/issues/2572)).
6870

6971
{#v0-0-0-added}
7072
### Added

‎python/private/py_executable.bzl

+13-10
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
561561
template = runtime.site_init_template,
562562
output = site_init,
563563
substitutions = {
564+
"%coverage_tool%": _get_coverage_tool_runfiles_path(ctx, runtime),
564565
"%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
565566
"%site_init_runfiles_path%": "{}/{}".format(ctx.workspace_name, site_init.short_path),
566567
"%workspace_name%": ctx.workspace_name,
@@ -578,6 +579,17 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
578579
def _map_each_identity(v):
579580
return v
580581

582+
def _get_coverage_tool_runfiles_path(ctx, runtime):
583+
if (ctx.configuration.coverage_enabled and
584+
runtime and
585+
runtime.coverage_tool):
586+
return "{}/{}".format(
587+
ctx.workspace_name,
588+
runtime.coverage_tool.short_path,
589+
)
590+
else:
591+
return ""
592+
581593
def _create_stage2_bootstrap(
582594
ctx,
583595
*,
@@ -593,23 +605,14 @@ def _create_stage2_bootstrap(
593605
sibling = output_sibling,
594606
)
595607
runtime = runtime_details.effective_runtime
596-
if (ctx.configuration.coverage_enabled and
597-
runtime and
598-
runtime.coverage_tool):
599-
coverage_tool_runfiles_path = "{}/{}".format(
600-
ctx.workspace_name,
601-
runtime.coverage_tool.short_path,
602-
)
603-
else:
604-
coverage_tool_runfiles_path = ""
605608

606609
template = runtime.stage2_bootstrap_template
607610

608611
ctx.actions.expand_template(
609612
template = template,
610613
output = output,
611614
substitutions = {
612-
"%coverage_tool%": coverage_tool_runfiles_path,
615+
"%coverage_tool%": _get_coverage_tool_runfiles_path(ctx, runtime),
613616
"%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
614617
"%imports%": ":".join(imports.to_list()),
615618
"%main%": "{}/{}".format(ctx.workspace_name, main_py.short_path),

‎python/private/site_init_template.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def _maybe_add_path(path):
163163
if cov_tool:
164164
_print_verbose_coverage(f"Using toolchain coverage_tool {cov_tool}")
165165
elif cov_tool := os.environ.get("PYTHON_COVERAGE"):
166-
_print_verbose_coverage(f"PYTHON_COVERAGE: {cov_tool}")
166+
_print_verbose_coverage(f"Using env var coverage: PYTHON_COVERAGE={cov_tool}")
167167

168168
if cov_tool:
169169
if os.path.isabs(cov_tool):
@@ -185,7 +185,7 @@ def _maybe_add_path(path):
185185
coverage_setup = True
186186
else:
187187
_print_verbose_coverage(
188-
"Coverage was enabled, but python coverage tool was not configured."
188+
"Coverage was enabled, but the coverage tool was not found or valid. "
189189
+ "To enable coverage, consult the docs at "
190190
+ "https://rules-python.readthedocs.io/en/latest/coverage.html"
191191
)
@@ -194,3 +194,4 @@ def _maybe_add_path(path):
194194

195195

196196
COVERAGE_SETUP = _setup_sys_path()
197+
_print_verbose("DONE")

‎python/private/stage2_bootstrap_template.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ def print_verbose(*args, mapping=None, values=None):
106106

107107
def print_verbose_coverage(*args):
108108
"""Print output if VERBOSE_COVERAGE is non-empty in the environment."""
109-
if os.environ.get("VERBOSE_COVERAGE"):
110-
print(*args, file=sys.stderr, flush=True)
109+
if is_verbose_coverage():
110+
print("bootstrap: stage 2: coverage:", *args, file=sys.stderr, flush=True)
111111

112112

113113
def is_verbose_coverage():
@@ -271,6 +271,7 @@ def _run_py(main_filename, *, args, cwd=None):
271271

272272
@contextlib.contextmanager
273273
def _maybe_collect_coverage(enable):
274+
print_verbose_coverage("enabled:", enable)
274275
if not enable:
275276
yield
276277
return
@@ -283,7 +284,9 @@ def _maybe_collect_coverage(enable):
283284
unique_id = uuid.uuid4()
284285

285286
# We need for coveragepy to use relative paths. This can only be configured
287+
# using an rc file.
286288
rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id))
289+
print_verbose_coverage("coveragerc file:", rcfile_name)
287290
with open(rcfile_name, "w") as rcfile:
288291
rcfile.write(
289292
"""[run]
@@ -318,6 +321,7 @@ def _maybe_collect_coverage(enable):
318321
finally:
319322
cov.stop()
320323
lcov_path = os.path.join(coverage_dir, "pylcov.dat")
324+
print_verbose_coverage("generating lcov from:", lcov_path)
321325
cov.lcov_report(
322326
outfile=lcov_path,
323327
# Ignore errors because sometimes instrumented files aren't

0 commit comments

Comments
 (0)