Fix output bin_dir directory shadowing source directory (#91)
Fixes #88
In the test case added in this PR, the sandbox contains the following:
```
sandbox/.../execroot/_main/
|-- bazel-out/darwin_arm64-fastbuild/bin/
|-- nested/
|-- nested.mypy_cache/
|-- nested/
|-- __init__.py
```
The `bazel-out/darwin_arm64-fastbuild/bin/` directory is added to
MYPYPATH as part of `generated_dirs` and is necessary in certain
situations, but here, mypy finds the `nested/` directory in `bazel-out/`
instead of the one in the current directory, because mypy searches
MYPYPATH before CWD.
In this PR, we explicitly add CWD first to ensure we look at source
files before generated filesAn aspect to instrument py_* targets with mypy type-checking.
Compared to bazel-mypy-integration, this ruleset aims to make a couple of improvements:
[!WARNING]
rules_mypy's build actions produce mypy caches as outputs, and these may contain large file counts and that will only grow as a dependency chain grows. This may have an impact on the size and usage of build and/or remote caches.
This aspect will run over any py_binary, py_library or py_test.
Setup is significantly easier with bzlmod, we recommend and predominantly support bzlmod, though these rules should work without issue in non-bzlmod setups, albeit with more work to configure.
Add rules_mypy to your MODULE.bazel:
bazel_dep(name = "rules_mypy", version = "0.0.0")
Optionally, configure a types repository:
Many Python packages have separately published types/stubs packages. While mypy (and these rules) will work without including these types, this ruleset provides some utilities for leveraging these types to improve mypy's type checking.
types = use_extension("@rules_mypy//mypy:types.bzl", "types") types.requirements( name = "pip_types", # `@pip` in the next line corresponds to the `hub_name` when using # rules_python's `pip.parse(...)`. pip_requirements = "@pip//:requirements.bzl", # also legal to pass a `requirements.in` here requirements_txt = "//:requirements.txt", ) use_repo(types, "pip_types")
Configure mypy_aspect.
Define a new aspect in a .bzl file (such as ./tools/aspects.bzl):
load("@pip_types//:types.bzl", "types") load("@rules_mypy//mypy:mypy.bzl", "mypy") mypy_aspect = mypy(types = types)
Update your .bazelrc to include this new aspect:
# register mypy_aspect with Bazel build --aspects //tools:aspects.bzl%mypy_aspect # optionally, default enable the mypy checks build --output_groups=+mypy
mypy's behavior may be customized using a mypy config file file. To use a mypy config file, pass a label for a valid config file to the mypy aspect factory:
mypy_aspect = mypy( mypy_ini = "@@//:mypy.ini", types = types, )
[!NOTE] The label passed to
mypy_inineeds to be absolute (a prefix of@@means the root repo).
[!NOTE] mypy.ini files should likely contain the following lines to suppress type-checking 3rd party modules.
follow_imports = silent follow_imports_for_stubs = True
To customize the version of mypy, use rules_python's requirements resolution and construct a custom mypy CLI:
# in a BUILD file load("@pip//:requirements.bzl", "requirements") # '@pip' must match configured pip hub_name load("@rules_mypy//mypy:mypy.bzl", "mypy", "mypy_cli") mypy_cli( name = "mypy_cli", mypy_requirement = requirement("mypy"), )
And in your aspects.bzl (or similar) file:
load("@rules_mypy//mypy:mypy.bzl", "mypy") mypy_aspect = mypy( mypy_cli = ":mypy_cli", types = types, )
Further, to use mypy plugins referenced in any config file, use the deps attribute of mypy_cli:
# in a BUILD file load("@pip//:requirements.bzl", "requirement") # '@pip' must match configured pip hub_name load("@rules_mypy//mypy:mypy.bzl", "mypy", "mypy_cli") mypy_cli( name = "mypy_cli", mypy_requirement = requirement("mypy"), deps = [ requirement("pydantic"), ], )
Skip running mypy on targets by tagging with no-mypy, or customize the tags that will suppress mypy by providing a list to the suppression_tags argument of the mypy aspect initializer:
load("@rules_mypy//mypy:mypy.bzl", "mypy") mypy_aspect = mypy( suppression_tags = ["no-mypy", "no-checks"], types = types, )
To add type checking to a codebase incrementally, configure a list of opt-in tags that will suppress running mypy by default unless a target is tagged explicitly with one of the opt-in tags.
load("@rules_mypy//mypy:mypy.bzl", "mypy") mypy_aspect = mypy( opt_in_tags = ["typecheck"], types = types, )