Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8ecc4f7
[NO TESTS] WIP
arrdem Aug 13, 2025
869c736
[NO TESTS] WIP
arrdem Aug 18, 2025
33f5861
[NO TESTS] WIP
arrdem Aug 25, 2025
f1677e3
[NO TESTS] WIP
arrdem Sep 10, 2025
be8e869
wip
arrdem Sep 11, 2025
50fe90b
wip
arrdem Sep 12, 2025
b33ffb6
[NO TESTS] WIP
arrdem Sep 23, 2025
c12d501
Merge remote-tracking branch 'origin/main' into arrdem/feat-pip
arrdem Sep 25, 2025
ae54cb6
wip
arrdem Sep 25, 2025
cb05389
Hub sorta builds
arrdem Sep 25, 2025
f18274f
Got select sorta working
arrdem Sep 26, 2025
39f8053
Wiring up an example
arrdem Sep 26, 2025
cde1870
[NO TESTS] WIP
arrdem Sep 26, 2025
0ce4ea9
Working example
arrdem Sep 27, 2025
5fc76f1
Multiple venvs working
arrdem Sep 29, 2025
7119541
wip
arrdem Sep 29, 2025
ea3d238
[NO TESTS] WIP
arrdem Oct 1, 2025
23f28bc
[NO TESTS] WIP
arrdem Oct 1, 2025
87fb65b
[NO TESTS] WIP
arrdem Oct 1, 2025
65f64b0
Cutting over to prerelease toml tool
arrdem Oct 2, 2025
ef98024
Vendor in the tomltool library in lieu of BCR release
arrdem Oct 3, 2025
6b24ccd
Finish adding transition attrs.
arrdem Oct 3, 2025
63854cb
Start addressing lint.
arrdem Oct 3, 2025
f2edc08
More lint.
arrdem Oct 3, 2025
35f7e53
Jimmy: Match the rules_python style @pip//cowsay labels
arrdem Oct 3, 2025
e714a39
Jimmy: Integrate the code to match rules_python's normalization
arrdem Oct 3, 2025
2e46a34
Working on lint & docs.
arrdem Oct 3, 2025
f175cf7
Fix.
arrdem Oct 3, 2025
1ce369d
Update bzl_library graph
arrdem Oct 3, 2025
db91365
Add a docfile which we need to populate
arrdem Oct 3, 2025
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
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 21 additions & 3 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ module(
bazel_dep(name = "aspect_bazel_lib", version = "2.16.0")
bazel_dep(name = "aspect_tools_telemetry", version = "0.2.6")
bazel_dep(name = "bazel_skylib", version = "1.4.2")
bazel_dep(name = "rules_python", version = "0.29.0")
bazel_dep(name = "platforms", version = "0.0.7")
bazel_dep(name = "rules_python", version = "1.6.3")
bazel_dep(name = "platforms", version = "1.0.0")

bazel_lib = use_extension("@aspect_bazel_lib//lib:extensions.bzl", "toolchains")
bazel_lib.expand_template()
Expand All @@ -27,7 +27,7 @@ use_repo(tel, "aspect_tools_telemetry_report")
python = use_extension("@rules_python//python/extensions:python.bzl", "python", dev_dependency = True)
python.toolchain(
is_default = False,
python_version = "3.9",
python_version = "3.13",
)

tools = use_extension("//py:extensions.bzl", "py_tools")
Expand Down Expand Up @@ -114,3 +114,21 @@ oci.pull(
tag = "latest",
)
use_repo(oci, "ubuntu", "ubuntu_linux_amd64", "ubuntu_linux_arm64_v8")


# Mandatory pip2 host configuration
host = use_extension("//pip/private/host:extension.bzl", "host_platform")
use_repo(host, "aspect_rules_py_pip_host")

# Testing
pip2 = use_extension("//pip:extension.bzl", "pip")

pip2.declare_hub(hub_name = "pip2")

pip2.declare_venv(hub_name = "pip2", venv_name = "default")
pip2.lockfile(hub_name = "pip2", venv_name = "default", lockfile = "//:uv-default.lock")

pip2.declare_venv(hub_name = "pip2", venv_name = "airflow")
pip2.lockfile(hub_name = "pip2", venv_name = "airflow", lockfile = "//:uv-airflow.lock")

use_repo(pip2, "pip2")
4 changes: 2 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ load("@rules_python//python:repositories.bzl", "py_repositories", "python_regist

python_register_toolchains(
name = "python_toolchain_3_8",
python_version = "3.8.12",
python_version = "3.8",
# Setting `set_python_version_constraint` will set special constraints on the registered toolchain.
# This means that this toolchain registration will only be selected for `py_binary` / `py_test` targets
# that have the `python_version = "3.8.12"` attribute set. Targets that have no `python_attribute` will use
Expand All @@ -35,7 +35,7 @@ python_register_toolchains(
# py_test/py_binary target even if it has python_version attribute set.
python_register_toolchains(
name = "python_toolchain",
python_version = "3.9",
python_version = "3.13",
)

py_repositories()
Expand Down
25 changes: 25 additions & 0 deletions examples/pip2/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template")
load("//py/unstable:defs.bzl", "py_venv", "py_venv_binary")

expand_template(
name = "stamped",
stamp_substitutions = {
"<BUILD_TIMESTAMP>": "{{BUILD_TIMESTAMP}}",
},
template = "say.py",
)

py_venv_binary(
name = "say",
srcs = [
":stamped",
],
imports = [
".",
],
main = ":stamped",
deps = [
"@pip2//package:cowsay",
"@pip2//package:pandas",
],
)
44 changes: 44 additions & 0 deletions examples/pip2/say.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import sys

print("---")
import _virtualenv

output_base = _virtualenv.__file__.split("/execroot/")[0]
execroot = f"{output_base}/execroot"
external = f"{output_base}/external"
runfiles = _virtualenv.__file__.split(".runfiles/")[0] + ".runfiles"

def _simplify(s):
if isinstance(s, str):
return s \
.replace(sys.prefix, "${PYTHONHOME}") \
.replace(runfiles, "${RUNFILES}") \
.replace(execroot, "${BAZEL_EXECROOT}") \
.replace(external, "${BAZEL_EXTERNAL}") \
.replace(output_base, "${BAZEL_BASE}")

elif isinstance(s, list):
return [_simplify(it) for it in s]

print("virtualenv:", _simplify(_virtualenv.__file__))
import sys
print("sys.prefix:", _simplify(sys.prefix))
print("sys.path:")
for it in _simplify(sys.path):
print(" -", it)
import site
print("site.PREFIXES:")
for it in _simplify(site.PREFIXES):
print(" -", it)

import cowsay

cowsay.cow('hello py_venv! (built at <BUILD_TIMESTAMP>)')


import pandas
print("pandas", _simplify(pandas.__file__))

import numpy # pandas transitive
print("numpy", _simplify(numpy.__file__))
9 changes: 9 additions & 0 deletions internal_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,12 @@ def rules_py_internal_deps():
strip_prefix = "container-structure-test-56c7201716d770c0f820a9c19207ba2ea77c34f8",
sha256 = "29632b3226bb9c7dcde340b6efcc48cc5f32e60ec47c86a144922e234e4d9ce7",
)

_http_archive(
name = "yq.bzl",
urls = [
"https://github.com/bazel-contrib/yq.bzl/archive/refs/tags/v0.2.0.tar.gz",
],
strip_prefix = "yq.bzl-0.2.0",
sha256 = "FIXME",
)
Empty file added pip/BUILD.bazel
Empty file.
3 changes: 3 additions & 0 deletions pip/extension.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load("//pip/private:extension.bzl", _pip = "pip")

pip = _pip
Empty file added pip/private/BUILD.bazel
Empty file.
3 changes: 3 additions & 0 deletions pip/private/constraints/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load(":macros.bzl", "generate")

generate()
5 changes: 5 additions & 0 deletions pip/private/constraints/abi/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
load(":macro.bzl", "generate")

package(default_visibility = ["//visibility:public"])

generate()
122 changes: 122 additions & 0 deletions pip/private/constraints/abi/macro.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
load("//pip/private/constraints:defs.bzl", "MAJORS", "MINORS", "INTERPRETERS", "FLAGS")

# FIXME: Where does abi 2/3/4 fit in here?
# FIXME: Where do ABI feature flags fit in here?
def generate():
"""
Lay down `py3`, `py312`, `cp3`, `cp312` etc and critically `any`.

The interpretation is a bit tricky because `cp`
"""

# FIXME: Is there a better/worse way to do this?
selects.config_setting_group(
name = "none",
match_all = [
"//conditions:default",
]
)

native.constraint_setting(
name = "feature_pydebug",
default_constraint_value = ":pydebug_disabled",
)
native.constraint_value(
name = "pydebug_enabled",
constraint_setting = ":feature_pydebug",
)
native.constraint_value(
name = "pydebug_disabled",
constraint_setting = ":feature_pydebug",
)

native.constraint_setting(
name = "feature_pymalloc",
default_constraint_value = ":pymalloc_disabled",
)
native.constraint_value(
name = "pymalloc_enabled",
constraint_setting = ":feature_pymalloc",
)
native.constraint_value(
name = "pymalloc_disabled",
constraint_setting = ":feature_pymalloc",
)

native.constraint_setting(
name = "feature_freethreading",
default_constraint_value = ":freethreading_disabled",
)
native.constraint_value(
name = "freethreading_enabled",
constraint_setting = ":feature_freethreading",
)
native.constraint_value(
name = "freethreading_disabled",
constraint_setting = ":feature_freethreading",
)

native.constraint_setting(
name = "feature_wide_unicode",
default_constraint_value = ":wide_unicode_disabled",
)
native.constraint_value(
name = "wide_unicode_enabled",
constraint_setting = ":feature_wide_unicode",
)
native.constraint_value(
name = "wide_unicode_disabled",
constraint_setting = ":feature_wide_unicode",
)

native.alias(
name = "abi3",
actual = "is_py33"
)

for interpreter in INTERPRETERS:
for major in MAJORS:
# selects.config_setting_group(
# name = "{}{}".format(interpreter, major),
# match_all = [
# "//pip/private/constraints/python/interpreter:{}".format(interpreter),
# "//pip/private/constraints/python/major:{}".format(major),
# ]
# )

for minor in MINORS:
selects.config_setting_group(
name = "is_{}{}{}".format(interpreter, major, minor),
Copy link
Contributor

Choose a reason for hiding this comment

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

generally %s is faster than .format because it avoids a function call and has simpler (faster) arg conversion. Also if you have only a single substitution it helps even more because you don't even need to construct a tuple. Up to you where to use it, but at least for the functions in the extension that are called repeatedly I would strongly consider it. (Honestly I pretty much always use it unless I have a complex/multiline template with 4+ args and/or using an arg multiple times)

match_all = [
# "//pip/private/constraints/python/interpreter:{}".format(interpreter),
"//pip/private/constraints/python/major:{}".format(major),
"//pip/private/constraints/python/minor:{}".format(minor),
]
)

for d in [False, True]:
for m in [False, True]:
for t in [False, True]:
for u in [False, True]:
selects.config_setting_group(
# This is a bit out of hand I admit
name = "{0}{1}{2}{3}{4}{5}{6}".format(
interpreter,
major,
minor,
"d" if d else "",
"m" if m else "",
"t" if t else "",
"u" if u else "",
),
match_all = (
[
":is_{}{}{}".format(interpreter, major, minor),
]
+ ([":pydebug_enabled"] if d else [])
+ ([":pymalloc_enabled"] if m else [])
+ ([":freethreading_enabled"] if t else [])
+ ([":wide_unicode_enabled"] if u else [])
),
)
63 changes: 63 additions & 0 deletions pip/private/constraints/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
load("@bazel_skylib//lib:selects.bzl", "selects")

MAJORS = [2, 3] # There is no 4
MINORS = range(0, 21)
PATCHES = range(0, 31)
INTERPRETERS = [
"py", # Generic
"cp", # CPython
# "jy", # Jython
# "ip", # IronPython
# "pp", # PyPy, has its own ABI scheme :|
]
# FIXME: We're ignoring these for now which isn't ideal
FLAGS = {
"d": "pydebug",
"m": "pymalloc",
"t": "freethreading",
"u": "wide-unicode", # Deprecated in 3.13
}

def generate_gte_ladder(stages):
"""
Accept a list of names of individual conditions representing specific versions.
These names happen to be in ascending order and are generated by some other codepath.

Given these conditon lable names generate a sequence ("ladder") of equality
comparison operations + or with the "greater" comparison.

So for [macos_10_0, macos_10_1, macos_11_0, macos_11_1, ...]
We want to get

selects.config_setting_group(
name = "gte_macos_11_0",
match_any = [
":macos_11_0",
":gte_macos_10_1",
],
)
selects.config_setting_group(
name = "gte_macos_10_1",
match_any = [
":macos_10_1",
":gte_macos_10_0",
],
)

As an added refinement the stages are a struct

struct(
name = <name of the ladder step>,
condition = <condition for this ladder step>,
)

"""

# We loop up to the second-to-last item to ensure we always have a 'next' stage.
for i, current_stage in enumerate(stages):
selects.config_setting_group(
name = "{}".format(current_stage.name),
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: name = current_stage.name :)

match_any = [
":{}".format(current_stage.condition),
Copy link
Contributor

Choose a reason for hiding this comment

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

":" + current_Stage.condition or ":%s" % current_stage.condition are both faster and less chars :)

] + ([":{}".format(stages[i+1].name)] if i+1 < len(stages) else []),
)
5 changes: 5 additions & 0 deletions pip/private/constraints/platform/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
load(":macro.bzl", "generate")

package(default_visibility = ["//visibility:public"])

generate()
Loading
Loading