Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.

Commit 840537e

Browse files
jonmeowalexeagle
authored andcommitted
Include external srcs for mypy dep analysis with pip_install
1 parent afb77df commit 840537e

File tree

3 files changed

+58
-15
lines changed

3 files changed

+58
-15
lines changed

examples/third_party/BUILD

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,11 @@ py_binary(
1111

1212
mypy_test(
1313
name = "uses_deps_mypy",
14-
deps = [":uses-deps"],
14+
deps = [
15+
":uses-deps",
16+
requirement("types_python_dateutil"),
17+
],
18+
# Confirmed the launcher script contains
19+
# export MYPYPATH="$(pwd):../my_deps/pypi__types_python_dateutil"
20+
include_imports = True,
1521
)

mypy.bzl

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,21 @@ def _extract_srcs(srcs):
6565
direct_src_files.append(f)
6666
return direct_src_files
6767

68-
def _extract_transitive_deps(deps):
68+
def _extract_transitive_deps(deps, include_imports):
6969
transitive_deps = []
70+
transitive_imports = []
71+
seen_imports = {} # No sets in Starlark, so use a dict.
7072
for dep in deps:
71-
if MyPyStubsInfo not in dep and PyInfo in dep and not _is_external_dep(dep):
72-
transitive_deps.append(dep[PyInfo].transitive_sources)
73-
return transitive_deps
73+
if MyPyStubsInfo not in dep and PyInfo in dep:
74+
if include_imports:
75+
transitive_deps.append(dep[PyInfo].transitive_sources)
76+
for imp in dep[PyInfo].imports.to_list():
77+
if imp not in seen_imports:
78+
seen_imports[imp] = None
79+
transitive_imports.append(imp)
80+
elif not _is_external_dep(dep):
81+
transitive_deps.append(dep[PyInfo].transitive_sources)
82+
return transitive_deps, transitive_imports
7483

7584
def _extract_stub_deps(deps):
7685
# Need to add the .py files AND the .pyi files that are
@@ -110,22 +119,37 @@ def _mypy_rule_impl(ctx, is_aspect = False):
110119
transitive_srcs_depsets = []
111120
stub_files = []
112121

122+
include_imports = hasattr(base_rule.attr, "include_imports") and base_rule.attr.include_imports
123+
113124
if hasattr(base_rule.attr, "srcs"):
114125
direct_src_files = _extract_srcs(base_rule.attr.srcs)
115126

116127
if hasattr(base_rule.attr, "deps"):
117-
transitive_srcs_depsets = _extract_transitive_deps(base_rule.attr.deps)
128+
transitive_srcs_depsets, transitive_imports = _extract_transitive_deps(base_rule.attr.deps, include_imports)
118129
stub_files = _extract_stub_deps(base_rule.attr.deps)
130+
if transitive_imports:
131+
rel_workspace_root = ''
132+
# If in a package, imports need to be made relative to the
133+
# workspace root.
134+
if ctx.label.package:
135+
rel_workspace_root = '../' * (ctx.label.package.count('/') + 1)
136+
mypypath_parts += [rel_workspace_root + x for x in transitive_imports]
119137

120138
if hasattr(base_rule.attr, "imports"):
121-
mypypath_parts = _extract_imports(base_rule.attr.imports, ctx.label)
139+
mypypath_parts += _extract_imports(base_rule.attr.imports, ctx.label)
122140

123141
final_srcs_depset = depset(transitive = transitive_srcs_depsets +
124142
[depset(direct = direct_src_files)])
125-
src_files = [f for f in final_srcs_depset.to_list() if not _is_external_src(f)]
126-
if not src_files:
143+
input_src_files = final_srcs_depset.to_list()
144+
target_src_files = [f for f in input_src_files if not _is_external_src(f)]
145+
if not target_src_files:
127146
return None
128147

148+
# If imports aren't being included, the input src files are restricted to
149+
# only the direct targets.
150+
if not include_imports:
151+
input_src_files = target_src_files
152+
129153
mypypath_parts += [src_f.dirname for src_f in stub_files]
130154
mypypath = ":".join(mypypath_parts)
131155

@@ -149,34 +173,43 @@ def _mypy_rule_impl(ctx, is_aspect = False):
149173
# Compose a list of the files needed for use. Note that aspect rules can use
150174
# the project version of mypy however, other rules should fall back on their
151175
# relative runfiles.
152-
runfiles = ctx.runfiles(files = src_files + stub_files + [mypy_config_file])
176+
runfiles = ctx.runfiles(files = input_src_files + stub_files + [mypy_config_file])
153177
if not is_aspect:
154178
runfiles = runfiles.merge(ctx.attr._mypy_cli.default_runfiles)
155179

156180
src_root_paths = sets.to_list(
157-
sets.make([f.root.path for f in src_files]),
181+
sets.make([f.root.path for f in input_src_files]),
158182
)
159183

184+
follow_imports = ""
185+
if include_imports:
186+
# --follow-imports=silent is passed in order to suppress errors on
187+
# non-target (imported) libraries.
188+
# 0.810 has a --exclude flag which may work better:
189+
# https://github.com/python/mypy/pull/9992
190+
follow_imports = "--follow-imports=silent"
191+
160192
ctx.actions.expand_template(
161193
template = ctx.file._template,
162194
output = exe,
163195
substitutions = {
164196
"{MYPY_EXE}": ctx.executable._mypy_cli.path,
165197
"{MYPY_ROOT}": ctx.executable._mypy_cli.root.path,
166-
"{CACHE_MAP_TRIPLES}": " ".join(_sources_to_cache_map_triples(src_files, is_aspect)),
198+
"{CACHE_MAP_TRIPLES}": " ".join(_sources_to_cache_map_triples(input_src_files, is_aspect)),
167199
"{PACKAGE_ROOTS}": " ".join([
168200
"--package-root " + shell.quote(path or ".")
169201
for path in src_root_paths
170202
]),
171203
"{SRCS}": " ".join([
172204
shell.quote(f.path) if is_aspect else shell.quote(f.short_path)
173-
for f in src_files
205+
for f in target_src_files
174206
]),
175207
"{VERBOSE_OPT}": "--verbose" if DEBUG else "",
176208
"{VERBOSE_BASH}": "set -x" if DEBUG else "",
177209
"{OUTPUT}": out.path if out else "",
178210
"{MYPYPATH_PATH}": mypypath if mypypath else "",
179211
"{MYPY_INI_PATH}": mypy_config_file.path,
212+
"{FOLLOW_IMPORTS}": follow_imports,
180213
},
181214
is_executable = True,
182215
)
@@ -234,5 +267,9 @@ mypy_test = rule(
234267
implementation = _mypy_test_impl,
235268
test = True,
236269
attrs = dict(DEFAULT_ATTRS.items() +
237-
[("deps", attr.label_list(aspects = [mypy_aspect]))]),
270+
[("deps", attr.label_list(aspects = [mypy_aspect])),
271+
("include_imports",
272+
attr.bool(doc = "Set to true to include imported Python files for mypy. This is required for use with pip `requirement()` rules.")),
273+
]
274+
),
238275
)

templates/mypy.sh.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ main() {
2727
fi
2828

2929
set +o errexit
30-
output=$($mypy {VERBOSE_OPT} --bazel {PACKAGE_ROOTS} --config-file {MYPY_INI_PATH} --cache-map {CACHE_MAP_TRIPLES} -- {SRCS} 2>&1)
30+
output=$($mypy {VERBOSE_OPT} --bazel {PACKAGE_ROOTS} --config-file {MYPY_INI_PATH} --cache-map {CACHE_MAP_TRIPLES} {FOLLOW_IMPORTS} -- {SRCS} 2>&1)
3131
status=$?
3232
set -o errexit
3333

0 commit comments

Comments
 (0)