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