Skip to content

Commit c7a142b

Browse files
committed
POC
1 parent 1f45950 commit c7a142b

14 files changed

+117
-93
lines changed

examples/bzlmod/.coveragerc

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

examples/bzlmod/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ build_test(
8787

8888
my_cool_toolchain(
8989
name = "cool_prod_linux_runner",
90+
coverage_rc = ".coveragerc",
9091
)
9192

9293
toolchain(

examples/bzlmod/MODULE.bazel

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ python.toolchain(
3636
configure_coverage_tool = True,
3737
python_version = "3.10",
3838
)
39+
python.converage(
40+
name = "coverage",
41+
coveragerc = "coverage.rc",
42+
)
3943

4044
# You only need to load this repositories if you are using multiple Python versions.
4145
# See the tests folder for various examples on using multiple Python versions.

examples/bzlmod/test_toolchain.bzl

-54
This file was deleted.

python/BUILD.bazel

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,6 @@ current_py_toolchain(
342342
)
343343

344344
toolchain_type(
345-
name = "test_runner_toolchain_type",
345+
name = "py_test_toolchain_type",
346346
visibility = ["//visibility:public"],
347347
)

python/private/common/py_binary_rule_bazel.bzl

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,11 @@ _PY_TEST_ATTRS = {
3939
}
4040

4141
def _py_binary_impl(ctx):
42-
_, providers = py_executable_bazel_impl(
42+
return py_executable_bazel_impl(
4343
ctx = ctx,
4444
is_test = False,
4545
inherited_environment = [],
4646
)
47-
return providers
4847

4948
py_binary = create_executable_rule(
5049
implementation = _py_binary_impl,

python/private/common/py_executable.bzl

+20-20
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ Valid values are:
140140
allow_none = True,
141141
)
142142

143-
def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = []):
143+
def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = [], coverage_rc):
144144
"""Base rule implementation for a Python executable.
145145
146146
Google and Bazel call this common base and apply customizations using the
@@ -246,6 +246,7 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment =
246246
inherited_environment = inherited_environment,
247247
semantics = semantics,
248248
output_groups = exec_result.output_groups,
249+
coverage_rc = coverage_rc,
249250
)
250251

251252
def _get_build_info(ctx, cc_toolchain):
@@ -789,6 +790,7 @@ def _create_providers(
789790
inherited_environment,
790791
runtime_details,
791792
output_groups,
793+
coverage_rc,
792794
semantics):
793795
"""Creates the providers an executable should return.
794796
@@ -816,29 +818,26 @@ def _create_providers(
816818
Returns:
817819
A list of modern providers.
818820
"""
819-
default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles(
820-
ctx,
821-
runfiles_details.default_runfiles,
822-
)
823-
data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles(
824-
ctx,
825-
runfiles_details.data_runfiles,
826-
)
827-
binary_info = struct(
828-
files = default_outputs,
829-
default_runfiles = default_runfiles,
830-
data_runfiles = data_runfiles,
831-
executable = executable,
832-
)
821+
if coverage_rc:
822+
extra_test_env = {"COVERAGE_RC": coverage_rc.files.to_list()[0].path}
823+
default_runfiles = default_runfiles.merge(ctx.runfiles(files = coverage_rc.files.to_list()))
824+
else:
825+
extra_test_env = {}
833826
providers = [
834827
DefaultInfo(
835828
executable = executable,
836829
files = default_outputs,
837-
default_runfiles = default_runfiles,
838-
data_runfiles = data_runfiles,
830+
default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles(
831+
ctx,
832+
runfiles_details.default_runfiles,
833+
),
834+
data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles(
835+
ctx,
836+
runfiles_details.data_runfiles,
837+
),
839838
),
840839
create_instrumented_files_info(ctx),
841-
_create_run_environment_info(ctx, inherited_environment),
840+
_create_run_environment_info(ctx, inherited_environment, extra_test_env),
842841
PyExecutableInfo(
843842
main = main_py,
844843
runfiles_without_exe = runfiles_details.runfiles_without_exe,
@@ -905,9 +904,9 @@ def _create_providers(
905904
runtime_details = runtime_details,
906905
)
907906
providers.extend(extra_providers)
908-
return binary_info, providers
907+
return providers
909908

910-
def _create_run_environment_info(ctx, inherited_environment):
909+
def _create_run_environment_info(ctx, inherited_environment, extra_test_env):
911910
expanded_env = {}
912911
for key, value in ctx.attr.env.items():
913912
expanded_env[key] = _py_builtins.expand_location_and_make_variables(
@@ -916,6 +915,7 @@ def _create_run_environment_info(ctx, inherited_environment):
916915
expression = value,
917916
targets = ctx.attr.data,
918917
)
918+
expanded_env.update(extra_test_env)
919919
return RunEnvironmentInfo(
920920
environment = expanded_env,
921921
inherited_environment = inherited_environment,

python/private/common/py_executable_bazel.bzl

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,14 @@ def create_executable_rule(*, attrs, **kwargs):
101101
**kwargs
102102
)
103103

104-
def py_executable_bazel_impl(ctx, *, is_test, inherited_environment):
104+
def py_executable_bazel_impl(ctx, *, is_test, inherited_environment, coverage_rc = None):
105105
"""Common code for executables for Bazel."""
106106
return py_executable_base_impl(
107107
ctx = ctx,
108108
semantics = create_binary_semantics_bazel(),
109109
is_test = is_test,
110110
inherited_environment = inherited_environment,
111+
coverage_rc = coverage_rc,
111112
)
112113

113114
def create_binary_semantics_bazel():

python/private/common/py_test_rule_bazel.bzl

+10-10
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
load("@bazel_skylib//lib:dicts.bzl", "dicts")
1717
load(":attributes.bzl", "AGNOSTIC_TEST_ATTRS")
1818
load(":common.bzl", "maybe_add_test_execution_info")
19-
load("//python/private:py_executable_info.bzl", "PyExecutableInfo")
2019
load(
2120
":py_executable_bazel.bzl",
2221
"create_executable_rule",
2322
"py_executable_bazel_impl",
2423
)
24+
load("//python/private:toolchain_types.bzl", "PY_TEST_TOOLCHAIN_TYPE")
2525

2626
_BAZEL_PY_TEST_ATTRS = {
2727
# This *might* be a magic attribute to help C++ coverage work. There's no
@@ -39,28 +39,28 @@ _BAZEL_PY_TEST_ATTRS = {
3939
executable = True,
4040
),
4141
}
42-
_PY_TEST_TOOLCHAIN_TYPE = "@rules_python//python:test_runner_toolchain_type"
4342

4443
def _py_test_impl(ctx):
45-
binary_info, providers = py_executable_bazel_impl(
44+
py_test_toolchain = ctx.exec_groups["test"].toolchains[PY_TEST_TOOLCHAIN_TYPE]
45+
if py_test_toolchain:
46+
coverage_rc = py_test_toolchain.py_test_info.coverage_rc
47+
else:
48+
coverage_rc = None
49+
providers = py_executable_bazel_impl(
4650
ctx = ctx,
4751
is_test = True,
4852
inherited_environment = ctx.attr.env_inherit,
53+
coverage_rc = coverage_rc,
4954
)
5055
maybe_add_test_execution_info(providers, ctx)
51-
py_test_toolchain = ctx.exec_groups["test"].toolchains[_PY_TEST_TOOLCHAIN_TYPE]
52-
if not py_test_toolchain:
53-
return providers
54-
_ = providers.pop(0)
55-
test_providers = py_test_toolchain.py_test_info.get_runner.func(ctx, binary_info)
56-
providers.extend(test_providers)
56+
5757
return providers
5858

5959
py_test = create_executable_rule(
6060
implementation = _py_test_impl,
6161
attrs = dicts.add(AGNOSTIC_TEST_ATTRS, _BAZEL_PY_TEST_ATTRS),
6262
test = True,
6363
exec_groups = {
64-
"test": exec_group(toolchains = [config_common.toolchain_type(_PY_TEST_TOOLCHAIN_TYPE, mandatory = False)]),
64+
"test": exec_group(toolchains = [config_common.toolchain_type(PY_TEST_TOOLCHAIN_TYPE, mandatory = False)]),
6565
},
6666
)

python/private/py_test_toolchain.bzl

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
Simple toolchain which overrides env and exec requirements.
3+
"""
4+
5+
PytestProvider = provider(
6+
fields = [
7+
"coverage_rc",
8+
],
9+
)
10+
11+
def _py_test_toolchain_impl(ctx):
12+
return [
13+
platform_common.ToolchainInfo(
14+
py_test_info = PytestProvider(
15+
coverage_rc = ctx.attr.coverage_rc,
16+
),
17+
),
18+
]
19+
20+
py_test_toolchain = rule(
21+
implementation = _py_test_toolchain_impl,
22+
attrs = {
23+
"coverage_rc": attr.label(
24+
allow_single_file = True,
25+
),
26+
},
27+
)

python/private/py_toolchain_suite.bzl

+14
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ load(
2020
":toolchain_types.bzl",
2121
"EXEC_TOOLS_TOOLCHAIN_TYPE",
2222
"PY_CC_TOOLCHAIN_TYPE",
23+
"PY_TEST_TOOLCHAIN_TYPE",
2324
"TARGET_TOOLCHAIN_TYPE",
2425
)
26+
load(":py_test_toolchain.bzl", "py_test_toolchain")
2527

2628
_IS_EXEC_TOOLCHAIN_ENABLED = Label("//python/config_settings:is_exec_tools_toolchain_enabled")
2729

@@ -177,3 +179,15 @@ def define_local_toolchain_suites(name, version_aware_repo_names, version_unawar
177179
target_settings = [],
178180
target_compatible_with = ["@{}//:os".format(repo)],
179181
)
182+
183+
def register_py_test_toolchain(name, coverage_rc):
184+
# Need to create a repository rule for this to work.
185+
py_test_toolchain(
186+
name = "_{}_py_test_toolchain".format(name),
187+
coverage_rc = coverage_rc,
188+
)
189+
native.toolchain(
190+
name = "{}_py_test_toolchain".format(name),
191+
toolchain = ":_{}_py_test_toolchain".format(name),
192+
toolchain_type = PY_TEST_TOOLCHAIN_TYPE,
193+
)

python/private/python.bzl

+19
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
load("@bazel_features//:features.bzl", "bazel_features")
1818
load("//python:repositories.bzl", "python_register_toolchains")
1919
load("//python:versions.bzl", "MINOR_MAPPING", "TOOL_VERSIONS")
20+
load("//python/private:py_toolchain_suite.bzl", "register_py_test_toolchain")
2021
load(":full_version.bzl", "full_version")
2122
load(":pythons_hub.bzl", "hub_repo")
2223
load(":repo_utils.bzl", "repo_utils")
@@ -74,6 +75,11 @@ def parse_modules(module_ctx):
7475
ignore_root_user_error = False
7576

7677
for mod in module_ctx.modules:
78+
for tag in mod.tags.converage:
79+
register_py_test_toolchain(
80+
name = tag.name,
81+
coverage_rc = tag.coveragerc,
82+
)
7783
module_toolchain_versions = []
7884

7985
toolchain_attr_structs = _create_toolchain_attr_structs(mod)
@@ -401,6 +407,19 @@ can result in spurious build failures.
401407
),
402408
},
403409
),
410+
"converage": tag_class(
411+
doc = """Tag class used to register Python toolchains.""",
412+
attrs = {
413+
"name": attr.string(
414+
mandatory = True,
415+
doc = "Whether or not to configure the default coverage tool for the toolchains.",
416+
),
417+
"coveragerc": attr.label(
418+
doc = """ """,
419+
mandatory = True,
420+
),
421+
},
422+
),
404423
},
405424
**_get_bazel_version_specific_kwargs()
406425
)

