Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bzlmod): support bazel downloader when downloading wheels #1827

Closed
wants to merge 73 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
6720945
feat(bzlmod): introduce `pypi_index` for using bazel's downloader
aignas Mar 8, 2024
927310c
revert: use bazel 7.0.2 in rules_python
aignas Mar 10, 2024
677ed2d
fix(bzlmod): add WORKSPACE.bzlmod and actually use bzlmod locally
aignas Mar 10, 2024
fa1569e
remove WORKSPACE.bzlmod, will add in a separate PR
aignas Mar 10, 2024
8eb2ee5
start cleaning up pypi_index
aignas Mar 11, 2024
11f94c7
add WORKSPACE.bzlmod to distribution
aignas Mar 11, 2024
3efd55c
chore: add rules_testing to MODULE.bazel
aignas Mar 11, 2024
186a2c9
add rules_cc dev dep
aignas Mar 11, 2024
506d838
Add a function for getting shas for simple API packages
aignas Mar 14, 2024
956213a
use a better detection if we should use the index
aignas Mar 14, 2024
cdf0510
Use a better detection if we should use the index
aignas Mar 14, 2024
6651bc9
docs: add pypi_index docs
aignas Mar 18, 2024
7d7abd1
Add CHANGELOG
aignas Mar 18, 2024
fec7abb
Merge branch 'main' into feat/pypi-index-extension
aignas Mar 18, 2024
ec8dbc4
fixup changelog
aignas Mar 18, 2024
6f7a42c
refactor to try a different pip.parse usage of the simple api
aignas Mar 21, 2024
7addb40
use downloader without an extra extension
aignas Mar 21, 2024
0313f75
refactor and cleanup
aignas Mar 22, 2024
307819e
wip
aignas Mar 22, 2024
652d75c
Merge branch 'main' into exp/pip-simpleapi
aignas Mar 22, 2024
6ca8a4b
fixup the cleanup
aignas Mar 22, 2024
d21a29e
cleanup
aignas Mar 26, 2024
affbb32
fixup! cleanup
aignas Mar 26, 2024
2da0b29
fixup! fixup! cleanup
aignas Mar 26, 2024
5ba7881
wip
aignas Mar 28, 2024
bb30b41
wip
aignas Mar 29, 2024
26c678c
Merge branch 'main' into feat/pip-simpleapi
aignas Mar 29, 2024
186af97
more cleanup for the lock file
aignas Mar 29, 2024
88ce003
further cleanup
aignas Mar 29, 2024
41b23af
fixup: add platforms_bzl for pip_bzl
aignas Mar 29, 2024
fcf2700
chore: use rules-python-docs as the docs project name
aignas Mar 29, 2024
b6740f5
Revert "chore: use rules-python-docs as the docs project name"
aignas Mar 29, 2024
3c905ae
add bazel_features to WORKSPACE setup
aignas Mar 29, 2024
459b1ba
correct the host platform import
aignas Mar 29, 2024
095222e
Add an override for testing bazelbuild/platforms#89
aignas Mar 29, 2024
134a8fc
bump bazel version
aignas Mar 29, 2024
a7b92f5
fixup: add the platforms host only under bzlmod
aignas Mar 29, 2024
27307f0
fix(platforms): depend on module_ctx.os for getting host platform
aignas Mar 30, 2024
8fe82ba
comment: revert bazel to 7.0.0
aignas Mar 30, 2024
1d7a063
comment/improvement: pass around the auth attrs around
aignas Mar 30, 2024
0a6b850
comment: rewrite maybe_args loop
aignas Mar 30, 2024
f0eff9c
comment: unroll the loop which is popping args
aignas Mar 30, 2024
097009a
comment: use dict accesors instead of dict.update
aignas Mar 30, 2024
ff3983a
comment: add a note on why we are sorting attrs
aignas Mar 30, 2024
37c3ede
comment: move a comment
aignas Mar 30, 2024
09c724d
comment: adress comments in pypi_index code 1/n
aignas Mar 30, 2024
1331193
comment: improve naming
aignas Mar 30, 2024
02f7e89
fixup! comment: improve naming
aignas Mar 30, 2024
b05094b
simplify yanking behaviour and remove want_shas
aignas Mar 30, 2024
881211a
comment: better error msg
aignas Mar 30, 2024
8fc212c
comment: docs
aignas Mar 30, 2024
b76bd05
comment: use a tuple as a sorting key
aignas Mar 30, 2024
9acf7d3
comment: date
aignas Mar 30, 2024
01eccf5
comment: clarify docs
aignas Mar 30, 2024
e80d37c
comment: improve the doc
aignas Mar 30, 2024
28d40e3
comment: remove todo
aignas Mar 30, 2024
bdfef56
simplify whl library attrs
aignas Mar 30, 2024
0b1de6e
comment: improve error message
aignas Mar 30, 2024
9b0be62
comment: improve error message
aignas Mar 30, 2024
8dc3bdc
comment: improve error msg
aignas Mar 30, 2024
5b6a444
extra docs
aignas Mar 30, 2024
f438e6e
feat: support extra index URLs
aignas Mar 31, 2024
038e45e
test: add a test for HTML parsing
aignas Apr 2, 2024
04953d4
test: add a test for HTML parsing
aignas Apr 2, 2024
02ccc55
finish adding the first test
aignas Apr 2, 2024
b4a9aa8
test: more tests for the pypi html parsing
aignas Apr 2, 2024
d620037
doc: changelog
aignas Apr 2, 2024
f30a5df
feat: support sdists using bazel downloader
aignas Apr 5, 2024
aefd8e2
test: fix tests
aignas Apr 5, 2024
ed4e8ab
further fixups
aignas Apr 5, 2024
5643866
Do not fail on unknown platform tags and just print a warning
aignas Apr 5, 2024
eb67002
Add comments
aignas Apr 5, 2024
d984559
bump rules_testing in non-bzlmod
aignas Apr 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,4 @@ build:rtd --stamp
# Some bzl files contain repos only available under bzlmod
build:rtd --enable_bzlmod

