170 lines
5.1 KiB
Python
170 lines
5.1 KiB
Python
import os
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from .models import ProjectKind, Toolchain
|
|
|
|
VC_WORKLOAD = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
|
|
VS_CMAKE_RELATIVE = Path(
|
|
"Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin"
|
|
)
|
|
|
|
|
|
class ToolchainError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def _decode(value):
|
|
if isinstance(value, str):
|
|
return value
|
|
return (value or b"").decode("utf-8", errors="replace")
|
|
|
|
|
|
def _first_path(result, label, argv, cwd):
|
|
text = _decode(result.stdout).strip()
|
|
if result.returncode != 0:
|
|
diagnostic = (
|
|
_decode(result.stdout) + "\n" + _decode(result.stderr)
|
|
).rstrip()[-8000:]
|
|
diagnostic = diagnostic.replace("\r\n", " | ").replace("\n", " | ")
|
|
message = (
|
|
f"{label} probe failed with exit code {result.returncode}; "
|
|
f"argv={argv!r}; cwd={str(cwd)!r}"
|
|
)
|
|
if diagnostic:
|
|
message = f"{message}; output tail: {diagnostic}"
|
|
raise ToolchainError(
|
|
f"{message}; install Visual Studio Desktop development with C++"
|
|
)
|
|
if not text:
|
|
raise ToolchainError(
|
|
f"{label} probe returned no path; argv={argv!r}; cwd={str(cwd)!r}; "
|
|
"install Visual Studio Desktop development with C++"
|
|
)
|
|
path = Path(text.splitlines()[0]).resolve()
|
|
if not path.exists():
|
|
diagnostic = (
|
|
_decode(result.stdout) + "\n" + _decode(result.stderr)
|
|
).rstrip()[-8000:]
|
|
diagnostic = diagnostic.replace("\r\n", " | ").replace("\n", " | ")
|
|
message = (
|
|
f"{label} probe returned nonexistent path with exit code 0; "
|
|
f"argv={argv!r}; cwd={str(cwd)!r}"
|
|
)
|
|
if diagnostic:
|
|
message = f"{message}; output tail: {diagnostic}"
|
|
raise ToolchainError(
|
|
f"{message}; install or repair Visual Studio Desktop development with C++"
|
|
)
|
|
return path
|
|
|
|
|
|
def _require_path(path, label):
|
|
if path is None or not path.exists():
|
|
raise ToolchainError(f"{label} not found")
|
|
return path.resolve()
|
|
|
|
|
|
def _remaining(deadline, clock, label):
|
|
if deadline is None:
|
|
return None
|
|
remaining = deadline - clock()
|
|
if remaining <= 0:
|
|
raise ToolchainError(f"{label} probe timed out before it could start")
|
|
return remaining
|
|
|
|
|
|
def _probe(run, argv, label, *, deadline, clock):
|
|
cwd = Path.cwd().resolve()
|
|
kwargs = {
|
|
"cwd": cwd,
|
|
"capture_output": True,
|
|
"check": False,
|
|
"shell": False,
|
|
}
|
|
remaining = _remaining(deadline, clock, label)
|
|
if remaining is not None:
|
|
kwargs["timeout"] = remaining
|
|
try:
|
|
return run(argv, **kwargs)
|
|
except subprocess.TimeoutExpired as exc:
|
|
raise ToolchainError(
|
|
f"{label} probe timed out; argv={argv!r}; cwd={str(cwd)!r}; "
|
|
"install or repair Visual Studio Desktop development with C++"
|
|
) from exc
|
|
|
|
|
|
def discover_toolchain(
|
|
kind: ProjectKind,
|
|
*,
|
|
env=None,
|
|
which=shutil.which,
|
|
run=subprocess.run,
|
|
deadline=None,
|
|
clock=time.monotonic,
|
|
) -> Toolchain:
|
|
environment = os.environ if env is None else env
|
|
vswhere_path = which("vswhere.exe")
|
|
if vswhere_path is None:
|
|
program_files = environment.get("ProgramFiles(x86)")
|
|
if program_files:
|
|
candidate = Path(program_files) / "Microsoft Visual Studio/Installer/vswhere.exe"
|
|
vswhere_path = str(candidate) if candidate.exists() else None
|
|
vswhere = _require_path(
|
|
Path(vswhere_path) if vswhere_path else None,
|
|
"vswhere.exe; install Visual Studio Desktop development with C++",
|
|
)
|
|
|
|
install_argv = [
|
|
str(vswhere), "-latest", "-products", "*", "-requires", VC_WORKLOAD,
|
|
"-property", "installationPath",
|
|
]
|
|
install_result = _probe(
|
|
run,
|
|
install_argv,
|
|
"Visual Studio installation",
|
|
deadline=deadline,
|
|
clock=clock,
|
|
)
|
|
probe_cwd = Path.cwd().resolve()
|
|
installation = _require_path(
|
|
_first_path(
|
|
install_result,
|
|
"Visual Studio installation",
|
|
install_argv,
|
|
probe_cwd,
|
|
),
|
|
"Visual Studio installation",
|
|
)
|
|
msbuild_argv = [
|
|
str(vswhere), "-latest", "-products", "*", "-requires", VC_WORKLOAD,
|
|
"-find", r"MSBuild\**\Bin\MSBuild.exe",
|
|
]
|
|
msbuild_result = _probe(
|
|
run,
|
|
msbuild_argv,
|
|
"MSBuild",
|
|
deadline=deadline,
|
|
clock=clock,
|
|
)
|
|
msbuild = _require_path(
|
|
_first_path(msbuild_result, "MSBuild", msbuild_argv, probe_cwd),
|
|
"MSBuild.exe",
|
|
)
|
|
|
|
if kind is not ProjectKind.CMAKE:
|
|
return Toolchain(installation=installation, msbuild=msbuild)
|
|
|
|
bundled_cmake_dir = installation / VS_CMAKE_RELATIVE
|
|
cmake = _require_path(
|
|
Path(which("cmake.exe")) if which("cmake.exe") else bundled_cmake_dir / "cmake.exe",
|
|
"cmake.exe",
|
|
)
|
|
ctest = _require_path(
|
|
Path(which("ctest.exe")) if which("ctest.exe") else bundled_cmake_dir / "ctest.exe",
|
|
"ctest.exe",
|
|
)
|
|
return Toolchain(installation=installation, msbuild=msbuild, cmake=cmake, ctest=ctest)
|