diff --git a/python/private/pypi/whl_installer/wheel_installer.py b/python/private/pypi/whl_installer/wheel_installer.py index a6a9dd0429..aae24ff4c7 100644 --- a/python/private/pypi/whl_installer/wheel_installer.py +++ b/python/private/pypi/whl_installer/wheel_installer.py @@ -96,7 +96,17 @@ def _extract_wheel( whl = wheel.Wheel(wheel_file) whl.unzip(installation_dir) + if enable_pipstar: + return + + extras_requested = extras[whl.name] if whl.name in extras else set() + dependencies = whl.dependencies(extras_requested, platforms) + metadata = { + "name": whl.name, + "version": whl.version, + "deps": dependencies.deps, + "deps_by_platform": dependencies.deps_select, "entry_points": [ { "name": name, @@ -106,18 +116,6 @@ def _extract_wheel( for name, (module, attribute) in sorted(whl.entry_points().items()) ], } - if not enable_pipstar: - extras_requested = extras[whl.name] if whl.name in extras else set() - dependencies = whl.dependencies(extras_requested, platforms) - - metadata.update( - { - "name": whl.name, - "version": whl.version, - "deps": dependencies.deps, - "deps_by_platform": dependencies.deps_select, - } - ) with open(os.path.join(installation_dir, "metadata.json"), "w") as f: json.dump(metadata, f) diff --git a/python/private/pypi/whl_library.bzl b/python/private/pypi/whl_library.bzl index fe3308ad3c..65888a4b4f 100644 --- a/python/private/pypi/whl_library.bzl +++ b/python/private/pypi/whl_library.bzl @@ -391,18 +391,21 @@ def _whl_library_impl(rctx): logger = logger, ) - metadata = json.decode(rctx.read("metadata.json")) - rctx.delete("metadata.json") + metadata = whl_metadata( + install_dir = whl_path.dirname.get_child("site-packages"), + read_fn = rctx.read, + logger = logger, + ) # NOTE @aignas 2024-06-22: this has to live on until we stop supporting # passing `twine` as a `:pkg` library via the `WORKSPACE` builds. # # See ../../packaging.bzl line 190 entry_points = {} - for item in metadata["entry_points"]: - name = item["name"] - module = item["module"] - attribute = item["attribute"] + for item in metadata.entry_points: + name = item.name + module = item.module + attribute = item.attribute # There is an extreme edge-case with entry_points that end with `.py` # 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): ) entry_points[entry_point_without_py] = entry_point_script_name - metadata = whl_metadata( - install_dir = whl_path.dirname.get_child("site-packages"), - read_fn = rctx.read, - logger = logger, - ) - build_file_contents = generate_whl_library_build_bazel( name = whl_path.basename, sdist_filename = sdist_filename, diff --git a/python/private/pypi/whl_metadata.bzl b/python/private/pypi/whl_metadata.bzl index a56aac5782..0d3a14ab54 100644 --- a/python/private/pypi/whl_metadata.bzl +++ b/python/private/pypi/whl_metadata.bzl @@ -4,6 +4,7 @@ _NAME = "Name: " _PROVIDES_EXTRA = "Provides-Extra: " _REQUIRES_DIST = "Requires-Dist: " _VERSION = "Version: " +_CONSOLE_SCRIPTS = "[console_scripts]" def whl_metadata(*, install_dir, read_fn, logger): """Find and parse the METADATA file in the extracted whl contents dir. @@ -23,7 +24,13 @@ def whl_metadata(*, install_dir, read_fn, logger): """ metadata_file = find_whl_metadata(install_dir = install_dir, logger = logger) contents = read_fn(metadata_file) - result = parse_whl_metadata(contents) + entry_points_file = metadata_file.dirname.get_child("entry_points.txt") + if entry_points_file.exists: + entry_points_contents = read_fn(entry_points_file) + else: + entry_points_contents = "" + + result = parse_whl_metadata(contents, entry_points_contents) if not (result.name and result.version): 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): return result -def parse_whl_metadata(contents): +def parse_whl_metadata(contents, entry_points_contents = ""): """Parse .whl METADATA file Args: contents: {type}`str` the contents of the file. + entry_points_contents: {type}`str` the contents of the `entry_points.txt` file if it exists. Returns: A struct with parsed values: @@ -48,6 +56,8 @@ def parse_whl_metadata(contents): * `requires_dist`: {type}`list[str]` the list of requirements. * `provides_extra`: {type}`list[str]` the list of extras that this package provides. + * `entry_points`: {type}`list[struct]` the list of + entry_point metadata. """ parsed = { "name": "", @@ -79,6 +89,7 @@ def parse_whl_metadata(contents): provides_extra = parsed["provides_extra"], requires_dist = parsed["requires_dist"], version = parsed["version"], + entry_points = _parse_entry_points(entry_points_contents), ) def find_whl_metadata(*, install_dir, logger): @@ -110,3 +121,47 @@ def find_whl_metadata(*, install_dir, logger): else: logger.fail("The '*.dist-info' directory could not be found in '{}'".format(install_dir.basename)) return None + +def _parse_entry_points(contents): + """parse the entry_points.txt file. + + Args: + contents: {type}`str` The contents of the file + + Returns: + A list of console_script entry point metadata. + """ + start = False + ret = [] + for line in contents.split("\n"): + line = line.rstrip() + + if line == _CONSOLE_SCRIPTS: + start = True + continue + + if not start: + continue + + if start and line.startswith("["): + break + + line, _, _comment = line.partition("#") + line = line.strip() + if not line: + continue + + name, _, tail = line.partition("=") + + # importable.module:object.attr + py_import, _, extras = tail.strip().partition(" ") + module, _, attribute = py_import.partition(":") + + ret.append(struct( + name = name.strip(), + module = module.strip(), + attribute = attribute.strip(), + extras = extras.replace(" ", ""), + )) + + return ret diff --git a/tests/pypi/whl_metadata/whl_metadata_tests.bzl b/tests/pypi/whl_metadata/whl_metadata_tests.bzl index 329423a26c..1d78611901 100644 --- a/tests/pypi/whl_metadata/whl_metadata_tests.bzl +++ b/tests/pypi/whl_metadata/whl_metadata_tests.bzl @@ -81,12 +81,14 @@ def _parse_whl_metadata(env, **kwargs): version = result.version, requires_dist = result.requires_dist, provides_extra = result.provides_extra, + entry_points = result.entry_points, ), attrs = dict( name = subjects.str, version = subjects.str, requires_dist = subjects.collection, provides_extra = subjects.collection, + entry_points = subjects.collection, ), ) @@ -171,6 +173,53 @@ Requires-Dist: this will be ignored _tests.append(_test_parse_metadata_multiline_license) +def _test_parse_entry_points_txt(env): + got = _parse_whl_metadata( + env, + contents = """\ +Name: foo +Version: 0.0.1 +""", + entry_points_contents = """\ +[something] +interesting # with comments + +[console_scripts] +foo = foomod:main +# One which depends on extras: +foobar = importable.foomod:main_bar [bar, baz] + + # With a comment at the end +foobarbaz = foomod:main.attr # comment + +[something else] +not very much interesting + +""", + ) + got.entry_points().contains_exactly([ + struct( + attribute = "main", + extras = "", + module = "foomod", + name = "foo", + ), + struct( + attribute = "main_bar", + extras = "[bar,baz]", + module = "importable.foomod", + name = "foobar", + ), + struct( + attribute = "main.attr", + extras = "", + module = "foomod", + name = "foobarbaz", + ), + ]) + +_tests.append(_test_parse_entry_points_txt) + def whl_metadata_test_suite(name): # buildifier: disable=function-docstring test_suite( name = name,