# Disabled due to https://github.com/bazelbuild/bazel/issues/20942
build --lockfile_mode=off
build --lockfile_mode=update
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ A brief description of the categories of changes:

### Changed

* (bzlmod): The `MODULE.bazel.lock` `whl_library` rule attributes are now
sorted in the attributes section. We are also removing values that are not
default in order to reduce the size of the lock file.
* (deps): Bumped bazel_features to 1.9.1 to detect optional support
non-blocking downloads.

### Fixed

* (whl_library): Fix the experimental_target_platforms overriding for platform
Expand All @@ -42,12 +48,15 @@ A brief description of the categories of changes:
* (gazelle) Added a new `python_default_visibility` directive to control the
_default_ visibility of generated targets. See the [docs][python_default_visibility]
for details.

* (wheel) Add support for `data_files` attributes in py_wheel rule
([#1777](https://github.com/bazelbuild/rules_python/issues/1777))

* (py_wheel) `bzlmod` installations now provide a `twine` setup for the default
Python toolchain in `rules_python` for version 3.11.
* (bzlmod) New `experimental_index_url`, `experimental_extra_index_urls` and
`experimental_index_url_overrides` to `pip.parse` for using the bazel
downloader. If you see any issues, report in
[#1357](https://github.com/bazelbuild/rules_python/issues/1357). The URLs for
the whl and sdist files will be written to the lock file.

[0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0
[python_default_visibility]: gazelle/README.md#directive-python_default_visibility
Expand Down
9 changes: 7 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module(
compatibility_level = 1,
)

bazel_dep(name = "bazel_features", version = "1.1.1")
bazel_dep(name = "bazel_features", version = "1.9.1")
aignas marked this conversation as resolved.
Show resolved Hide resolved
bazel_dep(name = "bazel_skylib", version = "1.3.0")
bazel_dep(name = "platforms", version = "0.0.4")

Expand Down Expand Up @@ -58,6 +58,7 @@ register_toolchains("@pythons_hub//:all")

pip = use_extension("//python/extensions:pip.bzl", "pip")
pip.parse(
experimental_index_url = "https://pypi.org/simple",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional not to use PIP_INDEX_URL env here, and why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to override this value you can use bazel downloader config. In the general sense it is not a given that people will have their PIP_INDEX_URL configured to contain the index that works for fetching these dependencies.

Hence to avoid unintended breakage, this is hardcoded.

hub_name = "rules_python_publish_deps",
python_version = "3.11",
requirements_darwin = "//tools/publish:requirements_darwin.txt",
Expand All @@ -69,7 +70,7 @@ use_repo(pip, "rules_python_publish_deps")
# ===== DEV ONLY DEPS AND SETUP BELOW HERE =====
bazel_dep(name = "stardoc", version = "0.6.2", dev_dependency = True, repo_name = "io_bazel_stardoc")
bazel_dep(name = "rules_bazel_integration_test", version = "0.20.0", dev_dependency = True)
bazel_dep(name = "rules_testing", version = "0.5.0", dev_dependency = True)
bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True)
bazel_dep(name = "rules_cc", version = "0.0.9", dev_dependency = True)

# Extra gazelle plugin deps so that WORKSPACE.bzlmod can continue including it for e2e tests.
Expand All @@ -83,6 +84,8 @@ dev_pip = use_extension(
dev_dependency = True,
)
dev_pip.parse(
envsubst = ["PIP_INDEX_URL"],
experimental_index_url = "${PIP_INDEX_URL:-https://pypi.org/simple}",
experimental_requirement_cycles = {
"sphinx": [
"sphinx",
Expand All @@ -98,6 +101,8 @@ dev_pip.parse(
requirements_lock = "//docs/sphinx:requirements.txt",
)
dev_pip.parse(
envsubst = ["PIP_INDEX_URL"],
experimental_index_url = "${PIP_INDEX_URL:-https://pypi.org/simple}",
hub_name = "pypiserver",
python_version = "3.11",
requirements_lock = "//examples/wheel:requirements_server.txt",
Expand Down
14 changes: 14 additions & 0 deletions examples/bzlmod/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ use_repo(pip, "whl_mods_hub")
# Alternatively, `python_interpreter_target` can be used to directly specify
# the Python interpreter to run to resolve dependencies.
pip.parse(
# We can use `envsubst in the above
envsubst = ["PIP_INDEX_URL"],
# Use the bazel downloader to query the simple API for downloading the sources
# Note, that we can use envsubst for this value.
experimental_index_url = "${PIP_INDEX_URL:-https://pypi.org/simple}",
# One can also select a particular index for a particular package.
# This ensures that the setup is resistant against confusion attacks.
# experimental_index_url_overrides = {
# "my_package": "https://different-index-url.com",
# },
# Or you can specify extra indexes like with `pip`:
# experimental_extra_index_urls = [
# "https://different-index-url.com",
# ],
experimental_requirement_cycles = {
"sphinx": [
"sphinx",
Expand Down
22 changes: 10 additions & 12 deletions internal_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,9 @@ def rules_python_internal_deps():

http_archive(
name = "rules_testing",
sha256 = "b84ed8546f1969d700ead4546de9f7637e0f058d835e47e865dcbb13c4210aed",
strip_prefix = "rules_testing-0.5.0",
url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.5.0/rules_testing-v0.5.0.tar.gz",
)

http_archive(
name = "rules_license",
aignas marked this conversation as resolved.
Show resolved Hide resolved
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_license/releases/download/0.0.7/rules_license-0.0.7.tar.gz",
"https://github.com/bazelbuild/rules_license/releases/download/0.0.7/rules_license-0.0.7.tar.gz",
],
sha256 = "4531deccb913639c30e5c7512a054d5d875698daeb75d8cf90f284375fe7c360",
sha256 = "02c62574631876a4e3b02a1820cb51167bb9cdcdea2381b2fa9d9b8b11c407c4",
strip_prefix = "rules_testing-0.6.0",
url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.6.0/rules_testing-v0.6.0.tar.gz",
)

http_archive(
Expand Down Expand Up @@ -221,3 +212,10 @@ def rules_python_internal_deps():
],
sha256 = "4531deccb913639c30e5c7512a054d5d875698daeb75d8cf90f284375fe7c360",
)

http_archive(
name = "bazel_features",
sha256 = "d7787da289a7fb497352211ad200ec9f698822a9e0757a4976fd9f713ff372b3",
strip_prefix = "bazel_features-1.9.1",
url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.9.1/bazel_features-v1.9.1.tar.gz",
)
2 changes: 2 additions & 0 deletions internal_setup.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Setup for rules_python tests and tools."""

load("@bazel_features//:deps.bzl", "bazel_features_deps")
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies")
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
Expand Down Expand Up @@ -42,3 +43,4 @@ def rules_python_internal_setup():
bazel_integration_test_rules_dependencies()
bazel_starlib_dependencies()
bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS)
bazel_features_deps()
96 changes: 76 additions & 20 deletions python/pip_install/pip_repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ load("//python/pip_install:requirements_parser.bzl", parse_requirements = "parse
load("//python/pip_install/private:generate_group_library_build_bazel.bzl", "generate_group_library_build_bazel")
load("//python/pip_install/private:generate_whl_library_build_bazel.bzl", "generate_whl_library_build_bazel")
load("//python/pip_install/private:srcs.bzl", "PIP_INSTALL_PY_SRCS")
load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")
load("//python/private:envsubst.bzl", "envsubst")
load("//python/private:normalize_name.bzl", "normalize_name")
load("//python/private:parse_whl_name.bzl", "parse_whl_name")
Expand Down Expand Up @@ -187,7 +188,7 @@ def use_isolated(ctx, attr):

return use_isolated

def _parse_optional_attrs(rctx, args):
def _parse_optional_attrs(rctx, args, extra_pip_args = None):
"""Helper function to parse common attributes of pip_repository and whl_library repository rules.

This function also serializes the structured arguments as JSON
Expand All @@ -196,6 +197,7 @@ def _parse_optional_attrs(rctx, args):
Args:
rctx: Handle to the rule repository context.
args: A list of parsed args for the rule.
extra_pip_args: The pip args to pass.
Returns: Augmented args list.
"""

Expand All @@ -212,7 +214,7 @@ def _parse_optional_attrs(rctx, args):

# Check for None so we use empty default types from our attrs.
# Some args want to be list, and some want to be dict.
if rctx.attr.extra_pip_args != None:
if extra_pip_args != None:
args += [
"--extra_pip_args",
json.encode(struct(arg = [
Expand Down Expand Up @@ -759,24 +761,64 @@ def _whl_library_impl(rctx):
"--requirement",
rctx.attr.requirement,
]

args = _parse_optional_attrs(rctx, args)
extra_pip_args = []
extra_pip_args.extend(rctx.attr.extra_pip_args)

# Manually construct the PYTHONPATH since we cannot use the toolchain here
environment = _create_repository_execution_environment(rctx, python_interpreter)

repo_utils.execute_checked(
rctx,
op = "whl_library.ResolveRequirement({}, {})".format(rctx.attr.name, rctx.attr.requirement),
arguments = args,
environment = environment,
quiet = rctx.attr.quiet,
timeout = rctx.attr.timeout,
)
whl_path = None
if rctx.attr.whl_file:
whl_path = rctx.path(rctx.attr.whl_file)

# Simulate the behaviour where the whl is present in the current directory.
rctx.symlink(whl_path, whl_path.basename)
whl_path = rctx.path(whl_path.basename)
elif rctx.attr.urls:
filename = rctx.attr.filename
urls = rctx.attr.urls
if not filename:
_, _, filename = urls[0].rpartition("/")

if not (filename.endswith(".whl") or filename.endswith("tar.gz") or filename.endswith(".zip")):
if rctx.attr.filename:
msg = "got '{}'".format(filename)
else:
msg = "detected '{}' from url:\n{}".format(filename, urls[0])
fail("Only '.whl', '.tar.gz' or '.zip' files are supported, {}".format(msg))

result = rctx.download(
url = urls,
output = filename,
sha256 = rctx.attr.sha256,
auth = get_auth(rctx, urls),
)

if not result.success:
fail("could not download the '{}' from {}:\n{}".format(filename, urls, result))

if filename.endswith(".whl"):
whl_path = rctx.path(rctx.attr.filename)
else:
# It is an sdist and we need to tell PyPI to use a file in this directory
# and not use any indexes.
extra_pip_args.extend(["--no-index", "--find-links", "."])

args = _parse_optional_attrs(rctx, args, extra_pip_args)

whl_path = rctx.path(json.decode(rctx.read("whl_file.json"))["whl_file"])
if not rctx.delete("whl_file.json"):
fail("failed to delete the whl_file.json file")
if not whl_path:
repo_utils.execute_checked(
rctx,
op = "whl_library.ResolveRequirement({}, {})".format(rctx.attr.name, rctx.attr.requirement),
arguments = args,
environment = environment,
quiet = rctx.attr.quiet,
timeout = rctx.attr.timeout,
)

whl_path = rctx.path(json.decode(rctx.read("whl_file.json"))["whl_file"])
if not rctx.delete("whl_file.json"):
fail("failed to delete the whl_file.json file")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fail("failed to delete the whl_file.json file")
fail("failed to delete: {}".format(rctx.path("whl_file.json")))


if rctx.attr.whl_patches:
patches = {}
Expand Down Expand Up @@ -890,14 +932,18 @@ if __name__ == "__main__":
)
return contents

whl_library_attrs = {
# NOTE @aignas 2024-03-21: The usage of dict({}, **common) ensures that all args to `dict` are unique
whl_library_attrs = dict({
rickeylev marked this conversation as resolved.
Show resolved Hide resolved
"annotation": attr.label(
doc = (
"Optional json encoded file containing annotation to apply to the extracted wheel. " +
"See `package_annotation`"
),
allow_files = True,
),
"filename": attr.string(
doc = "Download the whl file to this filename. Only used when the `urls` is passed. If not specified, will be auto-detected from the `urls`.",
),
"group_deps": attr.string_list(
doc = "List of dependencies to skip in order to break the cycles within a dependency group.",
default = [],
Expand All @@ -911,7 +957,18 @@ whl_library_attrs = {
),
"requirement": attr.string(
mandatory = True,
doc = "Python requirement string describing the package to make available",
doc = "Python requirement string describing the package to make available, if 'urls' or 'whl_file' is given, then this only needs to include foo[any_extras] as a bare minimum.",
),
"sha256": attr.string(
doc = "The sha256 of the downloaded whl. Only used when the `urls` is passed.",
),
"urls": attr.string_list(
doc = """\
The list of urls of the whl to be downloaded using bazel downloader. Using this
attr makes `extra_pip_args` and `download_only` ignored.""",
),
"whl_file": attr.label(
doc = "The whl file that should be used instead of downloading or building the whl.",
),
"whl_patches": attr.label_keyed_string_dict(
doc = """a label-keyed-string dict that has
Expand All @@ -933,9 +990,8 @@ whl_library_attrs = {
for repo in all_requirements
],
),
}

whl_library_attrs.update(**common_attrs)
}, **common_attrs)
whl_library_attrs.update(AUTH_ATTRS)

whl_library = repository_rule(
attrs = whl_library_attrs,
Expand Down
15 changes: 15 additions & 0 deletions python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ bzl_library(
srcs = ["parse_whl_name.bzl"],
)

bzl_library(
name = "pypi_index_bzl",
srcs = ["pypi_index.bzl"],
deps = [
":auth_bzl",
":normalize_name_bzl",
":text_util_bzl",
"//python/pip_install:requirements_parser_bzl",
"//python/private/bzlmod:bazel_features_bzl",
],
)

bzl_library(
name = "py_cc_toolchain_bzl",
srcs = [
Expand Down Expand Up @@ -260,6 +272,9 @@ bzl_library(
name = "whl_target_platforms_bzl",
srcs = ["whl_target_platforms.bzl"],
visibility = ["//:__subpackages__"],
deps = [
"parse_whl_name_bzl",
],
)

bzl_library(
Expand Down
Loading
Loading