Skip to content

Commit 6461690

Browse files
committed
feat(pipstar): parse entry_points without Python
This is a small utility function to get us Python free when wheels are extracted in the repository phase. Remaining bits are: - [ ] Extract the wheel using `repository_ctx.extract`. - [ ] Patch the wheel after extracting.
1 parent 5bc7ba3 commit 6461690

File tree

4 files changed

+123
-26
lines changed

4 files changed

+123
-26
lines changed

python/private/pypi/whl_installer/wheel_installer.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,17 @@ def _extract_wheel(
9696
whl = wheel.Wheel(wheel_file)
9797
whl.unzip(installation_dir)
9898

99+
if enable_pipstar:
100+
return
101+
102+
extras_requested = extras[whl.name] if whl.name in extras else set()
103+
dependencies = whl.dependencies(extras_requested, platforms)
104+
99105
metadata = {
106+
"name": whl.name,
107+
"version": whl.version,
108+
"deps": dependencies.deps,
109+
"deps_by_platform": dependencies.deps_select,
100110
"entry_points": [
101111
{
102112
"name": name,
@@ -106,18 +116,6 @@ def _extract_wheel(
106116
for name, (module, attribute) in sorted(whl.entry_points().items())
107117
],
108118
}
109-
if not enable_pipstar:
110-
extras_requested = extras[whl.name] if whl.name in extras else set()
111-
dependencies = whl.dependencies(extras_requested, platforms)
112-
113-
metadata.update(
114-
{
115-
"name": whl.name,
116-
"version": whl.version,
117-
"deps": dependencies.deps,
118-
"deps_by_platform": dependencies.deps_select,
119-
}
120-
)
121119

122120
with open(os.path.join(installation_dir, "metadata.json"), "w") as f:
123121
json.dump(metadata, f)

python/private/pypi/whl_library.bzl

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -391,18 +391,21 @@ def _whl_library_impl(rctx):
391391
logger = logger,
392392
)
393393

394-
metadata = json.decode(rctx.read("metadata.json"))
395-
rctx.delete("metadata.json")
394+
metadata = whl_metadata(
395+
install_dir = whl_path.dirname.get_child("site-packages"),
396+
read_fn = rctx.read,
397+
logger = logger,
398+
)
396399

397400
# NOTE @aignas 2024-06-22: this has to live on until we stop supporting
398401
# passing `twine` as a `:pkg` library via the `WORKSPACE` builds.
399402
#
400403
# See ../../packaging.bzl line 190
401404
entry_points = {}
402-
for item in metadata["entry_points"]:
403-
name = item["name"]
404-
module = item["module"]
405-
attribute = item["attribute"]
405+
for item in metadata.entry_points:
406+
name = item.name
407+
module = item.module
408+
attribute = item.attribute
406409

407410
# There is an extreme edge-case with entry_points that end with `.py`
408411
# See: https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/test/java/com/google/devtools/build/lib/rules/python/PyBinaryConfiguredTargetTest.java#L174
@@ -418,12 +421,6 @@ def _whl_library_impl(rctx):
418421
)
419422
entry_points[entry_point_without_py] = entry_point_script_name
420423

421-
metadata = whl_metadata(
422-
install_dir = whl_path.dirname.get_child("site-packages"),
423-
read_fn = rctx.read,
424-
logger = logger,
425-
)
426-
427424
build_file_contents = generate_whl_library_build_bazel(
428425
name = whl_path.basename,
429426
sdist_filename = sdist_filename,

python/private/pypi/whl_metadata.bzl

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ _NAME = "Name: "
44
_PROVIDES_EXTRA = "Provides-Extra: "
55
_REQUIRES_DIST = "Requires-Dist: "
66
_VERSION = "Version: "
7+
_CONSOLE_SCRIPTS = "[console_scripts]"
78

89
def whl_metadata(*, install_dir, read_fn, logger):
910
"""Find and parse the METADATA file in the extracted whl contents dir.
@@ -23,7 +24,13 @@ def whl_metadata(*, install_dir, read_fn, logger):
2324
"""
2425
metadata_file = find_whl_metadata(install_dir = install_dir, logger = logger)
2526
contents = read_fn(metadata_file)
26-
result = parse_whl_metadata(contents)
27+
entry_points_file = metadata_file.dirname.get_child("entry_points.txt")
28+
if entry_points_file.exists:
29+
entry_points_contents = read_fn(entry_points_file)
30+
else:
31+
entry_points_contents = ""
32+
33+
result = parse_whl_metadata(contents, entry_points_contents)
2734

2835
if not (result.name and result.version):
2936
logger.fail("Failed to parse the wheel METADATA file:\n{}\n{}\n{}".format(
@@ -35,11 +42,12 @@ def whl_metadata(*, install_dir, read_fn, logger):
3542

3643
return result
3744

38-
def parse_whl_metadata(contents):
45+
def parse_whl_metadata(contents, entry_points_contents = ""):
3946
"""Parse .whl METADATA file
4047
4148
Args:
4249
contents: {type}`str` the contents of the file.
50+
entry_points_contents: {type}`str` the contents of the `entry_points.txt` file if it exists.
4351
4452
Returns:
4553
A struct with parsed values:
@@ -48,6 +56,8 @@ def parse_whl_metadata(contents):
4856
* `requires_dist`: {type}`list[str]` the list of requirements.
4957
* `provides_extra`: {type}`list[str]` the list of extras that this package
5058
provides.
59+
* `entry_points`: {type}`list[dict[str, str]]` the list of
60+
entry_point metadata.
5161
"""
5262
parsed = {
5363
"name": "",
@@ -79,6 +89,7 @@ def parse_whl_metadata(contents):
7989
provides_extra = parsed["provides_extra"],
8090
requires_dist = parsed["requires_dist"],
8191
version = parsed["version"],
92+
entry_points = _parse_entry_points(entry_points_contents),
8293
)
8394

8495
def find_whl_metadata(*, install_dir, logger):
@@ -110,3 +121,46 @@ def find_whl_metadata(*, install_dir, logger):
110121
else:
111122
logger.fail("The '*.dist-info' directory could not be found in '{}'".format(install_dir.basename))
112123
return None
124+
125+
def _parse_entry_points(contents):
126+
"""parse the entry_points.txt file.
127+
128+
Args:
129+
contents: {type}`str` The contents of the file
130+
131+
Returns:
132+
A list of console_script entry point metadata.
133+
"""
134+
start = False
135+
ret = []
136+
for line in contents.split("\n"):
137+
line = line.rstrip()
138+
139+
if line == _CONSOLE_SCRIPTS:
140+
start = True
141+
continue
142+
143+
if not start:
144+
continue
145+
146+
if start and not line:
147+
break
148+
149+
line, _, _comment = line.partition("#")
150+
if not line:
151+
continue
152+
153+
name, _, tail = line.partition("=")
154+
155+
# importable.module:object.attr
156+
py_import, _, extras = tail.strip().partition(" ")
157+
module, _, attribute = py_import.partition(":")
158+
159+
ret.append(struct(
160+
name = name.strip(),
161+
module = module.strip(),
162+
attribute = attribute.strip(),
163+
extras = extras.replace(" ", ""),
164+
))
165+
166+
return ret

tests/pypi/whl_metadata/whl_metadata_tests.bzl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@ def _parse_whl_metadata(env, **kwargs):
8181
version = result.version,
8282
requires_dist = result.requires_dist,
8383
provides_extra = result.provides_extra,
84+
entry_points = result.entry_points,
8485
),
8586
attrs = dict(
8687
name = subjects.str,
8788
version = subjects.str,
8889
requires_dist = subjects.collection,
8990
provides_extra = subjects.collection,
91+
entry_points = subjects.collection,
9092
),
9193
)
9294

@@ -171,6 +173,52 @@ Requires-Dist: this will be ignored
171173

172174
_tests.append(_test_parse_metadata_multiline_license)
173175

176+
def _test_parse_entry_points_txt(env):
177+
got = _parse_whl_metadata(
178+
env,
179+
contents = """\
180+
Name: foo
181+
Version: 0.0.1
182+
""",
183+
entry_points_contents = """\
184+
[something]
185+
interesting # with comments
186+
187+
[console_scripts]
188+
foo = foomod:main
189+
# One which depends on extras:
190+
foobar = importable.foomod:main_bar [bar, baz]
191+
# With a comment at the end
192+
foobarbaz = foomod:main.attr # comment
193+
194+
[something else]
195+
not very much interesting
196+
197+
""",
198+
)
199+
got.entry_points().contains_exactly([
200+
struct(
201+
attribute = "main",
202+
extras = "",
203+
module = "foomod",
204+
name = "foo",
205+
),
206+
struct(
207+
attribute = "main_bar",
208+
extras = "[bar,baz]",
209+
module = "importable.foomod",
210+
name = "foobar",
211+
),
212+
struct(
213+
attribute = "main.attr",
214+
extras = "",
215+
module = "foomod",
216+
name = "foobarbaz",
217+
),
218+
])
219+
220+
_tests.append(_test_parse_entry_points_txt)
221+
174222
def whl_metadata_test_suite(name): # buildifier: disable=function-docstring
175223
test_suite(
176224
name = name,

0 commit comments

Comments
 (0)