blob: 994710ac01747c77f93249a7254a1e22b37240f6 [file]
"""
mypy build rule.
The mypy build rule runs mypy, succeeding if mypy is error free and failing if mypy produces errors. The
result of the build is a mypy cache directory, located at [name].mypy_cache. When provided input cache
directories (the results of other mypy builds), the build rule first attempts to merge the cache directories.
"""
load("@rules_mypy_pip//:requirements.bzl", "requirement")
load("@rules_python//python:py_binary.bzl", "py_binary")
load("//mypy/private:py_type_library.bzl", "PyTypeLibraryInfo")
MypyCacheInfo = provider(
doc = "Output details of the mypy build rule.",
fields = {
"cache_directory": "Location of the mypy cache produced by this target.",
},
)
def _mypy_impl(ctx):
# we need to help mypy map the location of external deps by setting
# MYPYPATH to include the site-packages directories.
external_deps = []
types = []
for dep in ctx.attr.deps:
if PyTypeLibraryInfo in dep:
types.append(dep[PyTypeLibraryInfo].directory + "/site-packages")
elif dep.label.workspace_root.startswith("external/"):
external_deps.append(dep.label.workspace_root + "/site-packages")
# types need to appear first in the mypy path since the module directories
# are the same and mypy resolves the first ones, first.
mypy_path = ":".join(types + external_deps)
cache_directory = ctx.actions.declare_directory(ctx.attr.name + ".mypy_cache")
args = ctx.actions.args()
args.add("--cache-dir", cache_directory.path)
args.add_all([
cache[MypyCacheInfo].cache_directory.path
for cache in ctx.attr.caches
], before_each = "--upstream-cache")
args.add_all(ctx.files.srcs)
ctx.actions.run(
mnemonic = "mypy",
inputs = depset(direct = ctx.files.srcs + ctx.files.deps + ctx.files.caches),
outputs = [cache_directory],
executable = ctx.executable.mypy_cli,
arguments = [args],
env = {
"MYPYPATH": mypy_path,
# force color on
"MYPY_FORCE_COLOR": "1",
# force color on only works if TERM is set to something that supports color
"TERM": "xterm-256color",
} | ctx.configuration.default_shell_env,
)
return [
DefaultInfo(
files = depset([cache_directory]),
runfiles = ctx.runfiles(files = [cache_directory]),
),
MypyCacheInfo(cache_directory = cache_directory),
]
_mypy = rule(
implementation = _mypy_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
"deps": attr.label_list(),
"mypy_ini": attr.label(allow_single_file = True),
"mypy_cli": attr.label(cfg = "exec", default = "//mypy/private:mypy", executable = True),
"caches": attr.label_list(),
},
)
def mypy(name, srcs = None, deps = None, mypy_ini = None, mypy_cli = None, visibility = None):
"""
Create a mypy target inferring upstream caches from deps.
Args:
name: name of the target to produce
srcs: (optional) srcs to type-check
deps: (optional) deps used as input to type-checking
mypy_ini: (optional) mypy_ini file to use
mypy_cli: (optional) a replacement mypy_cli to use (recommended to produce
with mypy_cli macro)
visibility: (optional) visibility of this target (recommended to set to same
as the py_* target it inherits)
"""
upstream_caches = []
if deps:
for dep in deps:
lab = native.package_relative_label(dep)
if lab.workspace_root == "":
upstream_caches.append(str(lab) + ".mypy")
_mypy(
name = name,
srcs = srcs,
deps = deps,
mypy_ini = mypy_ini,
mypy_cli = mypy_cli,
visibility = visibility,
)
def mypy_cli(name, deps = None, mypy_requirement = None):
"""
Produce a custom mypy executable for use with the mypy build rule.
Args:
name: name of the binary target this macro produces
deps: (optional) additional dependencies to include (e.g. mypy plugins)
mypy_requirement: (optional) a replacement mypy requirement
"""
deps = deps or []
mypy_requirement = mypy_requirement or requirement("mypy")
py_binary(
name = name,
srcs = ["@rules_mypy//mypy/private:mypy.py"],
main = "@rules_mypy//mypy/private:mypy.py",
visibility = ["//visibility:public"],
deps = [
mypy_requirement,
requirement("click"),
] + deps,
)