Skip to content

Commit 920f5ad

Browse files
committed
Support source-less wheels with dependencies
`rules_python` `2.3.x` wraps source-less wheels in a `py_library` whose `srcs` target has no Python files. Forward the target only when it has valid sources, and cover the wrapper behavior with unit tests.
1 parent 6ba12ce commit 920f5ad

3 files changed

Lines changed: 147 additions & 8 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(pypi) Fixed analysis failures for source-less wheels with dependencies in
2+
per-wheel repositories.

python/private/pypi/whl_library_targets.bzl

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def whl_library_targets(
101101
**kwargs: Extra args passed to the {obj}`whl_library_deps_targets` and {obj}`whl_library_srcs`.
102102
"""
103103
create_extra_targets = bool(requires_dist or group_name) and dep_template
104-
whl_library_srcs(
104+
wrapper_srcs = whl_library_srcs(
105105
name = name,
106106
sdist_filename = sdist_filename,
107107
data_exclude = data_exclude,
@@ -133,6 +133,7 @@ def whl_library_targets(
133133
dep_template = dep_template, # only needed if requires_dist is present
134134
repo = None, # set aliases in the same repo
135135
aliases = {},
136+
srcs = wrapper_srcs,
136137
**kwargs
137138
)
138139

@@ -193,9 +194,14 @@ def whl_library_srcs(
193194
pkg_name: {type}`str` The label name to use for the py_library target.
194195
native: {type}`native` The native struct for overriding in tests.
195196
rules: {type}`struct` A struct with references to rules for creating targets.
197+
198+
Returns:
199+
The source labels attached to the generated `py_library`, or `None` when
200+
`rules` does not define `py_library`.
196201
"""
197202
tags = sorted(tags)
198203
data = [] + data
204+
wrapper_srcs = None
199205

200206
bins_for_data_label = []
201207

@@ -301,6 +307,7 @@ def whl_library_srcs(
301307
# pure-Python code, e.g. pymssql, which is written in Cython.
302308
allow_empty = True,
303309
)
310+
wrapper_srcs = [pkg_name] if srcs else []
304311

305312
# NOTE: pyi files should probably be excluded because they're carried
306313
# by the pyi_srcs attribute. However, historical behavior included
@@ -328,13 +335,19 @@ def whl_library_srcs(
328335
)
329336

330337
if not enable_implicit_namespace_pkgs:
338+
generated_namespace_package_files = rules.create_inits(
339+
srcs = srcs + data + pyi_srcs,
340+
ignored_dirnames = [], # If you need to ignore certain folders, you can patch rules_python here to do so.
341+
root = "site-packages",
342+
)
343+
if not wrapper_srcs and generated_namespace_package_files:
344+
wrapper_srcs = select({
345+
_IS_VENV_SITE_PACKAGES_YES: [],
346+
"//conditions:default": [pkg_name],
347+
})
331348
generated_namespace_package_files = select({
332349
_IS_VENV_SITE_PACKAGES_YES: [],
333-
"//conditions:default": rules.create_inits(
334-
srcs = srcs + data + pyi_srcs,
335-
ignored_dirnames = [], # If you need to ignore certain folders, you can patch rules_python here to do so.
336-
root = "site-packages",
337-
),
350+
"//conditions:default": generated_namespace_package_files,
338351
})
339352
namespace_package_files += generated_namespace_package_files
340353
srcs = srcs + generated_namespace_package_files
@@ -356,6 +369,7 @@ def whl_library_srcs(
356369
experimental_venvs_site_packages = _VENV_SITE_PACKAGES_FLAG,
357370
namespace_package_files = namespace_package_files,
358371
)
372+
return wrapper_srcs
359373

360374
def whl_library_deps_targets(
361375
*,
@@ -369,6 +383,7 @@ def whl_library_deps_targets(
369383
group_deps = [],
370384
group_name = None,
371385
dep_template,
386+
srcs = None,
372387
tags = [],
373388
visibility = ["//visibility:public"],
374389
native = native,
@@ -396,6 +411,9 @@ def whl_library_deps_targets(
396411
include: {type}`list[str]` The list of packages to include.
397412
group_name: {type}`str | None` name of the dependency group (if any).
398413
dep_template: {type}`str | None` The dep_template to use.
414+
srcs: {type}`list[Label] | None` or a configurable expression with the
415+
source labels attached to the wrapper `py_library`. If `None`, the
416+
source library target is used.
399417
tags: {type}`list[str]` The tags set on the targets.
400418
repo: {type}`str | Label | None` The BUILD.bazel label to the parent repo that has the
401419
sources. If none, then will take the targets from the current dir.
@@ -501,10 +519,13 @@ def whl_library_deps_targets(
501519
)
502520

503521
if hasattr(rules, "py_library"):
522+
if srcs == None:
523+
srcs = [repo_label(PY_SRCS_LABEL)]
504524
rules.py_library(
505525
name = py_library_label,
506-
# We include as srcs to ensure that the (locations :pkg) works as expected.
507-
srcs = [repo_label(PY_SRCS_LABEL)],
526+
# Forward source-producing targets through `srcs` so downstream
527+
# `$(locations :pkg)` expansion does not reject source-less wheels.
528+
srcs = srcs,
508529
deps = _deps(
509530
# We include as deps, so that `PyInfo` and friends (e.g. `pyi_srcs`) get
510531
# propagated. Just passing the target as `srcs` is not enough to propagate

tests/pypi/whl_library_targets/whl_library_targets_tests.bzl

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,46 @@ load(
1919
"//python/private/pypi:whl_library_targets.bzl",
2020
"whl_library_deps_targets",
2121
"whl_library_srcs",
22+
"whl_library_targets",
2223
) # buildifier: disable=bzl-visibility
2324
load("//tests/support/mocks:mocks.bzl", "mocks")
2425

2526
_tests = []
2627

28+
def _make_whl_library_targets_py_library_calls(
29+
*,
30+
srcs,
31+
data,
32+
generated_inits,
33+
enable_implicit_namespace_pkgs = False):
34+
py_library_calls = []
35+
m_glob = mocks.glob()
36+
m_glob.results.append([]) # bin
37+
m_glob.results.append([]) # rewrite-bin
38+
m_glob.results.append([]) # rewrite-record
39+
m_glob.results.append(srcs)
40+
m_glob.results.append(data)
41+
m_glob.results.append([]) # pyi
42+
43+
whl_library_targets(
44+
name = "foo-0-py3-none-any.whl",
45+
metadata_name = "Foo",
46+
requires_dist = ["bar"],
47+
dep_template = "@pypi//{name}:{target}",
48+
enable_implicit_namespace_pkgs = enable_implicit_namespace_pkgs,
49+
filegroups = {},
50+
native = struct(glob = m_glob.glob),
51+
rules = struct(
52+
create_inits = lambda **_: generated_inits,
53+
env_marker_setting = lambda **_: None,
54+
gen_wheel_record = lambda **_: None,
55+
py_library = lambda **kwargs: py_library_calls.append(kwargs),
56+
venv_rewrite_shebang = lambda **_: None,
57+
),
58+
)
59+
60+
return py_library_calls
61+
2762
def _test_filegroups(env):
2863
calls = []
2964

@@ -191,6 +226,87 @@ def _test_whl_library_deps_targets(env):
191226

192227
_tests.append(_test_whl_library_deps_targets)
193228

229+
def _test_whl_library_targets_sourceless(env):
230+
for enable_implicit_namespace_pkgs, expected_leaf_srcs in [
231+
(False, [] + select({
232+
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
233+
"//conditions:default": [],
234+
})),
235+
(True, []),
236+
]:
237+
py_library_calls = _make_whl_library_targets_py_library_calls(
238+
srcs = [],
239+
data = [],
240+
generated_inits = [],
241+
enable_implicit_namespace_pkgs = enable_implicit_namespace_pkgs,
242+
)
243+
244+
env.expect.that_collection(py_library_calls).has_size(2)
245+
if len(py_library_calls) != 2:
246+
return
247+
248+
env.expect.that_dict(py_library_calls[0]).contains_at_least({
249+
"srcs": expected_leaf_srcs,
250+
})
251+
env.expect.that_dict(py_library_calls[1]).contains_exactly({
252+
"name": "pkg",
253+
"srcs": [],
254+
"deps": ["srcs", "@pypi//bar:pkg"],
255+
"tags": [],
256+
"visibility": ["//visibility:public"],
257+
}) # buildifier: @unsorted-dict-items
258+
259+
_tests.append(_test_whl_library_targets_sourceless)
260+
261+
def _test_whl_library_targets_sourceful(env):
262+
for srcs, data, generated_inits, expected_leaf_srcs, expected_wrapper_srcs in [
263+
(
264+
["site-packages/foo.py"],
265+
[],
266+
[],
267+
["site-packages/foo.py"] + select({
268+
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
269+
"//conditions:default": [],
270+
}),
271+
["srcs"],
272+
),
273+
(
274+
[],
275+
["site-packages/ext/mod.so"],
276+
["site-packages/ext/__init__.py"],
277+
[] + select({
278+
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
279+
"//conditions:default": ["site-packages/ext/__init__.py"],
280+
}),
281+
select({
282+
Label("//python/config_settings:_is_venvs_site_packages_yes"): [],
283+
"//conditions:default": ["srcs"],
284+
}),
285+
),
286+
]:
287+
py_library_calls = _make_whl_library_targets_py_library_calls(
288+
srcs = srcs,
289+
data = data,
290+
generated_inits = generated_inits,
291+
)
292+
293+
env.expect.that_collection(py_library_calls).has_size(2)
294+
if len(py_library_calls) != 2:
295+
return
296+
297+
env.expect.that_dict(py_library_calls[0]).contains_at_least({
298+
"srcs": expected_leaf_srcs,
299+
})
300+
env.expect.that_dict(py_library_calls[1]).contains_exactly({
301+
"name": "pkg",
302+
"srcs": expected_wrapper_srcs,
303+
"deps": ["srcs", "@pypi//bar:pkg"],
304+
"tags": [],
305+
"visibility": ["//visibility:public"],
306+
}) # buildifier: @unsorted-dict-items
307+
308+
_tests.append(_test_whl_library_targets_sourceful)
309+
194310
def _test_whl_library_deps_targets_no_deps(env):
195311
alias_calls = []
196312
filegroup_calls = []

0 commit comments

Comments
 (0)