Skip to content

[build] remove cmake cache and reconfigure again if it is invalid #156958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions tools/setup_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

import os
import sys
import warnings


def which(thefile: str) -> str | None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just wrap this with typing_extensions.deprecated?

Copy link
Collaborator Author

@XuehaiPan XuehaiPan Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this, but typing-extensions is not installed in CI environment because build isolation is disabled.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? It's in the build_requires? sigh...

warnings.warn(
"tools.setup_helpers.which is deprecated and will be removed in a future version. "
"Use shutil.which instead.",
FutureWarning,
stacklevel=2,
)

path = os.environ.get("PATH", os.defpath).split(os.pathsep)
for d in path:
fname = os.path.join(d, thefile)
Expand Down
83 changes: 59 additions & 24 deletions tools/setup_helpers/cmake.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
"Manages CMake."
"""Manages CMake."""

from __future__ import annotations

import functools
import json
import multiprocessing
import os
import platform
import shutil
import sys
import sysconfig
from pathlib import Path
from subprocess import CalledProcessError, check_call, check_output, DEVNULL
from typing import cast

from . import which
from .cmake_utils import CMakeValue, get_cmake_cache_variables_from_file
from .env import BUILD_DIR, check_negative_env_flag, IS_64BIT, IS_DARWIN, IS_WINDOWS

Expand All @@ -37,10 +38,14 @@ def _mkdir_p(d: str) -> None:
) from e


# Print to stderr
eprint = functools.partial(print, file=sys.stderr, flush=True)


# Ninja
# Use ninja if it is on the PATH. Previous version of PyTorch required the
# ninja python package, but we no longer use it, so we do not have to import it
USE_NINJA = not check_negative_env_flag("USE_NINJA") and which("ninja") is not None
USE_NINJA = bool(not check_negative_env_flag("USE_NINJA") and shutil.which("ninja"))
if "CMAKE_GENERATOR" in os.environ:
USE_NINJA = os.environ["CMAKE_GENERATOR"].lower() == "ninja"

Expand All @@ -61,15 +66,24 @@ def _cmake_cache_file(self) -> str:
"""
return os.path.join(self.build_dir, "CMakeCache.txt")

@property
def _ninja_build_file(self) -> str:
r"""Returns the path to build.ninja.

Returns:
string: The path to build.ninja.
"""
return os.path.join(self.build_dir, "build.ninja")

@staticmethod
def _get_cmake_command() -> str:
"Returns cmake command."
"""Returns cmake command."""

cmake_command = "cmake"
if IS_WINDOWS:
return cmake_command
cmake3_version = CMake._get_version(which("cmake3"))
cmake_version = CMake._get_version(which("cmake"))
cmake3_version = CMake._get_version(shutil.which("cmake3"))
cmake_version = CMake._get_version(shutil.which("cmake"))

_cmake_min_version = Version("3.27.0")
if all(
Expand Down Expand Up @@ -115,10 +129,10 @@ def _get_version(cmd: str | None) -> Version | None:
raise RuntimeError(f"Failed to get CMake version from command: {cmd}")

def run(self, args: list[str], env: dict[str, str]) -> None:
"Executes cmake with arguments and an environment."
"""Executes cmake with arguments and an environment."""

command = [self._cmake_command] + args
print(" ".join(command))
eprint(" ".join(command))
try:
check_call(command, cwd=self.build_dir, env=env)
except (CalledProcessError, KeyboardInterrupt):
Expand All @@ -129,7 +143,7 @@ def run(self, args: list[str], env: dict[str, str]) -> None:

@staticmethod
def defines(args: list[str], **kwargs: CMakeValue) -> None:
"Adds definitions to a cmake argument list."
"""Adds definitions to a cmake argument list."""
for key, value in sorted(kwargs.items()):
if value is not None:
args.append(f"-D{key}={value}")
Expand All @@ -151,14 +165,31 @@ def generate(
my_env: dict[str, str],
rerun: bool,
) -> None:
"Runs cmake to generate native build files."
"""Runs cmake to generate native build files."""

if rerun and os.path.isfile(self._cmake_cache_file):
os.remove(self._cmake_cache_file)

ninja_build_file = os.path.join(self.build_dir, "build.ninja")
if os.path.exists(self._cmake_cache_file) and not (
USE_NINJA and not os.path.exists(ninja_build_file)
cmake_cache_file_available = os.path.exists(self._cmake_cache_file)
if cmake_cache_file_available:
cmake_cache_variables = self.get_cmake_cache_variables()
make_program: str | None = cmake_cache_variables.get("CMAKE_MAKE_PROGRAM") # type: ignore[assignment]
if make_program and not shutil.which(make_program):
# CMakeCache.txt exists, but the make program (e.g., ninja) does not.
# See also: https://github.com/astral-sh/uv/issues/14269
# This can happen if building with PEP-517 build isolation, where `ninja` was
# installed in the isolated environment of the previous build run, but it has been
# removed. The `ninja` executable with an old absolute path not available anymore.
eprint(
"!!!WARNING!!!: CMakeCache.txt exists, "
f"but CMAKE_MAKE_PROGRAM ({make_program!r}) does not exist. "
"Clearing CMake cache."
)
self.clear_cache()
cmake_cache_file_available = False

if cmake_cache_file_available and (
not USE_NINJA or os.path.exists(self._ninja_build_file)
):
# Everything's in place. Do not rerun.
return
Expand All @@ -172,9 +203,9 @@ def generate(
generator = os.getenv("CMAKE_GENERATOR", "Visual Studio 16 2019")
supported = ["Visual Studio 16 2019", "Visual Studio 17 2022"]
if generator not in supported:
print("Unsupported `CMAKE_GENERATOR`: " + generator)
print("Please set it to one of the following values: ")
print("\n".join(supported))
eprint("Unsupported `CMAKE_GENERATOR`: " + generator)
eprint("Please set it to one of the following values: ")
eprint("\n".join(supported))
sys.exit(1)
args.append("-G" + generator)
toolset_dict = {}
Expand All @@ -183,7 +214,7 @@ def generate(
toolset_dict["version"] = toolset_version
curr_toolset = os.getenv("VCToolsVersion")
if curr_toolset is None:
print(
eprint(
"When you specify `CMAKE_GENERATOR_TOOLSET_VERSION`, you must also "
"activate the vs environment of this version. Please read the notes "
"in the build steps carefully."
Expand Down Expand Up @@ -328,7 +359,7 @@ def generate(
# error if the user also attempts to set these CMAKE options directly.
specified_cmake__options = set(build_options).intersection(cmake__options)
if len(specified_cmake__options) > 0:
print(
eprint(
", ".join(specified_cmake__options)
+ " should not be specified in the environment variable. They are directly set by PyTorch build script."
)
Expand Down Expand Up @@ -357,11 +388,8 @@ def generate(
my_env[env_var_name] = str(my_env[env_var_name].encode("utf-8"))
except UnicodeDecodeError as e:
shex = ":".join(f"{ord(c):02x}" for c in my_env[env_var_name])
print(
f"Invalid ENV[{env_var_name}] = {shex}",
file=sys.stderr,
)
print(e, file=sys.stderr)
eprint(f"Invalid ENV[{env_var_name}] = {shex}")
eprint(e)
# According to the CMake manual, we should pass the arguments first,
# and put the directory as the last element. Otherwise, these flags
# may not be passed correctly.
Expand All @@ -372,7 +400,7 @@ def generate(
self.run(args, env=my_env)

def build(self, my_env: dict[str, str]) -> None:
"Runs cmake to build binaries."
"""Runs cmake to build binaries."""

from .env import build_type

Expand Down Expand Up @@ -410,3 +438,10 @@ def build(self, my_env: dict[str, str]) -> None:
# CMake 3.12 provides a '-j' option.
build_args += ["-j", max_jobs]
self.run(build_args, my_env)

def clear_cache(self) -> None:
"""Clears the CMake cache."""
if os.path.isfile(self._cmake_cache_file):
os.remove(self._cmake_cache_file)
if os.path.isfile(self._ninja_build_file):
os.remove(self._ninja_build_file)
Loading