From 747c22d7c61df996b071947a98f94bf8d34ce54b Mon Sep 17 00:00:00 2001 From: Masahiro Hiramori Date: Mon, 2 Nov 2026 09:47:03 +0000 Subject: [PATCH] Support installing ExecuTorch in uv virtual environments Fresh uv virtual environments do not include pip. Use pip when available, or fall back to uv pip targeting the running Python interpreter. Cache the choice so installing pip as a build dependency does not change installers midway through the process. Use the shared command for dependency bootstrap and package installation, preserve pip's index selection behavior, and remove the redundant pip-only upgrade strategy option. Document how to set up a uv environment. --- .../using-executorch-building-from-source.md | 11 ++++++ install_executorch.py | 8 ++--- install_requirements.py | 34 ++++++------------- install_utils.py | 32 +++++++++++++++-- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/docs/source/using-executorch-building-from-source.md b/docs/source/using-executorch-building-from-source.md index e96d643c209..535af51f05d 100644 --- a/docs/source/using-executorch-building-from-source.md +++ b/docs/source/using-executorch-building-from-source.md @@ -61,6 +61,17 @@ portability details. conda activate executorch ``` +Alternatively, on macOS or Linux, create and activate a virtual environment with uv: + +```bash +uv venv --python 3.12 .venv +source .venv/bin/activate +``` + +The installation scripts use pip when it is available in the current Python +environment, or fall back to `uv pip` when uv is on `PATH`. There is no need to +seed pip before running the installer. + > **_NOTE:_** Additional Windows Setup > > ExecuTorch requires symlinks to be enabled to build the Python components. To enable symlinks, run the following command before cloning the repository. Missing symlinks will manifest as an error related to `version.py` when running `pip install .`. See [src/README.md](https://github.com/pytorch/executorch/blob/main/src/README.md) for more information. diff --git a/install_executorch.py b/install_executorch.py index 276841364e6..45714f52c81 100644 --- a/install_executorch.py +++ b/install_executorch.py @@ -20,6 +20,7 @@ install_requirements, python_is_compatible, ) +from install_utils import get_package_install_command # Set up logging logging.basicConfig( @@ -237,12 +238,7 @@ def main(args): extras = ",".join(dict.fromkeys(args.optional_dependency)) package_spec = f".[{extras}]" cmd = ( - [ - sys.executable, - "-m", - "pip", - "install", - ] + [*get_package_install_command()] + (["--editable"] if args.editable else []) + [ package_spec, diff --git a/install_requirements.py b/install_requirements.py index 285c4bf281f..76e9789b577 100644 --- a/install_requirements.py +++ b/install_requirements.py @@ -11,7 +11,12 @@ import subprocess import sys -from install_utils import determine_torch_url, is_intel_mac_os, python_is_compatible +from install_utils import ( + determine_torch_url, + get_package_install_command, + is_intel_mac_os, + python_is_compatible, +) # The pip repository that hosts nightly torch packages. # This will be dynamically set based on CUDA availability and CUDA backend enabled/disabled. @@ -114,10 +119,7 @@ def install_requirements(use_pytorch_nightly): # versions on the provided URL if they aren't available on the default URL. subprocess.run( [ - sys.executable, - "-m", - "pip", - "install", + *get_package_install_command(), "-r", "requirements-dev.txt", *TORCH_PACKAGE, @@ -142,10 +144,7 @@ def install_requirements(use_pytorch_nightly): ) subprocess.run( [ - sys.executable, - "-m", - "pip", - "install", + *get_package_install_command(), "--no-build-isolation", "sentencepiece==0.2.2", ], @@ -170,10 +169,7 @@ def install_requirements(use_pytorch_nightly): new_env["CMAKE_POLICY_VERSION_MINIMUM"] = "3.5" subprocess.run( [ - sys.executable, - "-m", - "pip", - "install", + *get_package_install_command(), # Without --no-build-isolation, setup.py can't find the torch module. "--no-build-isolation", *LOCAL_REQUIREMENTS, @@ -214,10 +210,7 @@ def install_optional_example_requirements(use_pytorch_nightly): # Then install domain libraries subprocess.run( [ - sys.executable, - "-m", - "pip", - "install", + *get_package_install_command(), *DOMAIN_LIBRARIES, "--extra-index-url", torch_url, @@ -229,18 +222,13 @@ def install_optional_example_requirements(use_pytorch_nightly): print("Installing packages in requirements-examples.txt") subprocess.run( [ - sys.executable, - "-m", - "pip", - "install", + *get_package_install_command(), "-r", "requirements-examples.txt", *cu134_packages, "--extra-index-url", torch_url, *torchao_index, - "--upgrade-strategy", - "only-if-needed", ], check=True, ) diff --git a/install_utils.py b/install_utils.py index 4e1397413b3..81b3d7faf6e 100644 --- a/install_utils.py +++ b/install_utils.py @@ -6,6 +6,7 @@ # LICENSE file in the root directory of this source tree. import functools +import importlib.util import os import platform import re @@ -25,6 +26,33 @@ ) +@functools.lru_cache(maxsize=1) +def get_package_install_command() -> Tuple[str, ...]: + """Choose an installer once, even if requirements later install pip.""" + if importlib.util.find_spec("pip") is not None: + return (sys.executable, "-m", "pip", "install") + + uv = shutil.which("uv") + if uv is not None: + return ( + uv, + "pip", + "install", + "--python", + sys.executable, + # Match pip's selection across the PyTorch and PyPI indexes. + "--index-strategy", + "unsafe-best-match", + ) + + raise RuntimeError( + "Neither pip nor uv is available. " + f'Install pip with "{sys.executable} -m ensurepip --upgrade", ' + "or install uv and ensure it is on PATH: " + "https://docs.astral.sh/uv/getting-started/installation/" + ) + + def is_cmake_option_on( cmake_configuration_args: List[str], var_name: str, default: bool ) -> bool: @@ -397,9 +425,7 @@ def python_is_compatible(): try: import packaging except ImportError: - subprocess.run( - [sys.executable, "-m", "pip", "install", "packaging"], check=True - ) + subprocess.run([*get_package_install_command(), "packaging"], check=True) # Compare the current python version to the range in version_specifier. Exits # with status 1 if the version is not compatible, or with status 0 if the # version is compatible or the logic itself fails.