mirror of
https://github.com/astral-sh/uv.git
synced 2026-05-07 01:11:18 -04:00
8093dfcaa9
Should require https://github.com/astral-sh/uv/pull/18457
315 lines
11 KiB
Python
Executable File
315 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""Install `pylint` and `numpy` into the system Python.
|
|
|
|
To run locally, create a venv with seed packages.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def install_package(*, uv: str, package: str, version: str = None):
|
|
"""Install a package into the system Python."""
|
|
|
|
requirement = f"{package}=={version}" if version is not None else package
|
|
|
|
logging.info(f"Installing the package `{requirement}`.")
|
|
subprocess.run(
|
|
[uv, "pip", "install", requirement, "--system"] + allow_externally_managed,
|
|
cwd=temp_dir,
|
|
check=True,
|
|
)
|
|
|
|
logging.info(f"Checking that `{package}` can be imported with `{sys.executable}`.")
|
|
code = subprocess.run(
|
|
[sys.executable, "-c", f"import {package}"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode != 0:
|
|
raise Exception(f"Could not import {package}.")
|
|
|
|
code = subprocess.run([uv, "pip", "show", package, "--system"])
|
|
if code.returncode != 0:
|
|
raise Exception(f"Could not show {package}.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
|
|
parser = argparse.ArgumentParser(description="Check a Python interpreter.")
|
|
parser.add_argument("--uv", help="Path to a uv binary.")
|
|
parser.add_argument(
|
|
"--externally-managed",
|
|
action="store_true",
|
|
help="Set if the Python installation has an EXTERNALLY-MANAGED marker.",
|
|
)
|
|
parser.add_argument(
|
|
"--python",
|
|
required=False,
|
|
help="Set if the system Python version must be explicitly specified, e.g., for prereleases.",
|
|
)
|
|
parser.add_argument(
|
|
"--check-python-version",
|
|
required=False,
|
|
help="Verify that this tool has been started with the specified python version. Omitting the patch number will match any patch number.",
|
|
)
|
|
parser.add_argument(
|
|
"--check-path",
|
|
required=False,
|
|
action="store_true",
|
|
help="Attempt to verify that the PATH is set up so that this tool's python will match the python version that uv would automatically pick up.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
uv: str = os.path.abspath(args.uv) if args.uv else "uv"
|
|
allow_externally_managed = (
|
|
["--break-system-packages"] if args.externally_managed else []
|
|
)
|
|
python = ["--python", args.python] if args.python else []
|
|
|
|
# Pin packages to the last versions that support older Python interpreters.
|
|
if sys.version_info < (3, 7):
|
|
pylint_version = "2.12.2"
|
|
numpy_version = "1.19.5"
|
|
pydantic_core_version = None
|
|
elif sys.version_info < (3, 8):
|
|
pylint_version = "2.17.7"
|
|
numpy_version = "1.21.6"
|
|
pydantic_core_version = "2.14.6"
|
|
else:
|
|
pylint_version = None
|
|
numpy_version = None
|
|
pydantic_core_version = None
|
|
|
|
pylint_requirement = (
|
|
f"pylint=={pylint_version}" if pylint_version is not None else "pylint"
|
|
)
|
|
|
|
if args.check_python_version:
|
|
version = ".".join(map(str, sys.version_info[:3]))
|
|
if args.check_python_version != version and not version.startswith(
|
|
args.check_python_version + "."
|
|
):
|
|
raise Exception(
|
|
f"Expected to be running {args.check_python_version} but we are on {version}."
|
|
)
|
|
|
|
if args.check_path:
|
|
process = subprocess.run(
|
|
[
|
|
"python",
|
|
"-c",
|
|
"import os, sys; sys.stdout.buffer.write(os.fsencode(sys.executable))",
|
|
],
|
|
check=True,
|
|
stdout=subprocess.PIPE,
|
|
)
|
|
system_python_path = os.path.normcase(
|
|
os.path.normpath(os.fsdecode(process.stdout))
|
|
)
|
|
our_python_path = os.path.normcase(os.path.normpath(sys.executable))
|
|
|
|
if our_python_path != system_python_path:
|
|
raise Exception(
|
|
f"Script was ran with {our_python_path} but `python` resolves to {system_python_path}"
|
|
)
|
|
|
|
# Ensure that pip is available (e.g., the Chainguard distroless image ships
|
|
# Python but not pip).
|
|
try:
|
|
import pip # noqa: F401
|
|
except ModuleNotFoundError:
|
|
logging.info("pip not found, running ensurepip...")
|
|
subprocess.run(
|
|
[sys.executable, "-m", "ensurepip"],
|
|
check=True,
|
|
)
|
|
|
|
# Create a temporary directory.
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
# Ensure that the package (`pylint`) isn't installed.
|
|
logging.info("Checking that `pylint` isn't installed.")
|
|
code = subprocess.run(
|
|
[sys.executable, "-m", "pip", "show", "pylint"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode == 0:
|
|
raise Exception("The package `pylint` is installed (but shouldn't be).")
|
|
|
|
# Install the package (`pylint`).
|
|
logging.info("Installing the package `pylint`.")
|
|
subprocess.run(
|
|
[uv, "pip", "install", pylint_requirement, "--system", "--verbose"]
|
|
+ allow_externally_managed
|
|
+ python,
|
|
cwd=temp_dir,
|
|
check=True,
|
|
)
|
|
|
|
# Ensure that the package (`pylint`) is installed.
|
|
logging.info(
|
|
f"Checking that `pylint` is installed with `{sys.executable} -m pip`."
|
|
)
|
|
code = subprocess.run(
|
|
[sys.executable, "-m", "pip", "show", "pylint"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode != 0:
|
|
raise Exception("The package `pylint` isn't installed (but should be).")
|
|
|
|
logging.info("Checking that `pylint` is in the path.")
|
|
if shutil.which("pylint") is None:
|
|
raise Exception("The package `pylint` isn't in the path.")
|
|
|
|
# Uninstall the package (`pylint`).
|
|
logging.info("Uninstalling the package `pylint`.")
|
|
subprocess.run(
|
|
[uv, "pip", "uninstall", "pylint", "--system"]
|
|
+ allow_externally_managed
|
|
+ python,
|
|
cwd=temp_dir,
|
|
check=True,
|
|
)
|
|
|
|
# Ensure that the package (`pylint`) isn't installed.
|
|
logging.info("Checking that `pylint` isn't installed.")
|
|
code = subprocess.run(
|
|
[sys.executable, "-m", "pip", "show", "pylint"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode == 0:
|
|
raise Exception("The package `pylint` is installed (but shouldn't be).")
|
|
|
|
# Create a virtual environment with `uv`.
|
|
logging.info("Creating virtual environment with `uv`...")
|
|
subprocess.run(
|
|
[uv, "venv", ".venv", "--seed", "--python", sys.executable],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
)
|
|
|
|
if os.name == "nt":
|
|
executable = os.path.join(temp_dir, ".venv", "Scripts", "python.exe")
|
|
else:
|
|
executable = os.path.join(temp_dir, ".venv", "bin", "python")
|
|
|
|
logging.info("Querying virtual environment...")
|
|
subprocess.run(
|
|
[executable, "--version"],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
)
|
|
|
|
logging.info("Installing into `uv` virtual environment...")
|
|
|
|
# Disable the `CONDA_PREFIX` and `VIRTUAL_ENV` environment variables, so that
|
|
# we only rely on virtual environment discovery via the `.venv` directory.
|
|
# Our "system Python" here might itself be a Conda environment!
|
|
env = os.environ.copy()
|
|
env["CONDA_PREFIX"] = ""
|
|
env["VIRTUAL_ENV"] = ""
|
|
subprocess.run(
|
|
[uv, "pip", "install", pylint_requirement, "--verbose"],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
# Ensure that the package (`pylint`) isn't installed globally.
|
|
logging.info("Checking that `pylint` isn't installed.")
|
|
code = subprocess.run(
|
|
[sys.executable, "-m", "pip", "show", "pylint"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode == 0:
|
|
raise Exception(
|
|
"The package `pylint` is installed globally (but shouldn't be)."
|
|
)
|
|
|
|
# Ensure that the package (`pylint`) is installed in the virtual environment.
|
|
logging.info("Checking that `pylint` is installed.")
|
|
code = subprocess.run(
|
|
[executable, "-m", "pip", "show", "pylint"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode != 0:
|
|
raise Exception(
|
|
"The package `pylint` isn't installed in the virtual environment."
|
|
)
|
|
|
|
# Uninstall the package (`pylint`).
|
|
logging.info("Uninstalling the package `pylint`.")
|
|
subprocess.run(
|
|
[uv, "pip", "uninstall", "pylint", "--verbose"],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
# Ensure that the package (`pylint`) isn't installed in the virtual environment.
|
|
logging.info("Checking that `pylint` isn't installed.")
|
|
code = subprocess.run(
|
|
[executable, "-m", "pip", "show", "pylint"],
|
|
cwd=temp_dir,
|
|
)
|
|
if code.returncode == 0:
|
|
raise Exception(
|
|
"The package `pylint` is installed in the virtual environment (but shouldn't be)."
|
|
)
|
|
|
|
# Attempt to install NumPy.
|
|
# This ensures that we can successfully install a package with native libraries.
|
|
#
|
|
# NumPy doesn't distribute wheels for Python 3.13 or GraalPy (at time of writing).
|
|
if sys.version_info < (3, 13) and sys.implementation.name != "graalpy":
|
|
install_package(uv=uv, package="numpy", version=numpy_version)
|
|
|
|
# Attempt to install `pydantic_core`.
|
|
# This ensures that we can successfully install and recognize a package that may
|
|
# be installed into `platlib`.
|
|
#
|
|
# `pydantic_core` doesn't distribute wheels for non-CPython interpreters, nor
|
|
# for Python 3.13 (at time of writing).
|
|
if (
|
|
sys.version_info >= (3, 7)
|
|
and sys.version_info < (3, 13)
|
|
and sys.implementation.name == "cpython"
|
|
):
|
|
install_package(
|
|
uv=uv, package="pydantic_core", version=pydantic_core_version
|
|
)
|
|
|
|
# Next, create a virtual environment with `venv`, to ensure that `uv` can
|
|
# interoperate with `venv` virtual environments.
|
|
shutil.rmtree(os.path.join(temp_dir, ".venv"))
|
|
logging.info("Creating virtual environment with `venv`...")
|
|
subprocess.run(
|
|
[sys.executable, "-m", "venv", ".venv"],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
)
|
|
|
|
# Install the package (`pylint`) into the virtual environment.
|
|
logging.info("Installing into `venv` virtual environment...")
|
|
subprocess.run(
|
|
[uv, "pip", "install", pylint_requirement, "--verbose"],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
env=env,
|
|
)
|
|
|
|
# Uninstall the package (`pylint`).
|
|
logging.info("Uninstalling the package `pylint`.")
|
|
subprocess.run(
|
|
[uv, "pip", "uninstall", "pylint", "--verbose"],
|
|
cwd=temp_dir,
|
|
check=True,
|
|
env=env,
|
|
)
|