Skip to content

Commit ac30050

Browse files
committed
feat(toolchain) Add coveragepy configuration attribute
1 parent 6361057 commit ac30050

12 files changed

+78
-13
lines changed

examples/bzlmod/.coveragerc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[report]
2+
include_namespace_packages=True
3+
skip_covered = True
4+
[run]
5+
relative_files = True
6+
branch = True
7+
omit =
8+
*/external/*

examples/bzlmod/BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,5 @@ build_test(
8282
name = "all_requirements",
8383
targets = all_requirements,
8484
)
85+
86+
exports_files([".coveragerc"])

examples/bzlmod/MODULE.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bazel_dep(name = "protobuf", version = "24.4", repo_name = "com_google_protobuf"
2222
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
2323
python.toolchain(
2424
configure_coverage_tool = True,
25+
coverage_rc = "//:.coveragerc",
2526
# Only set when you have mulitple toolchain versions.
2627
is_default = True,
2728
python_version = "3.9",

examples/bzlmod/MODULE.bazel.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/private/common/providers.bzl

+8
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def _PyRuntimeInfo_init(
7373
interpreter = None,
7474
files = None,
7575
coverage_tool = None,
76+
coverage_rc = None,
7677
coverage_files = None,
7778
pyc_tag = None,
7879
python_version,
@@ -121,6 +122,7 @@ def _PyRuntimeInfo_init(
121122
"bootstrap_template": bootstrap_template,
122123
"coverage_files": coverage_files,
123124
"coverage_tool": coverage_tool,
125+
"coverage_rc": coverage_rc,
124126
"files": files,
125127
"implementation_name": implementation_name,
126128
"interpreter": interpreter,
@@ -202,6 +204,12 @@ The files required at runtime for using `coverage_tool`. Will be `None` if no
202204
203205
If set, this field is a `File` representing tool used for collecting code
204206
coverage information from python tests. Otherwise, this is `None`.
207+
""",
208+
"coverage_rc": """
209+
:type: File | None
210+
211+
If set, this field is a `File` representing the configuration file used by the
212+
coverage information from python tests. Otherwise, this is `None`.
205213
""",
206214
"files": """
207215
:type: depset[File] | None

python/private/common/py_executable.bzl

+2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ def _get_runtime_details(ctx, semantics):
313313
direct.append(effective_runtime.coverage_tool)
314314
if effective_runtime.coverage_files:
315315
transitive.append(effective_runtime.coverage_files)
316+
if effective_runtime.coverage_rc:
317+
direct.append(effective_runtime.coverage_rc)
316318
runtime_files = depset(direct = direct, transitive = transitive)
317319
else:
318320
runtime_files = depset()

python/private/common/py_executable_bazel.bzl

+11
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ def _create_stage2_bootstrap(
343343
)
344344
else:
345345
coverage_tool_runfiles_path = ""
346+
if runtime and runtime.coverage_rc:
347+
coverage_rc_path = runtime.coverage_rc.path
348+
else:
349+
coverage_rc_path = ""
346350

347351
template = runtime.stage2_bootstrap_template
348352

@@ -351,6 +355,7 @@ def _create_stage2_bootstrap(
351355
output = output,
352356
substitutions = {
353357
"%coverage_tool%": coverage_tool_runfiles_path,
358+
"%coverage_rc%": coverage_rc_path,
354359
"%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
355360
"%imports%": ":".join(imports.to_list()),
356361
"%main%": "{}/{}".format(ctx.workspace_name, main_py.short_path),
@@ -403,6 +408,12 @@ def _create_stage1_bootstrap(
403408
subs["%shebang%"] = DEFAULT_STUB_SHEBANG
404409
template = ctx.file._bootstrap_template
405410

411+
if runtime and runtime.coverage_rc:
412+
coverage_rc_path = runtime.coverage_rc.path
413+
else:
414+
coverage_rc_path = ""
415+
416+
subs["%coverage_rc%"] = coverage_rc_path
406417
subs["%coverage_tool%"] = coverage_tool_runfiles_path
407418
subs["%import_all%"] = ("True" if ctx.fragments.bazel_py.python_import_all_repositories else "False")
408419
subs["%imports%"] = ":".join(imports.to_list())

python/private/common/py_runtime_rule.bzl

+11
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ def _py_runtime_impl(ctx):
7676
else:
7777
coverage_tool = None
7878
coverage_files = None
79+
if ctx.attr.coverage_rc:
80+
coverage_rc = ctx.attr.coverage_rc.files.to_list()[0]
81+
else:
82+
coverage_rc = None
7983

8084
python_version = ctx.attr.python_version
8185

@@ -112,6 +116,7 @@ def _py_runtime_impl(ctx):
112116
py_runtime_info_kwargs.update(dict(
113117
implementation_name = ctx.attr.implementation_name,
114118
interpreter_version_info = interpreter_version_info,
119+
coverage_rc = coverage_rc,
115120
pyc_tag = pyc_tag,
116121
stage2_bootstrap_template = ctx.file.stage2_bootstrap_template,
117122
zip_main_template = ctx.file.zip_main_template,
@@ -218,6 +223,12 @@ of coverage.py (https://coverage.readthedocs.io), at least including
218223
the `run` and `lcov` subcommands.
219224
""",
220225
),
226+
"coverage_rc": attr.label(
227+
allow_single_file = True,
228+
doc = ".converage or pyproject.toml or " +
229+
"for configure coverage tool",
230+
mandatory = False,
231+
),
221232
"files": attr.label_list(
222233
allow_files = True,
223234
doc = """

python/private/python.bzl

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def _python_register_toolchains(name, toolchain_attr, module, ignore_root_user_e
3535
name = name,
3636
python_version = toolchain_attr.python_version,
3737
register_coverage_tool = toolchain_attr.configure_coverage_tool,
38+
coverage_rc = toolchain_attr.coverage_rc,
3839
ignore_root_user_error = ignore_root_user_error,
3940
)
4041
return struct(
@@ -287,6 +288,7 @@ def _create_toolchain_attrs_struct(*, tag = None, python_version = None, toolcha
287288
python_version = python_version if python_version else tag.python_version,
288289
configure_coverage_tool = getattr(tag, "configure_coverage_tool", False),
289290
ignore_root_user_error = getattr(tag, "ignore_root_user_error", False),
291+
coverage_rc = getattr(tag, "coverage_rc", None),
290292
)
291293

292294
def _get_bazel_version_specific_kwargs():
@@ -343,6 +345,10 @@ A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
343345
mandatory = False,
344346
doc = "Whether or not to configure the default coverage tool for the toolchains.",
345347
),
348+
"coverage_rc": attr.label(
349+
mandatory = False,
350+
doc = "The coverage configuration file to use for the toolchains.",
351+
),
346352
"ignore_root_user_error": attr.bool(
347353
default = False,
348354
doc = """\

python/private/python_bootstrap_template.txt

+5-3
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,11 @@ def _RunForCoverage(python_program, main_filename, args, env,
402402
"""
403403
# We need for coveragepy to use relative paths. This can only be configured
404404
unique_id = uuid.uuid4()
405-
rcfile_name = os.path.join(os.environ['COVERAGE_DIR'], ".coveragerc_{}".format(unique_id))
406-
with open(rcfile_name, "w") as rcfile:
407-
rcfile.write('''[run]
405+
rcfile_name = "%coverage_rc%"
406+
if not rcfile_name:
407+
rcfile_name = os.path.join(os.environ['COVERAGE_DIR'], ".coveragerc_{}".format(unique_id))
408+
with open(rcfile_name, "w") as rcfile:
409+
rcfile.write('''[run]
408410
relative_files = True
409411
''')
410412
PrintVerboseCoverage('Coverage entrypoint:', coverage_entrypoint)

python/private/python_repositories.bzl

+11
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ py_runtime(
398398
name = "py3_runtime",
399399
files = [":files"],
400400
{coverage_attr}
401+
coverage_rc = "{coverage_rc}",
401402
interpreter = "{python_path}",
402403
interpreter_version_info = {{
403404
"major": "{interpreter_version_info_major}",
@@ -433,6 +434,7 @@ py_exec_tools_toolchain(
433434
python_version = python_short_version,
434435
python_version_nodot = python_short_version.replace(".", ""),
435436
coverage_attr = coverage_attr_text,
437+
coverage_rc = rctx.attr.coverage_rc,
436438
interpreter_version_info_major = python_version_info[0],
437439
interpreter_version_info_minor = python_version_info[1],
438440
interpreter_version_info_micro = python_version_info[2],
@@ -445,6 +447,7 @@ py_exec_tools_toolchain(
445447
attrs = {
446448
"auth_patterns": rctx.attr.auth_patterns,
447449
"coverage_tool": rctx.attr.coverage_tool,
450+
"coverage_rc": rctx.attr.coverage_rc,
448451
"distutils": rctx.attr.distutils,
449452
"distutils_content": rctx.attr.distutils_content,
450453
"ignore_root_user_error": rctx.attr.ignore_root_user_error,
@@ -496,6 +499,12 @@ For more information see the official bazel docs
496499
(https://bazel.build/reference/be/python#py_runtime.coverage_tool).
497500
""",
498501
),
502+
"coverage_rc": attr.label(
503+
allow_single_file = True,
504+
doc = ".converage or pyproject.toml or " +
505+
"for configure coverage tool",
506+
mandatory = False,
507+
),
499508
"distutils": attr.label(
500509
allow_single_file = True,
501510
doc = "A distutils.cfg file to be included in the Python installation. " +
@@ -567,6 +576,7 @@ def python_register_toolchains(
567576
distutils_content = None,
568577
register_toolchains = True,
569578
register_coverage_tool = False,
579+
coverage_rc = None,
570580
set_python_version_constraint = False,
571581
tool_versions = TOOL_VERSIONS,
572582
**kwargs):
@@ -660,6 +670,7 @@ def python_register_toolchains(
660670
distutils_content = distutils_content,
661671
strip_prefix = strip_prefix,
662672
coverage_tool = coverage_tool,
673+
coverage_rc = coverage_rc,
663674
**kwargs
664675
)
665676
if register_toolchains:

python/private/stage2_bootstrap_template.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
IMPORT_ALL = True if "%import_all%" == "True" else False
3333
# Runfiles-relative path to the coverage tool entry point, if any.
3434
COVERAGE_TOOL = "%coverage_tool%"
35+
COVERAGE_RCFILE = "%coverage_rc%"
3536

3637
# ===== Template substitutions end =====
3738

@@ -344,14 +345,16 @@ def _maybe_collect_coverage(enable):
344345
coverage_dir = os.environ["COVERAGE_DIR"]
345346
unique_id = uuid.uuid4()
346347

347-
# We need for coveragepy to use relative paths. This can only be configured
348-
rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id))
349-
with open(rcfile_name, "w") as rcfile:
350-
rcfile.write(
351-
"""[run]
352-
relative_files = True
353-
"""
354-
)
348+
rcfile_name = COVERAGE_RCFILE
349+
if not rcfile_name:
350+
# We need for coveragepy to use relative paths. This can only be configured
351+
rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id))
352+
with open(rcfile_name, "w") as rcfile:
353+
rcfile.write(
354+
"""[run]
355+
relative_files = True
356+
"""
357+
)
355358
try:
356359
cov = coverage.Coverage(
357360
config_file=rcfile_name,

0 commit comments

Comments
 (0)