Skip to content

Commit 1fda4bf

Browse files
Create venv rules for mypy binary
**Why** The current method of symlinking in type stubs works on the few tests we have here, but suffers from extremely verbose outputs, including third party packages or generated code that you have no control over. Instead, in order to mimic mypy setups that don't use bazel and achieve parity on stub discovery, we create a virtual env and use submodule calls to invoke mypy. As a result we can clean up all our custom starlark for stub discovery, as well as mypy executable rules, and tighten up our mypy config. commit-id:871b27cd
1 parent 5629433 commit 1fda4bf

17 files changed

+225
-178
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ celerybeat.pid
102102
.env
103103
.venv
104104
env/
105-
venv/
106105
ENV/
107106
env.bak/
108107
venv.bak/

.mypy.ini

-10
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,3 @@ explicit_package_bases = true
44
namespace_packages = true
55
show_error_codes = true
66
strict = true
7-
8-
# TODO(alexmirrington): Automatically ignore errors from symlinked stub packages
9-
[mypy-grpc.*]
10-
ignore_errors = True
11-
12-
[mypy-google.*]
13-
ignore_errors = True
14-
15-
[mypy-pandas.*]
16-
ignore_errors = True

.tool-versions

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python 3.11.7

BUILD.bazel

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
load("@bazel_gazelle//:def.bzl", "gazelle")
2-
load("@rules_mypy_pip_deps//:requirements.bzl", "all_whl_requirements", "requirement")
2+
load("@rules_mypy_pip_deps//:requirements.bzl", "all_requirements", "all_whl_requirements", "requirement")
33
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
44
load("@rules_python_gazelle_plugin//:def.bzl", "GAZELLE_PYTHON_RUNTIME_DEPS")
55
load("@rules_python_gazelle_plugin//manifest:defs.bzl", "gazelle_python_manifest")
66
load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping")
7+
load("@rules_pyvenv//:venv.bzl", "py_venv")
78

89
exports_files([
910
".mypy.ini",
@@ -39,7 +40,6 @@ gazelle_python_manifest(
3940
modules_mapping = ":modules_map",
4041
pip_repository_name = "rules_mypy_pip_deps",
4142
requirements = "//:requirements_lock.txt",
42-
use_pip_repository_aliases = False,
4343
)
4444

4545
# gazelle:resolve py tests.proto.example.v1.example_pb2 //tests/proto/example/v1
@@ -48,3 +48,9 @@ gazelle(
4848
data = GAZELLE_PYTHON_RUNTIME_DEPS,
4949
gazelle = "@rules_python_gazelle_plugin//python:gazelle_binary",
5050
)
51+
52+
py_venv(
53+
name = "venv",
54+
venv_location = ".venv",
55+
deps = all_requirements,
56+
)

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ repin:
1111
bazel run //:requirements.update
1212
bazel run //:gazelle_python_manifest.update
1313

14+
.PHONY: venv
15+
venv:
16+
bazel run //:venv
17+
1418
.PHONY: generate
1519
generate:
1620
bazel run //:gazelle

WORKSPACE

-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ rules_mypy_py_deps(
7777
requirements_lock = "//:requirements_lock.txt",
7878
)
7979

80-
# TODO(alexmirrington): Move into above macro
8180
load("@rules_mypy_pip_deps//:requirements.bzl", install_pip_deps = "install_deps")
8281

8382
install_pip_deps()

pip.bzl

-2
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,3 @@ def rules_mypy_py_deps(requirements_lock, python_interpreter_target):
2020
python_interpreter_target = python_interpreter_target,
2121
requirements_lock = requirements_lock,
2222
)
23-
24-
# install_pip_deps()

repositories.bzl

+21-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ def rules_mypy_repos():
88
maybe(
99
http_archive,
1010
name = "rules_python",
11-
sha256 = "c68bdc4fbec25de5b5493b8819cfc877c4ea299c0dcb15c244c5a00208cde311",
12-
strip_prefix = "rules_python-0.31.0",
13-
url = "https://github.com/bazelbuild/rules_python/releases/download/0.31.0/rules_python-0.31.0.tar.gz",
11+
sha256 = "3b8b4cdc991bc9def8833d118e4c850f1b7498b3d65d5698eea92c3528b8cf2c",
12+
strip_prefix = "rules_python-0.30.0",
13+
url = "https://github.com/bazelbuild/rules_python/releases/download/0.30.0/rules_python-0.30.0.tar.gz",
1414
)
1515

1616
maybe(
@@ -23,6 +23,14 @@ def rules_mypy_repos():
2323
],
2424
)
2525

