Skip to content

Commit

Permalink
feat(toolchain) Add coveragepy configuration attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
ewianda committed Sep 16, 2024
1 parent 6361057 commit ac30050
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 13 deletions.
8 changes: 8 additions & 0 deletions examples/bzlmod/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[report]
include_namespace_packages=True
skip_covered = True
[run]
relative_files = True
branch = True
omit =
*/external/*
2 changes: 2 additions & 0 deletions examples/bzlmod/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,5 @@ build_test(
name = "all_requirements",
targets = all_requirements,
)

exports_files([".coveragerc"])
1 change: 1 addition & 0 deletions examples/bzlmod/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bazel_dep(name = "protobuf", version = "24.4", repo_name = "com_google_protobuf"
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
configure_coverage_tool = True,
coverage_rc = "//:.coveragerc",
# Only set when you have mulitple toolchain versions.
is_default = True,
python_version = "3.9",
Expand Down
4 changes: 2 additions & 2 deletions examples/bzlmod/MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions python/private/common/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def _PyRuntimeInfo_init(
interpreter = None,
files = None,
coverage_tool = None,
coverage_rc = None,
coverage_files = None,
pyc_tag = None,
python_version,
Expand Down Expand Up @@ -121,6 +122,7 @@ def _PyRuntimeInfo_init(
"bootstrap_template": bootstrap_template,
"coverage_files": coverage_files,
"coverage_tool": coverage_tool,
"coverage_rc": coverage_rc,
"files": files,
"implementation_name": implementation_name,
"interpreter": interpreter,
Expand Down Expand Up @@ -202,6 +204,12 @@ The files required at runtime for using `coverage_tool`. Will be `None` if no
If set, this field is a `File` representing tool used for collecting code
coverage information from python tests. Otherwise, this is `None`.
""",
"coverage_rc": """
:type: File | None
If set, this field is a `File` representing the configuration file used by the
coverage information from python tests. Otherwise, this is `None`.
""",
"files": """
:type: depset[File] | None
Expand Down
2 changes: 2 additions & 0 deletions python/private/common/py_executable.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ def _get_runtime_details(ctx, semantics):
direct.append(effective_runtime.coverage_tool)
if effective_runtime.coverage_files:
transitive.append(effective_runtime.coverage_files)
if effective_runtime.coverage_rc:
direct.append(effective_runtime.coverage_rc)
runtime_files = depset(direct = direct, transitive = transitive)
else:
runtime_files = depset()
Expand Down
11 changes: 11 additions & 0 deletions python/private/common/py_executable_bazel.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ def _create_stage2_bootstrap(
)
else:
coverage_tool_runfiles_path = ""
if runtime and runtime.coverage_rc:
coverage_rc_path = runtime.coverage_rc.path
else:
coverage_rc_path = ""

template = runtime.stage2_bootstrap_template

Expand All @@ -351,6 +355,7 @@ def _create_stage2_bootstrap(
output = output,
substitutions = {
"%coverage_tool%": coverage_tool_runfiles_path,
"%coverage_rc%": coverage_rc_path,
"%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
"%imports%": ":".join(imports.to_list()),
"%main%": "{}/{}".format(ctx.workspace_name, main_py.short_path),
Expand Down Expand Up @@ -403,6 +408,12 @@ def _create_stage1_bootstrap(
subs["%shebang%"] = DEFAULT_STUB_SHEBANG
template = ctx.file._bootstrap_template

if runtime and runtime.coverage_rc:
coverage_rc_path = runtime.coverage_rc.path
else:
coverage_rc_path = ""

subs["%coverage_rc%"] = coverage_rc_path
subs["%coverage_tool%"] = coverage_tool_runfiles_path
subs["%import_all%"] = ("True" if ctx.fragments.bazel_py.python_import_all_repositories else "False")
subs["%imports%"] = ":".join(imports.to_list())
Expand Down
11 changes: 11 additions & 0 deletions python/private/common/py_runtime_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def _py_runtime_impl(ctx):
else:
coverage_tool = None
coverage_files = None
if ctx.attr.coverage_rc:
coverage_rc = ctx.attr.coverage_rc.files.to_list()[0]
else:
coverage_rc = None

python_version = ctx.attr.python_version

Expand Down Expand Up @@ -112,6 +116,7 @@ def _py_runtime_impl(ctx):
py_runtime_info_kwargs.update(dict(
implementation_name = ctx.attr.implementation_name,
interpreter_version_info = interpreter_version_info,
coverage_rc = coverage_rc,
pyc_tag = pyc_tag,
stage2_bootstrap_template = ctx.file.stage2_bootstrap_template,
zip_main_template = ctx.file.zip_main_template,
Expand Down Expand Up @@ -218,6 +223,12 @@ of coverage.py (https://coverage.readthedocs.io), at least including
the `run` and `lcov` subcommands.
""",
),
"coverage_rc": attr.label(
allow_single_file = True,
doc = ".converage or pyproject.toml or " +
"for configure coverage tool",
mandatory = False,
),
"files": attr.label_list(
allow_files = True,
doc = """
Expand Down
6 changes: 6 additions & 0 deletions python/private/python.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def _python_register_toolchains(name, toolchain_attr, module, ignore_root_user_e
name = name,
python_version = toolchain_attr.python_version,
register_coverage_tool = toolchain_attr.configure_coverage_tool,
coverage_rc = toolchain_attr.coverage_rc,
ignore_root_user_error = ignore_root_user_error,
)
return struct(
Expand Down Expand Up @@ -287,6 +288,7 @@ def _create_toolchain_attrs_struct(*, tag = None, python_version = None, toolcha
python_version = python_version if python_version else tag.python_version,
configure_coverage_tool = getattr(tag, "configure_coverage_tool", False),
ignore_root_user_error = getattr(tag, "ignore_root_user_error", False),
coverage_rc = getattr(tag, "coverage_rc", None),
)

def _get_bazel_version_specific_kwargs():
Expand Down Expand Up @@ -343,6 +345,10 @@ A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
mandatory = False,
doc = "Whether or not to configure the default coverage tool for the toolchains.",
),
"coverage_rc": attr.label(
mandatory = False,
doc = "The coverage configuration file to use for the toolchains.",
),
"ignore_root_user_error": attr.bool(
default = False,
doc = """\
Expand Down
8 changes: 5 additions & 3 deletions python/private/python_bootstrap_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,11 @@ def _RunForCoverage(python_program, main_filename, args, env,
"""
# We need for coveragepy to use relative paths. This can only be configured
unique_id = uuid.uuid4()
rcfile_name = os.path.join(os.environ['COVERAGE_DIR'], ".coveragerc_{}".format(unique_id))
with open(rcfile_name, "w") as rcfile:
rcfile.write('''[run]
rcfile_name = "%coverage_rc%"
if not rcfile_name:
rcfile_name = os.path.join(os.environ['COVERAGE_DIR'], ".coveragerc_{}".format(unique_id))
with open(rcfile_name, "w") as rcfile:
rcfile.write('''[run]
relative_files = True
''')
PrintVerboseCoverage('Coverage entrypoint:', coverage_entrypoint)
Expand Down
11 changes: 11 additions & 0 deletions python/private/python_repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ py_runtime(
name = "py3_runtime",
files = [":files"],
{coverage_attr}
coverage_rc = "{coverage_rc}",
interpreter = "{python_path}",
interpreter_version_info = {{
"major": "{interpreter_version_info_major}",
Expand Down Expand Up @@ -433,6 +434,7 @@ py_exec_tools_toolchain(
python_version = python_short_version,
python_version_nodot = python_short_version.replace(".", ""),
coverage_attr = coverage_attr_text,
coverage_rc = rctx.attr.coverage_rc,
interpreter_version_info_major = python_version_info[0],
interpreter_version_info_minor = python_version_info[1],
interpreter_version_info_micro = python_version_info[2],
Expand All @@ -445,6 +447,7 @@ py_exec_tools_toolchain(
attrs = {
"auth_patterns": rctx.attr.auth_patterns,
"coverage_tool": rctx.attr.coverage_tool,
"coverage_rc": rctx.attr.coverage_rc,
"distutils": rctx.attr.distutils,
"distutils_content": rctx.attr.distutils_content,
"ignore_root_user_error": rctx.attr.ignore_root_user_error,
Expand Down Expand Up @@ -496,6 +499,12 @@ For more information see the official bazel docs
(https://bazel.build/reference/be/python#py_runtime.coverage_tool).
""",
),
"coverage_rc": attr.label(
allow_single_file = True,
doc = ".converage or pyproject.toml or " +
"for configure coverage tool",
mandatory = False,
),
"distutils": attr.label(
allow_single_file = True,
doc = "A distutils.cfg file to be included in the Python installation. " +
Expand Down Expand Up @@ -567,6 +576,7 @@ def python_register_toolchains(
distutils_content = None,
register_toolchains = True,
register_coverage_tool = False,
coverage_rc = None,
set_python_version_constraint = False,
tool_versions = TOOL_VERSIONS,
**kwargs):
Expand Down Expand Up @@ -660,6 +670,7 @@ def python_register_toolchains(
distutils_content = distutils_content,
strip_prefix = strip_prefix,
coverage_tool = coverage_tool,
coverage_rc = coverage_rc,
**kwargs
)
if register_toolchains:
Expand Down
19 changes: 11 additions & 8 deletions python/private/stage2_bootstrap_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
IMPORT_ALL = True if "%import_all%" == "True" else False
# Runfiles-relative path to the coverage tool entry point, if any.
COVERAGE_TOOL = "%coverage_tool%"
COVERAGE_RCFILE = "%coverage_rc%"

# ===== Template substitutions end =====

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

# We need for coveragepy to use relative paths. This can only be configured
rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id))
with open(rcfile_name, "w") as rcfile:
rcfile.write(
"""[run]
relative_files = True
"""
)
rcfile_name = COVERAGE_RCFILE
if not rcfile_name:
# We need for coveragepy to use relative paths. This can only be configured
rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id))
with open(rcfile_name, "w") as rcfile:
rcfile.write(
"""[run]
relative_files = True
"""
)
try:
cov = coverage.Coverage(
config_file=rcfile_name,
Expand Down

0 comments on commit ac30050

Please sign in to comment.