Skip to content

py_binary/py_test targets with slash names can share the same venv output directory #3844

Description

@gleyba

🐞 bug report

Affected Rule

The issue is caused by the rule: `py_binary`

py_test likely has the same issue because it shares the executable creation
implementation, but the minimal reproduction below uses py_binary.

Is this a regression?

Unknown. I reproduced this with `rules_python` 2.0.3, but I have not

identified the last version where this worked correctly.

Description

When two Python executable targets in the same package have names

containing path separators and the same basename, rules_python derives the
venv output directory from the executable basename. Because the venv outputs are
declared package-relative, distinct labels can declare the same venv output
paths.

For example, both of these labels are distinct:

//:foo/tool
//:bar/tool

But both generated executables have basename tool, so unpatched
rules_python 2.0.3 declares the same venv root for both targets:

bazel-out/.../bin/_tool.venv

Expected behavior: distinct executable labels should use distinct venv output
directories, for example _foo_tool.venv and _bar_tool.venv.

This looks related to the slash target handling fixed in #3790 for bootstrap
stub placement, but this issue affects the venv output prefix.

🔬 Minimal Reproduction

Create a fresh bzlmod workspace with these files:

MODULE.bazel

module(
    name = "rules_python_slash_venv_repro",
    version = "0.0.0",
)

bazel_dep(
    name = "rules_python",
    version = "2.0.3",
)

python = use_extension("@rules_python//python/extensions:python.bzl", "python")

python.toolchain(
    python_version = "3.11",
)

use_repo(python, "python_3_11")

register_toolchains("@python_3_11//:all")

BUILD.bazel

load("@rules_python//python:defs.bzl", "py_binary")

py_binary(
    name = "foo/tool",
    srcs = ["foo_tool.py"],
    main = "foo_tool.py",
)

py_binary(
    name = "bar/tool",
    srcs = ["bar_tool.py"],
    main = "bar_tool.py",
)

foo_tool.py

def main():
    print("foo")


if __name__ == "__main__":
    main()

bar_tool.py

def main():
    print("bar")


if __name__ == "__main__":
    main()

Then run:

bazel clean --expunge
bazel build //:foo/tool //:bar/tool
bazel aquery 'outputs(".*_tool[.]venv/pyvenv[.]cfg", deps(//:foo/tool + //:bar/tool))'

🔥 Exception or Error


Bazel 9.1.1 completes the build in this minimal reproduction, apparently
because these FileWrite actions are identical. However, filtered aquery shows
that two distinct targets declare the same package-relative venv output:

action 'Writing file _tool.venv/pyvenv.cfg'
  Target: //:bar/tool
  Outputs: [bazel-out/k8-fastbuild/bin/_tool.venv/pyvenv.cfg]

action 'Writing file _tool.venv/pyvenv.cfg'
  Target: //:foo/tool
  Outputs: [bazel-out/k8-fastbuild/bin/_tool.venv/pyvenv.cfg]

🌍 Your Environment

Operating System:

  
Ubuntu 24.04.3 LTS
Linux 6.8.0-124-generic x86_64
  

Output of bazel version:

  
Bazelisk version: v1.27.0
Build label: 9.1.1
Build target: @@//src/main/java/com/google/devtools/build/lib/bazel:BazelServer
Build time: Wed Jun 03 15:41:13 2026 (1780501273)
Build timestamp: 1780501273
Build timestamp as int: 1780501273
  

Rules_python version:

  
2.0.3
  

Anything else relevant?

The reproduction uses the rules_python Python toolchain extension with
python_version = "3.11".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions