fix: Handle relative paths properly in _absolute_url (#2153)
This updates the simpleapi parser to handle indexes where wheel and
sdist may be an index_url relative path. It also organises the
conditionals with fewer negations so they're easier to read
Fixes: #2150
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e59564..59dfbe4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@
* (gazelle): Fix incorrect use of `t.Fatal`/`t.Fatalf` in tests.
* (toolchain) Omit third-party python packages from coverage reports from
stage2 bootstrap template.
+* (bzlmod) Properly handle relative path URLs in parse_simpleapi_html.bzl
### Added
* Nothing yet
diff --git a/examples/bzlmod/MODULE.bazel.lock b/examples/bzlmod/MODULE.bazel.lock
index 4deef01..8d02256 100644
--- a/examples/bzlmod/MODULE.bazel.lock
+++ b/examples/bzlmod/MODULE.bazel.lock
@@ -1231,7 +1231,7 @@
},
"@@rules_python~//python/extensions:pip.bzl%pip": {
"general": {
- "bzlTransitiveDigest": "eWzi4IV0AzQ+cpVkMkdU/wOc7BUQdo0hAAQcCh8C4uU=",
+ "bzlTransitiveDigest": "9hiLCuWaaaU7Q+l2ONVr1A0NcG1JfSihv1UYeA1SpNY=",
"usagesDigest": "MChlcSw99EuW3K7OOoMcXQIdcJnEh6YmfyjJm+9mxIg=",
"recordedFileInputs": {
"@@other_module~//requirements_lock_3_11.txt": "a7d0061366569043d5efcf80e34a32c732679367cb3c831c4cdc606adc36d314",
@@ -6140,7 +6140,7 @@
},
"@@rules_python~//python/private/pypi:pip.bzl%pip_internal": {
"general": {
- "bzlTransitiveDigest": "RyEJxfGmNQVzqInjjGrR29yqfFPKe9DKgODI1mxd8wA=",
+ "bzlTransitiveDigest": "VoK/T0JkBdcomCHnDIYkX+stkywdxrh1MVM16e8D4sE=",
"usagesDigest": "Y8ihY+R57BAFhalrVLVdJFrpwlbsiKz9JPJ99ljF7HA=",
"recordedFileInputs": {
"@@rules_python~//tools/publish/requirements.txt": "031e35d03dde03ae6305fe4b3d1f58ad7bdad857379752deede0f93649991b8a",
diff --git a/python/private/pypi/parse_simpleapi_html.bzl b/python/private/pypi/parse_simpleapi_html.bzl
index 81ee385..b4e7dd8 100644
--- a/python/private/pypi/parse_simpleapi_html.bzl
+++ b/python/private/pypi/parse_simpleapi_html.bzl
@@ -78,7 +78,7 @@
url = _absolute_url(url, dist_url),
sha256 = sha256,
metadata_sha256 = metadata_sha256,
- metadata_url = _absolute_url(url, metadata_url),
+ metadata_url = _absolute_url(url, metadata_url) if metadata_url else "",
yanked = yanked,
)
else:
@@ -109,18 +109,33 @@
return "{}://{}".format(scheme, host)
+def _is_downloadable(url):
+ """Checks if the URL would be accepted by the Bazel downloader.
+
+ This is based on Bazel's HttpUtils::isUrlSupportedByDownloader
+ """
+ return url.startswith("http://") or url.startswith("https://") or url.startswith("file://")
+
def _absolute_url(index_url, candidate):
+ if candidate == "":
+ return candidate
+
+ if _is_downloadable(candidate):
+ return candidate
+
if candidate.startswith("/"):
- # absolute url
+ # absolute path
root_directory = _get_root_directory(index_url)
return "{}{}".format(root_directory, candidate)
- if not candidate.startswith(".."):
- return candidate
+ if candidate.startswith(".."):
+ # relative path with up references
+ candidate_parts = candidate.split("..")
+ last = candidate_parts[-1]
+ for _ in range(len(candidate_parts) - 1):
+ index_url, _, _ = index_url.rstrip("/").rpartition("/")
- candidate_parts = candidate.split("..")
- last = candidate_parts[-1]
- for _ in range(len(candidate_parts) - 1):
- index_url, _, _ = index_url.rstrip("/").rpartition("/")
+ return "{}/{}".format(index_url, last.strip("/"))
- return "{}/{}".format(index_url, last.strip("/"))
+ # relative path without up-references
+ return "{}/{}".format(index_url, candidate)
diff --git a/tests/pypi/parse_simpleapi_html/parse_simpleapi_html_tests.bzl b/tests/pypi/parse_simpleapi_html/parse_simpleapi_html_tests.bzl
index aa735b8..d3c42a8 100644
--- a/tests/pypi/parse_simpleapi_html/parse_simpleapi_html_tests.bzl
+++ b/tests/pypi/parse_simpleapi_html/parse_simpleapi_html_tests.bzl
@@ -255,6 +255,40 @@
yanked = False,
),
),
+ (
+ struct(
+ attrs = [
+ 'href="1.0.0/mypy_extensions-1.0.0-py3-none-any.whl#sha256=deadbeef"',
+ ],
+ filename = "mypy_extensions-1.0.0-py3-none-any.whl",
+ url = "https://example.org/simple/mypy_extensions",
+ ),
+ struct(
+ filename = "mypy_extensions-1.0.0-py3-none-any.whl",
+ metadata_sha256 = "",
+ metadata_url = "",
+ sha256 = "deadbeef",
+ url = "https://example.org/simple/mypy_extensions/1.0.0/mypy_extensions-1.0.0-py3-none-any.whl",
+ yanked = False,
+ ),
+ ),
+ (
+ struct(
+ attrs = [
+ 'href="unknown://example.com/mypy_extensions-1.0.0-py3-none-any.whl#sha256=deadbeef"',
+ ],
+ filename = "mypy_extensions-1.0.0-py3-none-any.whl",
+ url = "https://example.org/simple/mypy_extensions",
+ ),
+ struct(
+ filename = "mypy_extensions-1.0.0-py3-none-any.whl",
+ metadata_sha256 = "",
+ metadata_url = "",
+ sha256 = "deadbeef",
+ url = "https://example.org/simple/mypy_extensions/unknown://example.com/mypy_extensions-1.0.0-py3-none-any.whl",
+ yanked = False,
+ ),
+ ),
]
for (input, want) in tests: