1- "Manages CMake."
1+ """ Manages CMake."" "
22
33from __future__ import annotations
44
5+ import functools
56import json
67import multiprocessing
78import os
89import platform
10+ import shutil
911import sys
1012import sysconfig
1113from pathlib import Path
1214from subprocess import CalledProcessError , check_call , check_output , DEVNULL
1315from typing import cast
1416
15- from . import which
1617from .cmake_utils import CMakeValue , get_cmake_cache_variables_from_file
1718from .env import BUILD_DIR , check_negative_env_flag , IS_64BIT , IS_DARWIN , IS_WINDOWS
1819
@@ -37,10 +38,14 @@ def _mkdir_p(d: str) -> None:
3738 ) from e
3839
3940
41+ # Print to stderr
42+ eprint = functools .partial (print , file = sys .stderr , flush = True )
43+
44+
4045# Ninja
4146# Use ninja if it is on the PATH. Previous version of PyTorch required the
4247# ninja python package, but we no longer use it, so we do not have to import it
43- USE_NINJA = not check_negative_env_flag ("USE_NINJA" ) and which ("ninja" ) is not None
48+ USE_NINJA = bool ( not check_negative_env_flag ("USE_NINJA" ) and shutil . which ("ninja" ))
4449if "CMAKE_GENERATOR" in os .environ :
4550 USE_NINJA = os .environ ["CMAKE_GENERATOR" ].lower () == "ninja"
4651
@@ -61,15 +66,24 @@ def _cmake_cache_file(self) -> str:
6166 """
6267 return os .path .join (self .build_dir , "CMakeCache.txt" )
6368
69+ @property
70+ def _ninja_build_file (self ) -> str :
71+ r"""Returns the path to build.ninja.
72+
73+ Returns:
74+ string: The path to build.ninja.
75+ """
76+ return os .path .join (self .build_dir , "build.ninja" )
77+
6478 @staticmethod
6579 def _get_cmake_command () -> str :
66- "Returns cmake command."
80+ """ Returns cmake command."" "
6781
6882 cmake_command = "cmake"
6983 if IS_WINDOWS :
7084 return cmake_command
71- cmake3_version = CMake ._get_version (which ("cmake3" ))
72- cmake_version = CMake ._get_version (which ("cmake" ))
85+ cmake3_version = CMake ._get_version (shutil . which ("cmake3" ))
86+ cmake_version = CMake ._get_version (shutil . which ("cmake" ))
7387
7488 _cmake_min_version = Version ("3.27.0" )
7589 if all (
@@ -115,10 +129,10 @@ def _get_version(cmd: str | None) -> Version | None:
115129 raise RuntimeError (f"Failed to get CMake version from command: { cmd } " )
116130
117131 def run (self , args : list [str ], env : dict [str , str ]) -> None :
118- "Executes cmake with arguments and an environment."
132+ """ Executes cmake with arguments and an environment."" "
119133
120134 command = [self ._cmake_command ] + args
121- print (" " .join (command ))
135+ eprint (" " .join (command ))
122136 try :
123137 check_call (command , cwd = self .build_dir , env = env )
124138 except (CalledProcessError , KeyboardInterrupt ):
@@ -129,7 +143,7 @@ def run(self, args: list[str], env: dict[str, str]) -> None:
129143
130144 @staticmethod
131145 def defines (args : list [str ], ** kwargs : CMakeValue ) -> None :
132- "Adds definitions to a cmake argument list."
146+ """ Adds definitions to a cmake argument list."" "
133147 for key , value in sorted (kwargs .items ()):
134148 if value is not None :
135149 args .append (f"-D{ key } ={ value } " )
@@ -151,14 +165,31 @@ def generate(
151165 my_env : dict [str , str ],
152166 rerun : bool ,
153167 ) -> None :
154- "Runs cmake to generate native build files."
168+ """ Runs cmake to generate native build files."" "
155169
156170 if rerun and os .path .isfile (self ._cmake_cache_file ):
157171 os .remove (self ._cmake_cache_file )
158172
159- ninja_build_file = os .path .join (self .build_dir , "build.ninja" )
160- if os .path .exists (self ._cmake_cache_file ) and not (
161- USE_NINJA and not os .path .exists (ninja_build_file )
173+ cmake_cache_file_available = os .path .exists (self ._cmake_cache_file )
174+ if cmake_cache_file_available :
175+ cmake_cache_variables = self .get_cmake_cache_variables ()
176+ make_program : str | None = cmake_cache_variables .get ("CMAKE_MAKE_PROGRAM" ) # type: ignore[assignment]
177+ if make_program and not shutil .which (make_program ):
178+ # CMakeCache.txt exists, but the make program (e.g., ninja) does not.
179+ # See also: https://github.com/astral-sh/uv/issues/14269
180+ # This can happen if building with PEP-517 build isolation, where `ninja` was
181+ # installed in the isolated environment of the previous build run, but it has been
182+ # removed. The `ninja` executable with an old absolute path not available anymore.
183+ eprint (
184+ "!!!WARNING!!!: CMakeCache.txt exists, "
185+ f"but CMAKE_MAKE_PROGRAM ({ make_program !r} ) does not exist. "
186+ "Clearing CMake cache."
187+ )
188+ self .clear_cache ()
189+ cmake_cache_file_available = False
190+
191+ if cmake_cache_file_available and (
192+ not USE_NINJA or os .path .exists (self ._ninja_build_file )
162193 ):
163194 # Everything's in place. Do not rerun.
164195 return
@@ -172,9 +203,9 @@ def generate(
172203 generator = os .getenv ("CMAKE_GENERATOR" , "Visual Studio 16 2019" )
173204 supported = ["Visual Studio 16 2019" , "Visual Studio 17 2022" ]
174205 if generator not in supported :
175- print ("Unsupported `CMAKE_GENERATOR`: " + generator )
176- print ("Please set it to one of the following values: " )
177- print ("\n " .join (supported ))
206+ eprint ("Unsupported `CMAKE_GENERATOR`: " + generator )
207+ eprint ("Please set it to one of the following values: " )
208+ eprint ("\n " .join (supported ))
178209 sys .exit (1 )
179210 args .append ("-G" + generator )
180211 toolset_dict = {}
@@ -183,7 +214,7 @@ def generate(
183214 toolset_dict ["version" ] = toolset_version
184215 curr_toolset = os .getenv ("VCToolsVersion" )
185216 if curr_toolset is None :
186- print (
217+ eprint (
187218 "When you specify `CMAKE_GENERATOR_TOOLSET_VERSION`, you must also "
188219 "activate the vs environment of this version. Please read the notes "
189220 "in the build steps carefully."
@@ -328,7 +359,7 @@ def generate(
328359 # error if the user also attempts to set these CMAKE options directly.
329360 specified_cmake__options = set (build_options ).intersection (cmake__options )
330361 if len (specified_cmake__options ) > 0 :
331- print (
362+ eprint (
332363 ", " .join (specified_cmake__options )
333364 + " should not be specified in the environment variable. They are directly set by PyTorch build script."
334365 )
@@ -357,11 +388,8 @@ def generate(
357388 my_env [env_var_name ] = str (my_env [env_var_name ].encode ("utf-8" ))
358389 except UnicodeDecodeError as e :
359390 shex = ":" .join (f"{ ord (c ):02x} " for c in my_env [env_var_name ])
360- print (
361- f"Invalid ENV[{ env_var_name } ] = { shex } " ,
362- file = sys .stderr ,
363- )
364- print (e , file = sys .stderr )
391+ eprint (f"Invalid ENV[{ env_var_name } ] = { shex } " )
392+ eprint (e )
365393 # According to the CMake manual, we should pass the arguments first,
366394 # and put the directory as the last element. Otherwise, these flags
367395 # may not be passed correctly.
@@ -372,7 +400,7 @@ def generate(
372400 self .run (args , env = my_env )
373401
374402 def build (self , my_env : dict [str , str ]) -> None :
375- "Runs cmake to build binaries."
403+ """ Runs cmake to build binaries."" "
376404
377405 from .env import build_type
378406
@@ -410,3 +438,10 @@ def build(self, my_env: dict[str, str]) -> None:
410438 # CMake 3.12 provides a '-j' option.
411439 build_args += ["-j" , max_jobs ]
412440 self .run (build_args , my_env )
441+
442+ def clear_cache (self ) -> None :
443+ """Clears the CMake cache."""
444+ if os .path .isfile (self ._cmake_cache_file ):
445+ os .remove (self ._cmake_cache_file )
446+ if os .path .isfile (self ._ninja_build_file ):
447+ os .remove (self ._ninja_build_file )
0 commit comments