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 63114a3 commit fe7b293
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 14 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 @@ -320,6 +320,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 @@ -77,6 +77,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 @@ -117,6 +121,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 @@ -216,6 +221,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: 5 additions & 1 deletion python/private/hermetic_runtime_repo_setup.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def define_hermetic_runtime_toolchain_impl(
extra_files_glob_exclude,
python_version,
python_bin,
coverage_tool):
coverage_tool,
coverage_rc):
"""Define a toolchain implementation for a python-build-standalone repo.
It expected this macro is called in the top-level package of an extracted
Expand All @@ -47,6 +48,8 @@ def define_hermetic_runtime_toolchain_impl(
repositoroy.
coverage_tool: {type}`str` optional target to the coverage tool to
use.
coverage_rc: {type}`str` optional target to the coverage rc file to
use.
"""
_ = name # @unused
version_info = semver(python_version)
Expand Down Expand Up @@ -134,6 +137,7 @@ def define_hermetic_runtime_toolchain_impl(
},
# Convert empty string to None
coverage_tool = coverage_tool or None,
coverage_rc = coverage_rc or None,
python_version = "PY3",
implementation_name = "cpython",
# See https://peps.python.org/pep-3147/ for pyc tag infix format
Expand Down
5 changes: 5 additions & 0 deletions python/private/python.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,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 @@ -375,6 +376,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 @@ -313,13 +313,15 @@ define_hermetic_runtime_toolchain_impl(
python_version = {python_version},
python_bin = {python_bin},
coverage_tool = {coverage_tool},
coverage_rc = {coverage_rc},
)
""".format(
extra_files_glob_exclude = render.list(glob_exclude),
extra_files_glob_include = render.list(glob_include),
python_bin = render.str(python_bin),
python_version = render.str(rctx.attr.python_version),
coverage_tool = render.str(coverage_tool),
coverage_rc = rctx.attr.coverage_rc,
)
rctx.delete("python")
rctx.symlink(python_bin, "python")
Expand All @@ -329,6 +331,7 @@ define_hermetic_runtime_toolchain_impl(
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 @@ -381,6 +384,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 @@ -465,6 +474,7 @@ def python_register_toolchains(
python_version,
register_toolchains = True,
register_coverage_tool = False,
coverage_rc = None,
set_python_version_constraint = False,
tool_versions = None,
minor_mapping = None,
Expand Down Expand Up @@ -564,6 +574,7 @@ def python_register_toolchains(
urls = urls,
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 fe7b293

Please sign in to comment.