@@ -65,12 +65,21 @@ def _extract_srcs(srcs):
65
65
direct_src_files .append (f )
66
66
return direct_src_files
67
67
68
- def _extract_transitive_deps (deps ):
68
+ def _extract_transitive_deps (deps , include_imports ):
69
69
transitive_deps = []
70
+ transitive_imports = []
71
+ seen_imports = {} # No sets in Starlark, so use a dict.
70
72
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
74
83
75
84
def _extract_stub_deps (deps ):
76
85
# Need to add the .py files AND the .pyi files that are
@@ -110,22 +119,37 @@ def _mypy_rule_impl(ctx, is_aspect = False):
110
119
transitive_srcs_depsets = []
111
120
stub_files = []
112
121
122
+ include_imports = hasattr (base_rule .attr , "include_imports" ) and base_rule .attr .include_imports
123
+
113
124
if hasattr (base_rule .attr , "srcs" ):
114
125
direct_src_files = _extract_srcs (base_rule .attr .srcs )
115
126
116
127
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 )
118
129
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 ]
119
137
120
138
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 )
122
140
123
141
final_srcs_depset = depset (transitive = transitive_srcs_depsets +
124
142
[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 :
127
146
return None
128
147
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
+
129
153
mypypath_parts += [src_f .dirname for src_f in stub_files ]
130
154
mypypath = ":" .join (mypypath_parts )
131
155
@@ -149,34 +173,43 @@ def _mypy_rule_impl(ctx, is_aspect = False):
149
173
# Compose a list of the files needed for use. Note that aspect rules can use
150
174
# the project version of mypy however, other rules should fall back on their
151
175
# 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 ])
153
177
if not is_aspect :
154
178
runfiles = runfiles .merge (ctx .attr ._mypy_cli .default_runfiles )
155
179
156
180
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 ]),
158
182
)
159
183
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
+
160
192
ctx .actions .expand_template (
161
193
template = ctx .file ._template ,
162
194
output = exe ,
163
195
substitutions = {
164
196
"{MYPY_EXE}" : ctx .executable ._mypy_cli .path ,
165
197
"{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 )),
167
199
"{PACKAGE_ROOTS}" : " " .join ([
168
200
"--package-root " + shell .quote (path or "." )
169
201
for path in src_root_paths
170
202
]),
171
203
"{SRCS}" : " " .join ([
172
204
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
174
206
]),
175
207
"{VERBOSE_OPT}" : "--verbose" if DEBUG else "" ,
176
208
"{VERBOSE_BASH}" : "set -x" if DEBUG else "" ,
177
209
"{OUTPUT}" : out .path if out else "" ,
178
210
"{MYPYPATH_PATH}" : mypypath if mypypath else "" ,
179
211
"{MYPY_INI_PATH}" : mypy_config_file .path ,
212
+ "{FOLLOW_IMPORTS}" : follow_imports ,
180
213
},
181
214
is_executable = True ,
182
215
)
@@ -234,5 +267,9 @@ mypy_test = rule(
234
267
implementation = _mypy_test_impl ,
235
268
test = True ,
236
269
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
+ ),
238
275
)
0 commit comments