blob: 93c55c825f858d4f26e9eef11380b41544f6213d [file] [log] [blame]
"Set defaults for the pip-compile command to run it under Bazel"
import os
import sys
from shutil import copyfile
from piptools.scripts.compile import cli
if len(sys.argv) < 4:
print(
"Expected at least two arguments: requirements_in requirements_out",
file=sys.stderr,
)
sys.exit(1)
requirements_in = os.path.relpath(sys.argv.pop(1))
requirements_txt = sys.argv.pop(1)
update_target_label = sys.argv.pop(1)
# Before loading click, set the locale for its parser.
# If it leaks through to the system setting, it may fail:
# RuntimeError: Click will abort further execution because Python 3 was configured to use ASCII
# as encoding for the environment. Consult https://click.palletsprojects.com/python3/ for
# mitigation steps.
os.environ["LC_ALL"] = "C.UTF-8"
os.environ["LANG"] = "C.UTF-8"
UPDATE = True
# Detect if we are running under `bazel test`
if "TEST_TMPDIR" in os.environ:
UPDATE = False
# pip-compile wants the cache files to be writeable, but if we point
# to the real user cache, Bazel sandboxing makes the file read-only
# and we fail.
# In theory this makes the test more hermetic as well.
sys.argv.append("--cache-dir")
sys.argv.append(os.environ["TEST_TMPDIR"])
# Make a copy for pip-compile to read and mutate
requirements_out = os.path.join(
os.environ["TEST_TMPDIR"], os.path.basename(requirements_txt) + ".out"
)
copyfile(requirements_txt, requirements_out)
elif "BUILD_WORKSPACE_DIRECTORY" in os.environ:
# This value, populated when running under `bazel run`, is a path to the
# "root of the workspace where the build was run."
# This matches up with the values passed in via the macro using the 'rootpath' Make variable,
# which for source files provides a path "relative to your workspace root."
#
# Changing to the WORKSPACE root avoids 'file not found' errors when the `.update` target is run
# from different directories within the WORKSPACE.
os.chdir(os.environ["BUILD_WORKSPACE_DIRECTORY"])
else:
err_msg = (
"Expected to find BUILD_WORKSPACE_DIRECTORY (running under `bazel run`) or "
"TEST_TMPDIR (running under `bazel test`) in environment."
)
print(
err_msg,
file=sys.stderr,
)
sys.exit(1)
update_target_pkg = "/".join(requirements_in.split("/")[:-1])
# $(rootpath) in the workspace root gives ./requirements.in
if update_target_pkg == ".":
update_target_pkg = ""
update_command = os.getenv("CUSTOM_COMPILE_COMMAND") or "bazel run %s" % (
update_target_label,
)
os.environ["CUSTOM_COMPILE_COMMAND"] = update_command
os.environ["PIP_CONFIG_FILE"] = os.getenv("PIP_CONFIG_FILE") or os.devnull
sys.argv.append("--generate-hashes")
sys.argv.append("--output-file")
sys.argv.append(requirements_txt if UPDATE else requirements_out)
sys.argv.append(requirements_in)
if UPDATE:
print("Updating " + requirements_txt)
cli()
else:
# cli will exit(0) on success
try:
print("Checking " + requirements_txt)
cli()
print("cli() should exit", file=sys.stderr)
sys.exit(1)
except SystemExit as e:
if e.code == 2:
print(
"pip-compile exited with code 2. This means that pip-compile found "
"incompatible requirements or could not find a version that matches "
f"the install requirement in {requirements_in}.",
file=sys.stderr,
)
sys.exit(1)
elif e.code == 0:
golden = open(requirements_txt).readlines()
out = open(requirements_out).readlines()
if golden != out:
import difflib
print("".join(difflib.unified_diff(golden, out)), file=sys.stderr)
print(
"Lock file out of date. Run '" + update_command + "' to update.",
file=sys.stderr,
)
sys.exit(1)
sys.exit(0)
else:
print(
f"pip-compile unexpectedly exited with code {e.code}.", file=sys.stderr
)
sys.exit(1)