Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ bzl_library(
visibility = ["//visibility:public"],
deps = [
"//py/private:py_binary",
"//py/private:py_executable",
"//py/private:py_image_layer",
"//py/private:py_library",
"//py/private:py_pex_binary",
Expand Down
27 changes: 2 additions & 25 deletions py/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ python.toolchain(python_version = "3.9", is_default = True)
```
"""

load("@aspect_bazel_lib//lib:utils.bzl", "propagate_common_rule_attributes")
load("//py/private:py_binary.bzl", _py_binary = "py_binary", _py_test = "py_test")
load("//py/private:py_executable.bzl", "determine_main")
load("//py/private:py_image_layer.bzl", _py_image_layer = "py_image_layer")
load("//py/private:py_library.bzl", _py_library = "py_library")
load("//py/private:py_pex_binary.bzl", _py_pex_binary = "py_pex_binary")
Expand All @@ -62,32 +60,13 @@ py_image_layer = _py_image_layer

resolutions = _resolutions

def _py_binary_or_test(name, rule, srcs, main, data = [], deps = [], resolutions = {}, **kwargs):
exec_properties = kwargs.pop("exec_properties", {})
non_test_exec_properties = {k: v for k, v in exec_properties.items() if not k.startswith("test.")}

# Compatibility with rules_python, see docs in py_executable.bzl
main_target = "{}.find_main".format(name)
determine_main(
name = main_target,
target_name = name,
main = main,
srcs = srcs,
exec_properties = non_test_exec_properties,
**propagate_common_rule_attributes(kwargs)
)

package_collisions = kwargs.pop("package_collisions", None)

def _py_binary_or_test(name, rule, srcs, main, data = [], deps = [], **kwargs):
rule(
name = name,
srcs = srcs,
main = main_target,
main = main,
data = data,
deps = deps,
resolutions = resolutions,
package_collisions = package_collisions,
exec_properties = exec_properties,
**kwargs
)

Expand All @@ -97,8 +76,6 @@ def _py_binary_or_test(name, rule, srcs, main, data = [], deps = [], resolutions
data = data,
deps = deps,
imports = kwargs.get("imports"),
resolutions = resolutions,
package_collisions = package_collisions,
tags = ["manual"],
testonly = kwargs.get("testonly", False),
target_compatible_with = kwargs.get("target_compatible_with", []),
Expand Down
5 changes: 0 additions & 5 deletions py/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,6 @@ bzl_library(
srcs = ["providers.bzl"],
)

bzl_library(
name = "py_executable",
srcs = ["py_executable.bzl"],
)

bzl_library(
name = "transitions",
srcs = ["transitions.bzl"],
Expand Down
23 changes: 19 additions & 4 deletions py/private/py_binary.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ def _py_binary_rule_impl(ctx):
venv_toolchain = ctx.toolchains[VENV_TOOLCHAIN]
py_toolchain = _py_semantics.resolve_toolchain(ctx)

# Resolve our `main=` to a label, which it isn't
main = _py_semantics.determine_main(ctx)

# Check for duplicate virtual dependency names. Those that map to the same resolution target would have been merged by the depset for us.
virtual_resolution = _py_library.resolve_virtuals(ctx)
imports_depset = _py_library.make_imports_depset(ctx, extra_imports_depsets = virtual_resolution.imports)
Expand Down Expand Up @@ -78,7 +81,7 @@ def _py_binary_rule_impl(ctx):
"{{ARG_PYTHON}}": to_rlocation_path(ctx, py_toolchain.python) if py_toolchain.runfiles_interpreter else py_toolchain.python.path,
"{{ARG_VENV_NAME}}": ".{}.venv".format(ctx.attr.name),
"{{ARG_PTH_FILE}}": to_rlocation_path(ctx, site_packages_pth_file),
"{{ENTRYPOINT}}": to_rlocation_path(ctx, ctx.file.main),
"{{ENTRYPOINT}}": to_rlocation_path(ctx, main),
"{{PYTHON_ENV}}": "\n".join(_dict_to_exports(default_env)).strip(),
"{{EXEC_PYTHON_BIN}}": "python{}".format(
py_toolchain.interpreter_version_info.major,
Expand Down Expand Up @@ -114,7 +117,7 @@ def _py_binary_rule_impl(ctx):
DefaultInfo(
files = depset([
executable_launcher,
ctx.file.main,
main,
site_packages_pth_file,
]),
executable = executable_launcher,
Expand All @@ -140,9 +143,21 @@ _attrs = dict({
default = {},
),
"main": attr.label(
doc = "Script to execute with the Python interpreter.",
allow_single_file = True,
mandatory = True,
doc = """
Script to execute with the Python interpreter.

Must be a label pointing to a `.py` source file.
If such a label is provided, it will be honored.

If no label is provided AND there is only one `srcs` file, that `srcs` file will be used.

If there are more than one `srcs`, a file matching `{name}.py` is searched for.
This is for historical compatibility with the Bazel native `py_binary` and `rules_python`.
Relying on this behavior is STRONGLY discouraged, may produce warnings and may
be deprecated in the future.

""",
),
"venv": attr.string(
doc = """The name of the Python virtual environment within which deps should be resolved.
Expand Down
92 changes: 0 additions & 92 deletions py/private/py_executable.bzl

This file was deleted.

54 changes: 54 additions & 0 deletions py/private/py_semantics.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,61 @@ def _resolve_toolchain(ctx):
flags = _INTERPRETER_FLAGS,
)

def _csv(values):
"""Convert a list of strings to comma separated value string."""
return ", ".join(sorted(values))

def _path_endswith(path, endswith):
# Use slash to anchor each path to prevent e.g.
# "ab/c.py".endswith("b/c.py") from incorrectly matching.
return ("/" + path).endswith("/" + endswith)

def _determine_main(ctx):
"""Determine the main entry point .py source file.

Args:
ctx: The rule ctx.

Returns:
Artifact; the main file. If one can't be found, an error is raised.
"""
if ctx.attr.main:
if not ctx.attr.main.label.name.endswith(".py"):
fail("main must end in '.py'")

# Short circuit; if the user gave us a label, believe them.
return ctx.file.main

elif len(ctx.files.srcs) == 1:
# If the user only provided one src, take that
return ctx.files.srcs[0]

else:
# Legacy rule name based searching :/
if ctx.label.name.endswith(".py"):
fail("name must not end in '.py'")
proposed_main = ctx.label.name + ".py"

print(ctx.files.srcs)
main_files = [src for src in ctx.files.srcs if _path_endswith(src.short_path, proposed_main)]

if len(main_files) > 1:
fail(("file name '{}' specified by 'main' attributes matches multiple files. " +
"Matches: {}").format(
proposed_main,
_csv([f.short_path for f in main_files]),
))

elif len(main_files) == 1:
return main_files[0]

else:
fail("{} does not specify main=, and has multiple sources. Disambiguate the entrypoint".format(
ctx.label,
))

semantics = struct(
interpreter_flags = _INTERPRETER_FLAGS,
resolve_toolchain = _resolve_toolchain,
determine_main = _determine_main,
)