xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/py_interpreter_program.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1# Copyright 2024 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Internal only bootstrap level binary-like rule."""
16
17load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
18
19PyInterpreterProgramInfo = provider(
20    doc = "Information about how to run a program with an external interpreter.",
21    fields = {
22        "env": "dict[str, str] of environment variables to set prior to execution.",
23        "interpreter_args": "List of strings; additional args to pass " +
24                            "to the interpreter before the main program.",
25        "main": "File; the .py file that is the entry point.",
26    },
27)
28
29def _py_interpreter_program_impl(ctx):
30    # Bazel requires the executable file to be an output created by this target.
31    executable = ctx.actions.declare_file(ctx.label.name)
32    ctx.actions.symlink(output = executable, target_file = ctx.file.main)
33    execution_requirements = {}
34    execution_requirements.update([
35        value.split("=", 1)
36        for value in ctx.attr.execution_requirements[BuildSettingInfo].value
37        if value.strip()
38    ])
39
40    return [
41        DefaultInfo(
42            executable = executable,
43            files = depset([executable]),
44            runfiles = ctx.runfiles(files = [
45                executable,
46            ]),
47        ),
48        PyInterpreterProgramInfo(
49            env = ctx.attr.env,
50            interpreter_args = ctx.attr.interpreter_args,
51            main = ctx.file.main,
52        ),
53        testing.ExecutionInfo(
54            requirements = execution_requirements,
55        ),
56    ]
57
58py_interpreter_program = rule(
59    doc = """
60Binary-like rule that doesn't require a toolchain because its part of
61implementing build tools for the toolchain. This rule expects the Python
62interprter to be externally provided.
63
64To run a `py_interpreter_program` as an action, pass it as a tool that is
65used by the actual interpreter executable. This ensures its runfiles are
66setup. Also pass along any interpreter args, environment, and requirements.
67
68```starlark
69ctx.actions.run(
70    executable = <python interpreter executable>,
71    args = (
72        target[PyInterpreterProgramInfo].interpreter_args +
73        [target[DefaultInfo].files_to_run.executable]
74    ),
75    tools = target[DefaultInfo].files_to_run,
76    env = target[PyInterpreterProgramInfo].env,
77    execution_requirements = target[testing.ExecutionInfo].requirements,
78)
79```
80
81""",
82    implementation = _py_interpreter_program_impl,
83    attrs = {
84        "env": attr.string_dict(
85            doc = "Environment variables that should set prior to running.",
86        ),
87        "execution_requirements": attr.label(
88            doc = "Execution requirements to set when running it as an action",
89            providers = [BuildSettingInfo],
90        ),
91        "interpreter_args": attr.string_list(
92            doc = "Args that should be passed to the interpreter.",
93        ),
94        "main": attr.label(
95            doc = "The entry point Python file.",
96            allow_single_file = True,
97        ),
98    },
99    # This is set to False because this isn't a binary/executable in the usual
100    # Bazel sense (even though it sets DefaultInfo.files_to_run). It just holds
101    # information so that a caller can construct how to execute it correctly.
102    executable = False,
103)
104