xref: /aosp_15_r20/external/bazelbuild-rules_python/python/uv/private/lock.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1*60517a1eSAndroid Build Coastguard Worker# Copyright 2024 The Bazel Authors. All rights reserved.
2*60517a1eSAndroid Build Coastguard Worker#
3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*60517a1eSAndroid Build Coastguard Worker#
7*60517a1eSAndroid Build Coastguard Worker#    http://www.apache.org/licenses/LICENSE-2.0
8*60517a1eSAndroid Build Coastguard Worker#
9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*60517a1eSAndroid Build Coastguard Worker# limitations under the License.
14*60517a1eSAndroid Build Coastguard Worker
15*60517a1eSAndroid Build Coastguard Worker"""A simple macro to lock the requirements.
16*60517a1eSAndroid Build Coastguard Worker"""
17*60517a1eSAndroid Build Coastguard Worker
18*60517a1eSAndroid Build Coastguard Workerload("@bazel_skylib//rules:write_file.bzl", "write_file")
19*60517a1eSAndroid Build Coastguard Workerload("//python:py_binary.bzl", "py_binary")
20*60517a1eSAndroid Build Coastguard Workerload("//python/config_settings:transition.bzl", transition_py_binary = "py_binary")
21*60517a1eSAndroid Build Coastguard Workerload("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")  # buildifier: disable=bzl-visibility
22*60517a1eSAndroid Build Coastguard Worker
23*60517a1eSAndroid Build Coastguard Workervisibility(["//..."])
24*60517a1eSAndroid Build Coastguard Worker
25*60517a1eSAndroid Build Coastguard Worker_REQUIREMENTS_TARGET_COMPATIBLE_WITH = [] if BZLMOD_ENABLED else ["@platforms//:incompatible"]
26*60517a1eSAndroid Build Coastguard Worker
27*60517a1eSAndroid Build Coastguard Workerdef lock(*, name, srcs, out, upgrade = False, universal = True, python_version = None):
28*60517a1eSAndroid Build Coastguard Worker    """Pin the requirements based on the src files.
29*60517a1eSAndroid Build Coastguard Worker
30*60517a1eSAndroid Build Coastguard Worker    Args:
31*60517a1eSAndroid Build Coastguard Worker        name: The name of the target to run for updating the requirements.
32*60517a1eSAndroid Build Coastguard Worker        srcs: The srcs to use as inputs.
33*60517a1eSAndroid Build Coastguard Worker        out: The output file.
34*60517a1eSAndroid Build Coastguard Worker        upgrade: Tell `uv` to always upgrade the dependencies instead of
35*60517a1eSAndroid Build Coastguard Worker            keeping them as they are.
36*60517a1eSAndroid Build Coastguard Worker        universal: Tell `uv` to generate a universal lock file.
37*60517a1eSAndroid Build Coastguard Worker        python_version: Tell `rules_python` to use a particular version.
38*60517a1eSAndroid Build Coastguard Worker            Defaults to the default py toolchain.
39*60517a1eSAndroid Build Coastguard Worker
40*60517a1eSAndroid Build Coastguard Worker    Differences with the current pip-compile rule:
41*60517a1eSAndroid Build Coastguard Worker    - This is implemented in shell and uv.
42*60517a1eSAndroid Build Coastguard Worker    - This does not error out if the output file does not exist yet.
43*60517a1eSAndroid Build Coastguard Worker    - Supports transitions out of the box.
44*60517a1eSAndroid Build Coastguard Worker    """
45*60517a1eSAndroid Build Coastguard Worker    pkg = native.package_name()
46*60517a1eSAndroid Build Coastguard Worker    update_target = name + ".update"
47*60517a1eSAndroid Build Coastguard Worker
48*60517a1eSAndroid Build Coastguard Worker    args = [
49*60517a1eSAndroid Build Coastguard Worker        "--custom-compile-command='bazel run //{}:{}'".format(pkg, update_target),
50*60517a1eSAndroid Build Coastguard Worker        "--generate-hashes",
51*60517a1eSAndroid Build Coastguard Worker        "--emit-index-url",
52*60517a1eSAndroid Build Coastguard Worker        "--no-strip-extras",
53*60517a1eSAndroid Build Coastguard Worker        "--python=$(PYTHON3)",
54*60517a1eSAndroid Build Coastguard Worker    ] + [
55*60517a1eSAndroid Build Coastguard Worker        "$(location {})".format(src)
56*60517a1eSAndroid Build Coastguard Worker        for src in srcs
57*60517a1eSAndroid Build Coastguard Worker    ]
58*60517a1eSAndroid Build Coastguard Worker    if upgrade:
59*60517a1eSAndroid Build Coastguard Worker        args.append("--upgrade")
60*60517a1eSAndroid Build Coastguard Worker    if universal:
61*60517a1eSAndroid Build Coastguard Worker        args.append("--universal")
62*60517a1eSAndroid Build Coastguard Worker    args.append("--output-file=$@")
63*60517a1eSAndroid Build Coastguard Worker    cmd = "$(UV_BIN) pip compile " + " ".join(args)
64*60517a1eSAndroid Build Coastguard Worker
65*60517a1eSAndroid Build Coastguard Worker    # Check if the output file already exists, if yes, first copy it to the
66*60517a1eSAndroid Build Coastguard Worker    # output file location in order to make `uv` not change the requirements if
67*60517a1eSAndroid Build Coastguard Worker    # we are just running the command.
68*60517a1eSAndroid Build Coastguard Worker    if native.glob([out]):
69*60517a1eSAndroid Build Coastguard Worker        cmd = "cp -v $(location {}) $@; {}".format(out, cmd)
70*60517a1eSAndroid Build Coastguard Worker        srcs.append(out)
71*60517a1eSAndroid Build Coastguard Worker
72*60517a1eSAndroid Build Coastguard Worker    native.genrule(
73*60517a1eSAndroid Build Coastguard Worker        name = name,
74*60517a1eSAndroid Build Coastguard Worker        srcs = srcs,
75*60517a1eSAndroid Build Coastguard Worker        outs = [out + ".new"],
76*60517a1eSAndroid Build Coastguard Worker        cmd_bash = cmd,
77*60517a1eSAndroid Build Coastguard Worker        tags = [
78*60517a1eSAndroid Build Coastguard Worker            "local",
79*60517a1eSAndroid Build Coastguard Worker            "manual",
80*60517a1eSAndroid Build Coastguard Worker            "no-cache",
81*60517a1eSAndroid Build Coastguard Worker        ],
82*60517a1eSAndroid Build Coastguard Worker        target_compatible_with = _REQUIREMENTS_TARGET_COMPATIBLE_WITH,
83*60517a1eSAndroid Build Coastguard Worker        toolchains = [
84*60517a1eSAndroid Build Coastguard Worker            Label("//python/uv:current_toolchain"),
85*60517a1eSAndroid Build Coastguard Worker            Label("//python:current_py_toolchain"),
86*60517a1eSAndroid Build Coastguard Worker        ],
87*60517a1eSAndroid Build Coastguard Worker    )
88*60517a1eSAndroid Build Coastguard Worker    if python_version:
89*60517a1eSAndroid Build Coastguard Worker        py_binary_rule = lambda *args, **kwargs: transition_py_binary(python_version = python_version, *args, **kwargs)
90*60517a1eSAndroid Build Coastguard Worker    else:
91*60517a1eSAndroid Build Coastguard Worker        py_binary_rule = py_binary
92*60517a1eSAndroid Build Coastguard Worker
93*60517a1eSAndroid Build Coastguard Worker    # Write a script that can be used for updating the in-tree version of the
94*60517a1eSAndroid Build Coastguard Worker    # requirements file
95*60517a1eSAndroid Build Coastguard Worker    write_file(
96*60517a1eSAndroid Build Coastguard Worker        name = name + ".update_gen",
97*60517a1eSAndroid Build Coastguard Worker        out = update_target + ".py",
98*60517a1eSAndroid Build Coastguard Worker        content = [
99*60517a1eSAndroid Build Coastguard Worker            "from os import environ",
100*60517a1eSAndroid Build Coastguard Worker            "from pathlib import Path",
101*60517a1eSAndroid Build Coastguard Worker            "from sys import stderr",
102*60517a1eSAndroid Build Coastguard Worker            "",
103*60517a1eSAndroid Build Coastguard Worker            'src = Path(environ["REQUIREMENTS_FILE"])',
104*60517a1eSAndroid Build Coastguard Worker            'assert src.exists(), f"the {src} file does not exist"',
105*60517a1eSAndroid Build Coastguard Worker            'dst = Path(environ["BUILD_WORKSPACE_DIRECTORY"]) / "{}" / "{}"'.format(pkg, out),
106*60517a1eSAndroid Build Coastguard Worker            'print(f"Writing requirements contents\\n  from {src.absolute()}\\n  to {dst.absolute()}", file=stderr)',
107*60517a1eSAndroid Build Coastguard Worker            "dst.write_text(src.read_text())",
108*60517a1eSAndroid Build Coastguard Worker            'print("Success!", file=stderr)',
109*60517a1eSAndroid Build Coastguard Worker        ],
110*60517a1eSAndroid Build Coastguard Worker    )
111*60517a1eSAndroid Build Coastguard Worker
112*60517a1eSAndroid Build Coastguard Worker    py_binary_rule(
113*60517a1eSAndroid Build Coastguard Worker        name = update_target,
114*60517a1eSAndroid Build Coastguard Worker        srcs = [update_target + ".py"],
115*60517a1eSAndroid Build Coastguard Worker        main = update_target + ".py",
116*60517a1eSAndroid Build Coastguard Worker        data = [name],
117*60517a1eSAndroid Build Coastguard Worker        env = {
118*60517a1eSAndroid Build Coastguard Worker            "REQUIREMENTS_FILE": "$(rootpath {})".format(name),
119*60517a1eSAndroid Build Coastguard Worker        },
120*60517a1eSAndroid Build Coastguard Worker        tags = ["manual"],
121*60517a1eSAndroid Build Coastguard Worker    )
122