python/private/stage2_bootstrap_template.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -345,13 +345,19 @@ def _maybe_collect_coverage(enable):
345345
unique_id = uuid.uuid4()
346346

347347
# 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]
348+
if os.environ.get("COVERAGE_RC"):
349+
rcfile_name = os.environ["COVERAGE_RC"]
350+
assert (
351+
os.path.exists(rcfile_name) == True
352+
), f"Coverage rc {rcfile_name} file does not exist"
353+
else:
354+
rcfile_name = os.path.join(coverage_dir, ".coveragerc_{}".format(unique_id))
355+
with open(rcfile_name, "w") as rcfile:
356+
rcfile.write(
357+
"""[run]
352358
relative_files = True
353359
"""
354-
)
360+
)
355361
try:
356362
cov = coverage.Coverage(
357363
config_file=rcfile_name,

python/private/toolchain_types.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ implementation of the toolchain.
2121
TARGET_TOOLCHAIN_TYPE = Label("//python:toolchain_type")
2222
EXEC_TOOLS_TOOLCHAIN_TYPE = Label("//python:exec_tools_toolchain_type")
2323
PY_CC_TOOLCHAIN_TYPE = Label("//python/cc:toolchain_type")
24+
PY_TEST_TOOLCHAIN_TYPE = Label("//python:py_test_toolchain_type")

0 commit comments

Comments
 (0)