@@ -126,7 +126,7 @@ class InstallOptions:
126126 "cmake_path" : "Path to the cmake executable, if not in system PATH." ,
127127 "python_path" : "Path to the python executable, if not in system PATH." ,
128128 "java_path" : "Path to the java executable, if not in system PATH." ,
129- "ninja_path" : "Path to the ninja executable. Leave empty to download it automatically." ,
129+ "ninja_path" : "Path to the ninja executable. Leave empty to look for ninja in PATH or to download it automatically." ,
130130 "mrdocs_src_dir" : "MrDocs source directory." ,
131131 "mrdocs_repo" : "URL of the MrDocs repository to clone." ,
132132 "mrdocs_branch" : "Branch or tag of the MrDocs repository to use." ,
@@ -602,6 +602,93 @@ def is_executable(self, path):
602602 else :
603603 return os .access (path , os .X_OK )
604604
605+ @lru_cache (maxsize = 1 )
606+ def get_vs_install_locations (self ):
607+ p = os .environ .get ('ProgramFiles(x86)' , r"C:\Program Files (x86)" )
608+ path_vswhere = os .path .join (p ,
609+ "Microsoft Visual Studio" , "Installer" , "vswhere.exe" )
610+ if not self .is_executable (path_vswhere ):
611+ return None
612+ cmd = [path_vswhere ,
613+ "-latest" , "-products" , "*" ,
614+ "-requires" , "Microsoft.Component.MSBuild" ,
615+ "-format" , "json" ]
616+ data = subprocess .check_output (cmd , universal_newlines = True )
617+ info = json .loads (data )
618+ if not info :
619+ return None
620+ return [inst .get ("installationPath" ) for inst in info ]
621+
622+ def find_vs_tool (self , tool ):
623+ vs_tools = ["cmake" , "ninja" , "git" , "python" ]
624+ if tool not in vs_tools :
625+ return None
626+ vs_roots = self .get_vs_install_locations ()
627+ for vs_root in vs_roots or []:
628+ ms_cext_path = os .path .join (vs_root , "Common7" , "IDE" , "CommonExtensions" , "Microsoft" )
629+ toolpaths = {
630+ 'cmake' : os .path .join (ms_cext_path , "CMake" , "CMake" , "bin" , "cmake.exe" ),
631+ 'git' : os .path .join (ms_cext_path , "TeamFoundation" , "Team Explorer" , "Git" , "cmd" , "git.exe" ),
632+ 'ninja' : os .path .join (ms_cext_path , "CMake" , "Ninja" , "ninja.exe" )
633+ }
634+ path = toolpaths .get (tool )
635+ if path and self .is_executable (path ):
636+ return path
637+ return None
638+
639+ def find_java (self ):
640+ # 1. check JAVA_HOME env variable
641+ java_home = os .environ .get ("JAVA_HOME" )
642+ if java_home :
643+ exe = os .path .join (java_home , "bin" , "java.exe" )
644+ if os .path .isfile (exe ):
645+ return exe
646+
647+ # 2. check registry (64+32-bit)
648+ import winreg
649+ def reg_lookup (base , subkey ):
650+ try :
651+ with winreg .OpenKey (base , subkey ) as key :
652+ ver , _ = winreg .QueryValueEx (key , "CurrentVersion" )
653+ key2 = winreg .OpenKey (base , subkey + "\\ " + ver )
654+ path , _ = winreg .QueryValueEx (key2 , "JavaHome" )
655+ exe = os .path .join (path , "bin" , "java.exe" )
656+ if os .path .isfile (exe ):
657+ return exe
658+ except OSError :
659+ return None
660+
661+ for hive , sub in [
662+ (winreg .HKEY_LOCAL_MACHINE , r"SOFTWARE\JavaSoft\Java Runtime Environment" ),
663+ (winreg .HKEY_LOCAL_MACHINE , r"SOFTWARE\Wow6432Node\JavaSoft\Java Runtime Environment" )
664+ ]:
665+ result = reg_lookup (hive , sub )
666+ if result :
667+ return result
668+
669+ # 3. check common folders under Program Files
670+ for base in [os .environ .get ("ProgramFiles" ), os .environ .get ("ProgramFiles(x86)" )]:
671+ if not base :
672+ continue
673+ jroot = os .path .join (base , "Java" )
674+ if os .path .isdir (jroot ):
675+ for entry in os .listdir (jroot ):
676+ candidate = os .path .join (jroot , entry , "bin" , "java.exe" )
677+ if os .path .isfile (candidate ):
678+ return candidate
679+
680+ return None
681+
682+ def find_tool (self , tool ):
683+ tool_path = shutil .which (tool )
684+ if not tool_path and self .is_windows ():
685+ tool_path = self .find_vs_tool (tool )
686+ if not tool_path and tool == "java" :
687+ tool_path = self .find_java ()
688+ if not tool_path and tool == "python" :
689+ tool_path = sys .executable
690+ return tool_path
691+
605692 def check_tool (self , tool ):
606693 """
607694 Checks if the required tools are available as a command line argument or
@@ -617,7 +704,9 @@ def check_tool(self, tool):
617704
618705 :return: None
619706 """
620- default_value = shutil .which (tool )
707+ default_value = self .find_tool (tool )
708+ if not default_value :
709+ default_value = tool
621710 setattr (self .default_options , f"{ tool } _path" , default_value )
622711 tool_path = self .prompt_option (f"{ tool } _path" )
623712 if not self .is_executable (tool_path ):
@@ -717,7 +806,7 @@ def probe_compilers(self):
717806 f .write ("\n " .join (cmake_lists ))
718807
719808 # Build command
720- cmd = ["cmake" , "-S" , probe_dir ]
809+ cmd = [self . options . cmake_path , "-S" , probe_dir ]
721810 env = os .environ .copy ()
722811 if self .options .cc :
723812 cmd += ["-DCMAKE_C_COMPILER=" + self .options .cc ]
@@ -762,13 +851,13 @@ def install_ninja(self):
762851 # 1. Check if the user has set a ninja_path option
763852 if self .prompt_option ("ninja_path" ):
764853 if not os .path .isabs (self .options .ninja_path ):
765- self .options .ninja_path = shutil . which (self .options .ninja_path )
854+ self .options .ninja_path = self . find_tool (self .options .ninja_path )
766855 if not self .is_executable (self .options .ninja_path ):
767856 raise FileNotFoundError (f"Ninja executable not found at { self .options .ninja_path } ." )
768857 return
769858
770859 # 2. If ninja_path is not set, but does the user have it available in PATH?
771- ninja_path = shutil . which ("ninja" )
860+ ninja_path = self . find_tool ("ninja" )
772861 if ninja_path :
773862 print (f"Ninja found in PATH at { ninja_path } . Using it." )
774863 self .options .ninja_path = ninja_path
@@ -973,10 +1062,10 @@ def install_llvm(self):
9731062 self .prompt_option ("llvm_repo" )
9741063 self .prompt_option ("llvm_commit" )
9751064 os .makedirs (self .options .llvm_src_dir , exist_ok = True )
976- self .run_cmd ("git init" , self .options .llvm_src_dir )
977- self .run_cmd (f"git remote add origin { self .options .llvm_repo } " , self .options .llvm_src_dir )
978- self .run_cmd (f"git fetch --depth 1 origin { self .options .llvm_commit } " , self .options .llvm_src_dir )
979- self .run_cmd ("git checkout FETCH_HEAD" , self .options .llvm_src_dir )
1065+ self .run_cmd ([ self . options . git_path , " init"] , self .options .llvm_src_dir )
1066+ self .run_cmd ([ self . options . git_path , " remote" , " add" , " origin" , self .options .llvm_repo ] , self .options .llvm_src_dir )
1067+ self .run_cmd ([ self . options . git_path , " fetch" , " --depth" , "1" , " origin" , self .options .llvm_commit ] , self .options .llvm_src_dir )
1068+ self .run_cmd ([ self . options . git_path , " checkout" , " FETCH_HEAD"] , self .options .llvm_src_dir )
9801069
9811070 llvm_subproject_dir = os .path .join (self .options .llvm_src_dir , "llvm" )
9821071 llvm_patches = os .path .join (self .options .mrdocs_src_dir , 'third-party' , 'llvm' )
0 commit comments