26+
maybe(
27+
http_archive,
28+
name = "rules_pyvenv",
29+
sha256 = "3a3cc6e211850178de02b618d301f3f39d1a9cddb54d499d816ff9ea835a2834",
30+
strip_prefix = "rules_pyvenv-1.2",
31+
url = "https://github.com/cedarai/rules_pyvenv/archive/refs/tags/v1.2.tar.gz",
32+
)
33+
2634
def rules_mypy_internal_repos():
2735
"""Install all bazel dependencies required to build and develop the project."""
2836
http_archive(
@@ -45,9 +53,9 @@ def rules_mypy_internal_repos():
4553

4654
http_archive(
4755
name = "rules_python_gazelle_plugin",
48-
sha256 = "5868e73107a8e85d8f323806e60cad7283f34b32163ea6ff1020cf27abef6036",
49-
strip_prefix = "rules_python-0.25.0/gazelle",
50-
url = "https://github.com/bazelbuild/rules_python/releases/download/0.25.0/rules_python-0.25.0.tar.gz",
56+
sha256 = "3b8b4cdc991bc9def8833d118e4c850f1b7498b3d65d5698eea92c3528b8cf2c",
57+
strip_prefix = "rules_python-0.30.0/gazelle",
58+
url = "https://github.com/bazelbuild/rules_python/releases/download/0.30.0/rules_python-0.30.0.tar.gz",
5159
)
5260

5361
http_archive(
@@ -56,3 +64,10 @@ def rules_mypy_internal_repos():
5664
strip_prefix = "rules_proto_grpc-4.6.0",
5765
urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/releases/download/4.6.0/rules_proto_grpc-4.6.0.tar.gz"],
5866
)
67+
68+
http_archive(
69+
name = "rules_pyvenv",
70+
sha256 = "3a3cc6e211850178de02b618d301f3f39d1a9cddb54d499d816ff9ea835a2834",
71+
strip_prefix = "rules_pyvenv-1.2",
72+
url = "https://github.com/cedarai/rules_pyvenv/archive/refs/tags/v1.2.tar.gz",
73+
)

rules/mypy/BUILD.bazel

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
load("@rules_mypy_pip_deps//:requirements.bzl", "all_requirements")
2-
load("@rules_python//python:defs.bzl", "py_binary")
3-
load("//rules/mypy:stubs.bzl", "py_stubs")
2+
load("//rules/venv:venv.bzl", "py_venv")
43

54
exports_files([
65
"mypy.bash.tpl",
7-
"__main__.py",
86
])
97

10-
py_binary(
11-
name = "__main__",
12-
srcs = ["__main__.py"],
13-
main = "__main__.py",
14-
visibility = ["//visibility:public"],
15-
deps = ["//config:mypy"], # keep
16-
)
17-
18-
# Make PEP-561 stubs-only packages available for type-checking.
19-
py_stubs(
20-
name = "stubs",
8+
py_venv(
9+
name = "venv",
2110
visibility = ["//visibility:public"],
2211
deps = all_requirements, # keep
2312
)

rules/mypy/__main__.py

-14
This file was deleted.

rules/mypy/mypy.bash.tpl

+4-14
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,13 @@ main() {
99
local output
1010
local report_file
1111
local status
12-
local root
13-
local mypy
1412

1513
report_file="{OUTPUT}"
16-
root="{MYPY_ROOT}/"
17-
mypy="{MYPY_EXE}"
18-
19-
# TODO(Jonathon): Consider UX improvements using https://mypy.readthedocs.io/en/stable/command_line.html#configuring-error-messages
2014

2115
export MYPYPATH="$(pwd):{MYPYPATH_PATH}"
2216

23-
# Workspace rules run in a different location from aspect rules. Here we
24-
# normalize if the external source isn't found.
25-
if [ ! -f $mypy ]; then
26-
mypy=${mypy#${root}}
27-
fi
28-
2917
set +o errexit
30-
output=$($mypy {VERBOSE_OPT} --bazel {PACKAGE_ROOTS} --config-file {MYPY_INI_PATH} --cache-map {CACHE_MAP_TRIPLES} -- {SRCS} 2>&1)
18+
output=$({PY_INTERPRETER} -m mypy {VERBOSE_OPT} --bazel {PACKAGE_ROOTS} --config-file {MYPY_INI_PATH} --cache-map {CACHE_MAP_TRIPLES} -- {SRCS} 2>&1)
3119
status=$?
3220
set -o errexit
3321

@@ -36,7 +24,9 @@ main() {
3624
fi
3725

3826
if [[ $status -ne 0 ]]; then
39-
echo "${output}" # Show MyPy's error to end-user via Bazel's console logging
27+
# Show type checking errors to end-user via Bazel's console logging
28+
# TODO(alexmirrington): Consider UX improvements using https://mypy.readthedocs.io/en/stable/command_line.html#configuring-error-messages
29+
echo "${output}"
4030
exit 1
4131
fi
4232

rules/mypy/mypy.bzl

+9-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Initial source taken from here: https://github.com/bazel-contrib/bazel-mypy-inte
55

66
load("@bazel_skylib//lib:sets.bzl", "sets")
77
load("@bazel_skylib//lib:shell.bzl", "shell")
8-
load("//rules/mypy:stubs.bzl", "PyStubsInfo")
8+
load("//rules/venv:venv.bzl", "PyVenvInfo")
99

1010
MyPyAspectInfo = provider(
1111
"TODO: documentation",
@@ -23,23 +23,18 @@ MYPY_DEBUG = False
2323
VALID_EXTENSIONS = ["py", "pyi"]
2424

2525
DEFAULT_ATTRS = {
26-
"_mypy_cli": attr.label(
27-
default = Label("//rules/mypy:__main__"),
28-
executable = True,
29-
cfg = "exec",
30-
),
3126
"_mypy_config": attr.label(
3227
default = Label("//config:mypy_config"),
3328
allow_single_file = True,
3429
),
35-
"_stubs": attr.label(
36-
default = Label("//rules/mypy:stubs"),
37-
executable = False,
38-
),
3930
"_template": attr.label(
4031
default = Label("//rules/mypy:mypy.bash.tpl"),
4132
allow_single_file = True,
4233
),
34+
"_venv": attr.label(
35+
default = Label("//rules/mypy:venv"),
36+
executable = False,
37+
),
4338
}
4439

4540
def _sources_to_cache_map_triples(srcs, is_aspect):
@@ -133,8 +128,8 @@ def _mypy_rule_impl(ctx, is_aspect = False):
133128
base_rule = ctx.rule
134129

135130
mypy_config_file = ctx.file._mypy_config
136-
mypypath_parts = [ctx.attr._stubs[PyStubsInfo].stubs_directory]
137131

132+
mypypath_parts = []
138133
direct_src_files = []
139134

140135
transitive_srcs_depsets = []
@@ -178,10 +173,8 @@ def _mypy_rule_impl(ctx, is_aspect = False):
178173
# the project version of mypy however, other rules should fall back on their
179174
# relative runfiles.
180175
runfiles = ctx.runfiles(
181-
files = src_files + ctx.attr._stubs[PyInfo].transitive_sources.to_list() + [mypy_config_file],
176+
files = src_files + [ctx.attr._venv[PyVenvInfo].venv_dir] + [mypy_config_file],
182177
)
183-
if not is_aspect:
184-
runfiles = runfiles.merge(ctx.attr._mypy_cli.default_runfiles)
185178

186179
src_root_paths = sets.to_list(
187180
sets.make([f.root.path for f in src_files]),
@@ -193,14 +186,13 @@ def _mypy_rule_impl(ctx, is_aspect = False):
193186
substitutions = {
194187
"{CACHE_MAP_TRIPLES}": " ".join(_sources_to_cache_map_triples(src_files, is_aspect)),
195188
"{MYPYPATH_PATH}": mypypath if mypypath else "",
196-
"{MYPY_EXE}": ctx.executable._mypy_cli.path,
197189
"{MYPY_INI_PATH}": mypy_config_file.path,
198-
"{MYPY_ROOT}": ctx.executable._mypy_cli.root.path,
199190
"{OUTPUT}": out.path if out else "",
200191
"{PACKAGE_ROOTS}": " ".join([
201192
"--package-root " + shell.quote(path or ".")
202193
for path in src_root_paths
203194
]),
195+
"{PY_INTERPRETER}": "%s/bin/python3" % ctx.attr._venv[PyVenvInfo].venv_dir.path,
204196
"{SRCS}": " ".join([
205197
shell.quote(f.path) if is_aspect else shell.quote(f.short_path)
206198
for f in src_files
@@ -236,7 +228,7 @@ def _mypy_aspect_impl(_, ctx):
236228
ctx.actions.run(
237229
outputs = [aspect_info.out],
238230
inputs = info.default_runfiles.files,
239-
tools = [ctx.executable._mypy_cli],
231+
tools = [],
240232
executable = aspect_info.exe,
241233
mnemonic = "MyPy",
242234
progress_message = "Type-checking %s" % ctx.label,

rules/mypy/stubs.bzl

-90
This file was deleted.

rules/venv/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports_files([
2+
"venv.bash.tpl",
3+
])

0 commit comments

Comments
 (0)