blob: da449b4b506761a58b489fc7896667bf4dee7cdb [file] [log] [blame]
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""
load("@bazel_skylib//lib:types.bzl", "types")
load("//python/private:repo_utils.bzl", "repo_utils")
def _get_python_interpreter_attr(mrctx, *, python_interpreter = None):
"""A helper function for getting the `python_interpreter` attribute or it's default
Args:
mrctx (module_ctx or repository_ctx): Handle to the rule repository context.
python_interpreter (str): The python interpreter override.
Returns:
str: The attribute value or it's default
"""
if python_interpreter:
return python_interpreter
os = repo_utils.get_platforms_os_name(mrctx)
if "windows" in os:
return "python.exe"
else:
return "python3"
def _resolve_python_interpreter(mrctx, *, python_interpreter = None, python_interpreter_target = None):
"""Helper function to find the python interpreter from the common attributes
Args:
mrctx: Handle to the module_ctx or repository_ctx.
python_interpreter: str, the python interpreter to use.
python_interpreter_target: Label, the python interpreter to use after
downloading the label.
Returns:
`path` object, for the resolved path to the Python interpreter.
"""
python_interpreter = _get_python_interpreter_attr(mrctx, python_interpreter = python_interpreter)
if python_interpreter_target != None:
python_interpreter = mrctx.path(python_interpreter_target)
os = repo_utils.get_platforms_os_name(mrctx)
# On Windows, the symlink doesn't work because Windows attempts to find
# Python DLLs where the symlink is, not where the symlink points.
if "windows" in os:
python_interpreter = python_interpreter.realpath
elif "/" not in python_interpreter:
# It's a plain command, e.g. "python3", to look up in the environment.
python_interpreter = repo_utils.which_checked(mrctx, python_interpreter)
else:
python_interpreter = mrctx.path(python_interpreter)
return python_interpreter
def _construct_pypath(mrctx, *, entries):
"""Helper function to construct a PYTHONPATH.
Contains entries for code in this repo as well as packages downloaded from //python/pip_install:repositories.bzl.
This allows us to run python code inside repository rule implementations.
Args:
mrctx: Handle to the module_ctx or repository_ctx.
entries: The list of entries to add to PYTHONPATH.
Returns: String of the PYTHONPATH.
"""
if not entries:
return None
os = repo_utils.get_platforms_os_name(mrctx)
separator = ";" if "windows" in os else ":"
pypath = separator.join([
str(mrctx.path(entry).dirname)
# Use a dict as a way to remove duplicates and then sort it.
for entry in sorted({x: None for x in entries})
])
return pypath
def _execute_checked(mrctx, *, srcs, **kwargs):
"""Helper function to run a python script and modify the PYTHONPATH to include external deps.
Args:
mrctx: Handle to the module_ctx or repository_ctx.
srcs: The src files that the script depends on. This is important to
ensure that the Bazel repository cache or the bzlmod lock file gets
invalidated when any one file changes. It is advisable to use
`RECORD` files for external deps and the list of srcs from the
rules_python repo for any scripts.
**kwargs: Arguments forwarded to `repo_utils.execute_checked`. If
the `environment` has a value `PYTHONPATH` and it is a list, then
it will be passed to `construct_pythonpath` function.
"""
for src in srcs:
# This will ensure that we will re-evaluate the bzlmod extension or
# refetch the repository_rule when the srcs change. This should work on
# Bazel versions without `mrctx.watch` as well.
repo_utils.watch(mrctx.path(src))
env = kwargs.pop("environment", {})
pythonpath = env.get("PYTHONPATH", "")
if pythonpath and not types.is_string(pythonpath):
env["PYTHONPATH"] = _construct_pypath(mrctx, entries = pythonpath)
return repo_utils.execute_checked(
mrctx,
environment = env,
**kwargs
)
pypi_repo_utils = struct(
construct_pythonpath = _construct_pypath,
execute_checked = _execute_checked,
resolve_python_interpreter = _resolve_python_interpreter,
)