Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1db8653
fix(uv): allow overriding with shell environment
aignas May 28, 2026
3d514ce
test(uv_lock): add integration test for shell env override in lock.up…
aignas May 28, 2026
9300666
use pypiserver for the integration test setup instead of a custom PyP…
aignas May 29, 2026
61f06ad
doc: changelog
aignas May 29, 2026
fc5a48b
add a hash
aignas May 29, 2026
3ac8ee4
add another test
aignas May 29, 2026
6c6a887
docs: document credential helper support for uv lock rule
aignas May 29, 2026
d314931
add docs and adjust changelog
aignas May 29, 2026
0e20490
also run the credential tests
aignas May 29, 2026
de5fe1c
vibe: rev 2
aignas May 29, 2026
1545f6b
Apply suggestions from code review
rickeylev May 31, 2026
116a60a
vibe: course adjust
aignas May 30, 2026
ea78868
wip
aignas Jun 1, 2026
cc734d6
fix(pypi): do not fail on indexes without root index (#3799)
aignas May 29, 2026
2dcb2a7
fix(build-data): remove CONFIG_MODE from build data (#3801)
rickeylev May 31, 2026
6c6cc84
chore: tell agents to not amend or rebase PRs (#3804)
rickeylev Jun 1, 2026
e0bec20
test(mocks): extract mock python extension helper (#3803)
rickeylev Jun 1, 2026
0b588c0
revert unrelated changes
aignas Jun 1, 2026
56ef0d9
remove unnecessary code
aignas Jun 1, 2026
3362ba1
add a note
aignas Jun 1, 2026
bbb4900
wip
aignas Jun 1, 2026
cd9eeb8
wip
aignas Jun 1, 2026
2b7f8ee
wip
aignas Jun 1, 2026
565e2db
wip
aignas Jun 1, 2026
2f7da6c
Merge branch 'main' into aignas.fix.uv-allow-env-setting
aignas Jun 1, 2026
e4f328c
wip
aignas Jun 1, 2026
4edc539
fix windows
aignas Jun 1, 2026
0281889
add an extra test and docs on how to do debugging
aignas Jun 1, 2026
a3499db
reuse code
aignas Jun 2, 2026
a9a584c
attempt to remove the windows test skips
aignas Jun 2, 2026
142c108
fix: use .bat extension on Windows for lock run target, use CRLF line…
aignas Jun 2, 2026
b7d6cc1
fix: inherit parent environment when running subprocess in lock tests
aignas Jun 2, 2026
72004e0
fix: apply ruff formatting
aignas Jun 2, 2026
6f160d3
revert: go back to .exe extension and LF-only for lock scripts
aignas Jun 2, 2026
32d5dd9
fix: replace native_test with diff_test in lock test suite
aignas Jun 2, 2026
b6c800e
fix: use .bat extension on Windows for lock run target, use CRLF line…
aignas Jun 2, 2026
9280548
fix(uv_runner): add .exe extension for Windows compatibility
aignas Jun 2, 2026
49f2dd3
Update the skill
aignas Jun 2, 2026
82cefa3
iterate
aignas Jun 2, 2026
80f89ac
fix: Use File.path instead of short_path in .bat command
aignas Jun 2, 2026
297beba
fix: handle Windows .bat/.exe resolution in requirements_run_tests
aignas Jun 2, 2026
23843e4
fix(uv,lint): resolve Windows runfiles resolution and line-ending nor…
aignas Jun 2, 2026
af6495e
fix(uv,lock): normalize CRLF line endings and fix Windows binary exec…
aignas Jun 2, 2026
b361891
fix: remove log files from tracking
aignas Jun 2, 2026
6a677aa
fix(uv,lock): prefer .exe/.bat extensions when locating runfiles on W…
aignas Jun 2, 2026
2731895
chore: add Buildkite logs to .gitignore
aignas Jun 2, 2026
8f37969
fix(uv,lock): use binary file ops for CRLF normalization and fix path…
aignas Jun 2, 2026
516c5ed
chore: remove accidentally committed _pipeline_.log
aignas Jun 2, 2026
c9abae1
fix(uv,lock): re-add missing 'import pathlib' in normalization batch …
aignas Jun 2, 2026
5a05f6b
fix(uv,lock): remove backslash normalisation to avoid corrupting cont…
aignas Jun 2, 2026
724234c
fix(uv,lock): only convert / to \ for File objects in Windows batch a…
aignas Jun 2, 2026
1e62421
finish uncommenting
aignas Jun 2, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,32 @@ def fetch_buildkite_data(build_url):


def download_log(job_url, output_path):
# Construct raw log URL: job_url + "/raw" (Buildkite convention)
# job_url e.g. https://buildkite.com/org/pipeline/builds/14394#job-id
# Wait, the job['path'] gives /org/pipeline/builds/14394#job-id
# We want /org/pipeline/builds/14394/jobs/job-id/raw? No
# The clean URL for a job is https://buildkite.com/org/pipeline/builds/14394/jobs/job-id
# And raw log is https://buildkite.com/org/pipeline/builds/14394/jobs/job-id/raw

# We have full_url e.g. https://buildkite.com/bazel/rules-python-python/builds/14394#019c5cf9-e3cf-468f-a7b1-8f9f5ad4b08c
# We need to transform it.
# job_url looks like:
# https://buildkite.com/bazel/rules-python-python/builds/15594#019e879b-...
# We need to transform it to:
# https://buildkite.com/organizations/bazel/pipelines/rules-python-python/builds/15594/jobs/{job_id}/download.txt

if "#" in job_url:
base, job_id = job_url.split("#")
# Ensure base doesn't end with /
if base.endswith("/"):
base = base[:-1]

# Build raw URL
raw_url = f"{base}/jobs/{job_id}/raw"
base = base.rstrip("/")

# Parse the path segments: https://buildkite.com/org/pipeline/builds/N
# Rebuild with the /organizations/org/pipelines/pipeline/ format which
# supports the /jobs/{id}/download.txt log URL without auth.
parts = base.split("/")
# parts = ["https:", "", "buildkite.com", "org", "pipeline", "builds", "N"]
if len(parts) >= 7 and parts[2] == "buildkite.com":
org = parts[3]
pipeline = parts[4]
build_num = parts[6] if len(parts) >= 7 else ""
raw_url = (
f"https://buildkite.com/organizations/{org}"
f"/pipelines/{pipeline}"
f"/builds/{build_num}"
f"/jobs/{job_id}/download.txt"
)
else:
raw_url = f"{base}/jobs/{job_id}/download.txt"
else:
print(f"Could not parse job URL for download: {job_url}", file=sys.stderr)
return False
Expand Down
36 changes: 0 additions & 36 deletions .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ tasks:
test_targets:
- "//..."

# Keep in sync with .bcr/gazelle/presubmit.yml
gazelle_bcr_ubuntu:
<<: *gazelle_common_bcr
name: "Gazelle: BCR, Bazel {bazel}"
Expand Down Expand Up @@ -203,7 +202,6 @@ tasks:
<<: *common_workspace_flags_min_bazel
name: "Default: Ubuntu, workspace, minimum Bazel"
platform: ubuntu2204

ubuntu_min_bzlmod:
<<: *minimum_supported_version
<<: *reusable_config
Expand All @@ -220,16 +218,13 @@ tasks:
name: "Default: Ubuntu, upcoming Bazel"
platform: ubuntu2204
bazel: last_rc
# This is an advisory job; doesn't block merges
# RCs may have regressions, so don't fail our CI on them
soft_fail:
- exit_status: 1
- exit_status: 3
ubuntu_rolling:
name: "Default: Ubuntu, rolling Bazel"
platform: ubuntu2204
bazel: rolling
# This is an advisory job; doesn't block merges
soft_fail:
- exit_status: 1
- exit_status: 3
Expand Down Expand Up @@ -299,25 +294,14 @@ tasks:
platform: rbe_ubuntu2404
build_flags:
- "--experimental_repository_cache_hardlinks=false"
# BazelCI sets --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1,
# which prevents cc toolchain autodetection from working correctly
# on Bazel 5.4 and earlier. To workaround this, manually specify the
# build kite cc toolchain.
- "--extra_toolchains=@buildkite_config//config:cc-toolchain"
- "--build_tag_filters=-docs"
test_flags:
- "--test_tag_filters=-integration-test,-acceptance-test,-docs"
# BazelCI sets --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1,
# which prevents cc toolchain autodetection from working correctly
# on Bazel 5.4 and earlier. To workaround this, manually specify the
# build kite cc toolchain.
- "--extra_toolchains=@buildkite_config//config:cc-toolchain"
rbe:
<<: *reusable_config
name: "RBE: Ubuntu"
platform: rbe_ubuntu2404
# TODO @aignas 2024-12-11: get the RBE working in CI for bazel 8.0
# See https://github.com/bazelbuild/rules_python/issues/2499
bazel: 8.x
test_flags:
- "--test_tag_filters=-integration-test,-acceptance-test"
Expand Down Expand Up @@ -465,9 +449,6 @@ tasks:
<<: *common_bazelinbazel_config
name: "tests/integration bazel-in-bazel: Debian"
platform: debian11
# The bazelinbazel tests were disabled on Mac to save CI jobs slots, and
# have bitrotted a bit. For now, just run a subset of what we're most
# interested in.
integration_test_bazelinbazel_macos:
<<: *common_bazelinbazel_config
name: "tests/integration bazel-in-bazel: macOS (subset)"
Expand All @@ -490,14 +471,10 @@ tasks:
working_directory: tests/integration/compile_pip_requirements
platform: ubuntu2204
shell_commands:
# Make a change to the locked requirements and then assert that //:requirements.update does the
# right thing.
- "echo '' > requirements_lock.txt"
- "! git diff --exit-code"
- "bazel run //:requirements.update"
- "git diff --exit-code"
# Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
# right thing.
- "echo '' > requirements_lock_linux.txt"
- "! git diff --exit-code"
- "bazel run //:os_specific_requirements.update"
Expand All @@ -508,14 +485,10 @@ tasks:
working_directory: tests/integration/compile_pip_requirements
platform: debian11
shell_commands:
# Make a change to the locked requirements and then assert that //:requirements.update does the
# right thing.
- "echo '' > requirements_lock.txt"
- "! git diff --exit-code"
- "bazel run //:requirements.update"
- "git diff --exit-code"
# Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
# right thing.
- "echo '' > requirements_lock_linux.txt"
- "! git diff --exit-code"
- "bazel run //:os_specific_requirements.update"
Expand All @@ -526,14 +499,10 @@ tasks:
working_directory: tests/integration/compile_pip_requirements
platform: macos_arm64
shell_commands:
# Make a change to the locked requirements and then assert that //:requirements.update does the
# right thing.
- "echo '' > requirements_lock.txt"
- "! git diff --exit-code"
- "bazel run //:requirements.update"
- "git diff --exit-code"
# Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
# right thing.
- "echo '' > requirements_lock_darwin.txt"
- "! git diff --exit-code"
- "bazel run //:os_specific_requirements.update"
Expand Down Expand Up @@ -564,7 +533,6 @@ tasks:
working_directory: tests/integration/compile_pip_requirements_test_from_external_repo
platform: ubuntu2204
shell_commands:
# Assert that @compile_pip_requirements//:requirements_test does the right thing.
- "bazel test @compile_pip_requirements//..."
integration_compile_pip_requirements_test_from_external_repo_ubuntu_min_bzlmod:
<<: *minimum_supported_version
Expand All @@ -573,28 +541,24 @@ tasks:
platform: ubuntu2204
bazel: 7.x
shell_commands:
# Assert that @compile_pip_requirements//:requirements_test does the right thing.
- "bazel test @compile_pip_requirements//..."
integration_compile_pip_requirements_test_from_external_repo_ubuntu:
name: "compile_pip_requirements_test_from_external_repo: Ubuntu"
working_directory: tests/integration/compile_pip_requirements_test_from_external_repo
platform: ubuntu2204
shell_commands:
# Assert that @compile_pip_requirements//:requirements_test does the right thing.
- "bazel test @compile_pip_requirements//..."
integration_compile_pip_requirements_test_from_external_repo_debian:
name: "compile_pip_requirements_test_from_external_repo: Debian"
working_directory: tests/integration/compile_pip_requirements_test_from_external_repo
platform: debian11
shell_commands:
# Assert that @compile_pip_requirements//:requirements_test does the right thing.
- "bazel test @compile_pip_requirements//..."
integration_compile_pip_requirements_test_from_external_repo_macos:
name: "compile_pip_requirements_test_from_external_repo: macOS"
working_directory: tests/integration/compile_pip_requirements_test_from_external_repo
platform: macos_arm64
shell_commands:
# Assert that @compile_pip_requirements//:requirements_test does the right thing.
- "bazel test @compile_pip_requirements//..."
integration_compile_pip_requirements_test_from_external_repo_windows:
name: "compile_pip_requirements_test_from_external_repo: Windows"
Expand Down
1 change: 1 addition & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ tests/integration/compile_pip_requirements/bazel-compile_pip_requirements
tests/integration/local_toolchains/bazel-local_toolchains
tests/integration/py_cc_toolchain_registered/bazel-py_cc_toolchain_registered
tests/integration/toolchain_target_settings/bazel-module_under_test
tests/integration/uv_lock/bazel-uv_lock
1 change: 1 addition & 0 deletions .bazelrc.deleted_packages
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ common --deleted_packages=tests/integration/pip_parse/empty
common --deleted_packages=tests/integration/pip_parse_isolated
common --deleted_packages=tests/integration/py_cc_toolchain_registered
common --deleted_packages=tests/integration/toolchain_target_settings
common --deleted_packages=tests/integration/uv_lock
common --deleted_packages=tests/modules/another_module
common --deleted_packages=tests/modules/other
common --deleted_packages=tests/modules/other/nspkg_delta
Expand Down
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
python/features.bzl export-subst
tools/publish/*.txt linguist-generated=true
tests/uv/lock/testdata/requirements.txt text eol=lf
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ user.bazelrc
# MODULE.bazel.lock is ignored for now as per recommendation from upstream.
# See https://github.com/bazelbuild/bazel/issues/20369
MODULE.bazel.lock

# Buildkite logs
*Windows*.log

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ END_UNRELEASED_TEMPLATE
* (pypi) `package_metadata` support, fixes
[#2054](https://github.com/bazel-contrib/rules_python/issues/2054).
* (coverage) Add support for python 3.14 and bump `coverage.py` to 7.10.7.
* (uv) allow user overwrite the build environment using `--action_env` to allow
setting authentication for the index URL.
([#3045](https://github.com/bazel-contrib/rules_python/issues/3405))

{#v2-0-2}
## [2.0.2] - 2026-05-14
Expand Down
14 changes: 7 additions & 7 deletions python/uv/private/lock.bat
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
if defined BUILD_WORKSPACE_DIRECTORY (
set "out=%BUILD_WORKSPACE_DIRECTORY%\{{src_out}}"
) else (
exit /b 1
)

"{{args}}" --output-file "%out%" %*
if defined BUILD_WORKSPACE_DIRECTORY (
set "out=%BUILD_WORKSPACE_DIRECTORY%\{{src_out}}"
) else (
exit /b 1
)
"{{args}}" --output-file "%out%" %*
95 changes: 85 additions & 10 deletions python/uv/private/lock.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -117,31 +117,96 @@ def _lock_impl(ctx):
args.run_shell.add("--no-progress")
args.run_shell.add("--quiet")

# Generate a wrapper script that copies the existing output (if any) and
# then runs uv. On POSIX, args are forwarded via exec "$@". On Windows,
# the full command line is embedded in the .bat file with backslash paths
# (CMD doesn't recognize forward slashes in executable paths).
if ctx.attr.is_windows:
ext = ".bat"
lines = ["@echo off"]
else:
ext = ".sh"
lines = ["#!/usr/bin/env bash", "set -euo pipefail"]

python_path = getattr(python, "path", python)

if ctx.files.existing_output:
command = '{python} -c {python_cmd} && "$@"'.format(
python = getattr(python, "path", python),
python_cmd = shell.quote(
"from shutil import copy; copy(\"{src}\", \"{dst}\")".format(
python_cmd = "from shutil import copy; copy(\"{src}\", \"{dst}\")".format(
src = ctx.files.existing_output[0].path,
dst = output.path,
)
if ctx.attr.is_windows:
# In batch files, use "" to escape internal double quotes.
lines.append(
"\"{py}\" -c \"from shutil import copy; copy(\"\"{src}\"\", \"\"{dst}\"\")\"".format(
py = python_path,
src = ctx.files.existing_output[0].path,
dst = output.path,
),
)
else:
lines.append("{py} -c '{cmd}'".format(
py = python_path,
cmd = python_cmd,
))

if ctx.attr.is_windows:
# Build the command line with backslash paths for CMD.
# args.run_info has most args; add the output/progress/quiet
# args that were only added directly to args.run_shell.
def _quote(arg):
if hasattr(arg, "path"):
arg = arg.path.replace("/", "\\")
else:
arg = str(arg)
return '"' + arg.replace('"', '""') + '"'

bat_args = args.run_info + [
"--output-file",
output,
"--no-progress",
"--quiet",
]
lines.append(" ".join([_quote(a) for a in bat_args]))

# Normalize CRLF line endings in the output on Windows.
lines.append(
"\"{py}\" -c \"import pathlib;p=pathlib.Path(r\"\"{dst}\"\");p.write_bytes(p.read_bytes().replace(b'\\r\\n', b'\\n'))\"".format(
py = python_path,
dst = output.path,
),
)
else:
command = '"$@"'
lines.append('exec "$@"')

script = ctx.actions.declare_file(ctx.label.name + "_lock" + ext)
if ctx.attr.is_windows:
content = "\r\n".join(lines) + "\r\n"
else:
content = "\n".join(lines) + "\n"
ctx.actions.write(output = script, content = content, is_executable = True)

srcs = srcs + ctx.files.build_constraints + ctx.files.constraints

ctx.actions.run_shell(
command = command,
ctx.actions.run(
executable = script,
inputs = srcs + ctx.files.existing_output,
mnemonic = "PyRequirementsLockUv",
outputs = [output],
arguments = [args.run_shell],
# On Windows, the command line is embedded directly in the .bat
# script (with backslash paths). On POSIX, args are forwarded via
# exec "$@" in the .sh script.
arguments = [args.run_shell] if not ctx.attr.is_windows else [],
tools = [
uv,
python_files,
script,
],
# User reported being unable to add `--action_env` and get it to work.
# Without this flag.
#
# Ref: https://app.slack.com/client/TA4K1KQ87/CA306CEV6
use_default_shell_env = True,
progress_message = "Creating a requirements.txt with uv: %{label}",
env = ctx.attr.env,
)
Expand Down Expand Up @@ -205,6 +270,7 @@ modifications and the locking is not done from scratch.
doc = "Public, see the docs in the macro.",
default = True,
),
"is_windows": attr.bool(mandatory = True),
"output": attr.string(
doc = "Public, see the docs in the macro.",
mandatory = True,
Expand Down Expand Up @@ -241,7 +307,7 @@ The string to input for the 'uv pip compile'.
def _lock_run_impl(ctx):
if ctx.attr.is_windows:
path_sep = "\\"
ext = ".exe"
ext = ".bat"
else:
path_sep = "/"
ext = ""
Expand All @@ -250,7 +316,12 @@ def _lock_run_impl(ctx):
if hasattr(arg, "short_path"):
arg = arg.short_path

return shell.quote(arg.replace("/", path_sep))
arg = arg.replace("/", path_sep)
if ctx.attr.is_windows:
# On Windows, CMD uses double quotes for quoting, and internal
# double quotes are escaped by doubling them.
return '"' + arg.replace('"', '""') + '"'
return shell.quote(arg)

info = ctx.attr.lock[_RunLockInfo]
executable = ctx.actions.declare_file(ctx.label.name + ext)
Expand Down Expand Up @@ -438,6 +509,10 @@ def lock(
env = env,
existing_output = maybe_out,
generate_hashes = generate_hashes,
is_windows = select({
"@platforms//os:windows": True,
"//conditions:default": False,
}),
python_version = python_version,
srcs = srcs,
strip_extras = strip_extras,
Expand Down
Loading