Skip to content

Commit 0ca523e

Browse files
committed
build: bootstrap supports default Visual Studio tool paths on Windows
1 parent d97c381 commit 0ca523e

File tree

1 file changed

+98
-9
lines changed

1 file changed

+98
-9
lines changed

bootstrap.py

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)