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