From 867c915596f8e1de38e4c3ce59de7d66bee1ee5c Mon Sep 17 00:00:00 2001 From: Cheviiot <153805936+Cheviiot@users.noreply.github.com> Date: Sat, 25 Jul 2026 01:58:18 +1000 Subject: [PATCH] Fix MSBuild toolset/SDK detection under Wine msbuild .vcxproj previously failed with MSB8020 ("build tools for vNNN cannot be found") because MSBuild's own toolset/SDK resolution reads a different set of environment variables than cl/link/lib do directly (VCInstallDir_, VCToolsInstallDir_, VsInstallRoot, WindowsSdkDir_10, WindowsTargetPlatformVersion, DisableRegistryUse, etc) - none of which the generic INCLUDE/LIB/WINEPATH env covered. Added msbuildEnv, populated for every MSBuild toolset generation actually present under MSBuild/Microsoft/VC/v*, and wired into the msbuild wrapper. Verified end-to-end: msbuild successfully builds ocornut/imgui's example_win32_directx11.vcxproj (retargeted from its original v141 PlatformToolset to this install's v145) - compiles all 8 sources and links against d3d11.lib/d3dcompiler.lib/dxgi.lib, producing a valid PE32+ executable. --- internal/wineenv/paths.go | 13 +++++ internal/wrapper/msbuildenv.go | 86 ++++++++++++++++++++++++++++++++++ internal/wrapper/run.go | 10 +++- 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 internal/wrapper/msbuildenv.go diff --git a/internal/wineenv/paths.go b/internal/wineenv/paths.go index 5912278..446fe02 100644 --- a/internal/wineenv/paths.go +++ b/internal/wineenv/paths.go @@ -16,6 +16,14 @@ type Paths struct { SDKBinDir string // /kits/10/bin// - mc/midl/mt/rc live here MSBuildBinDir string // /MSBuild/Current/Bin/ - MSBuild.exe lives here + // Windows-notation ("z:\...") equivalents of the paths above, needed to + // populate the MSBuild-specific environment variables its toolset/SDK + // detection props read (see msbuildEnv in the wrapper package). + BaseWin string // z:\ + MSVCBaseWin string // z:\\vc + MSVCDirWin string // z:\\vc\tools\msvc\ + SDKBaseWin string // z:\\kits\10 + Include string Lib string LibPath string @@ -89,6 +97,11 @@ func NewPaths(cfg *Config, baseUnix string) *Paths { SDKBinDir: sdkBinDir, MSBuildBinDir: msbuildBinDir, + BaseWin: winBase, + MSVCBaseWin: msvcBase, + MSVCDirWin: msvcDirWin, + SDKBaseWin: sdkBase, + Include: include, Lib: lib, LibPath: lib, diff --git a/internal/wrapper/msbuildenv.go b/internal/wrapper/msbuildenv.go new file mode 100644 index 0000000..5c7cd0d --- /dev/null +++ b/internal/wrapper/msbuildenv.go @@ -0,0 +1,86 @@ +package wrapper + +import ( + "path/filepath" + "regexp" + "strings" + + "github.com/Cheviiot/msvc-go-wine/internal/wineenv" +) + +var reToolsetDir = regexp.MustCompile(`^v(\d+)$`) + +// msbuildEnv returns the extra environment variables MSBuild's own +// SDK/toolset-detection property sheets need. The generic INCLUDE/LIB/ +// WINEPATH set by buildEnv are enough for cl/link/lib invoked directly, but +// MSBuild resolves the compiler location and Windows SDK through a +// different, registry-oriented mechanism - DisableRegistryUse=true +// redirects that lookup to these variables instead of a (nonexistent) +// Windows Registry. +func msbuildEnv(cfg *wineenv.Config, paths *wineenv.Paths) map[string]string { + env := map[string]string{ + "DisableRegistryUse": "true", + "VCToolsVersion": cfg.MSVCVer, + "VsInstallRoot": paths.BaseWin + `\`, + "VSInstallDir": paths.BaseWin + `\`, + + "MicrosoftKitRoot": paths.BaseWin + `\`, + "SDKReferenceDirectoryRoot": paths.BaseWin + `\`, + "SDKExtensionDirectoryRoot": paths.BaseWin + `\`, + "MSBUILDSDKREFERENCEDIRECTORY": paths.BaseWin + `\`, + "MSBUILDMULTIPLATFORMSDKREFERENCEDIRECTORY": paths.BaseWin + `\`, + + "WindowsSdkDir_10": paths.SDKBaseWin + `\`, + "UniversalCRTSdkDir_10": paths.SDKBaseWin + `\`, + "WindowsSdkDir": paths.SDKBaseWin + `\`, + "UniversalCRTSdkDir": paths.SDKBaseWin + `\`, + "WindowsTargetPlatformVersion": cfg.SDKVer, + "UCRTContentRoot": paths.SDKBaseWin + `\`, + "NETFXKitsDir": paths.SDKBaseWin + `\`, + "NETFXSDKDir": paths.SDKBaseWin + `\`, + + // WDK-specific properties; harmless when not building a driver. + "WDKKitVersion": "10", + "Driver_SpectreMitigation": "false", + "SignMode": "off", + "Inf2CatNoCatalog": "true", + "ApiValidator_Enable": "False", + + "Platform": msbuildPlatform(cfg.Arch), + } + + // Microsoft.Cpp.props resolves the compiler/toolset location through + // VCInstallDir_/VCToolsInstallDir_, where is whatever numeric + // suffix the installed MSBuild toolset property sheets use (e.g. + // .../MSBuild/Microsoft/VC/v180 -> "180"). Populate every one actually + // present, so a project pinned to any of them resolves to the one real + // toolchain that's installed. + matches, _ := filepath.Glob(filepath.Join(paths.BaseUnix, "MSBuild", "Microsoft", "VC", "v*")) + for _, m := range matches { + sub := reToolsetDir.FindStringSubmatch(filepath.Base(m)) + if sub == nil { + continue + } + env["VCInstallDir_"+sub[1]] = paths.MSVCBaseWin + `\` + env["VCToolsInstallDir_"+sub[1]] = paths.MSVCDirWin + `\` + } + + if strings.HasSuffix(paths.MSBuildBinDir, "amd64") { + env["PreferredToolArchitecture"] = "x64" + } + + return env +} + +func msbuildPlatform(arch string) string { + switch arch { + case "x86": + return "Win32" + case "arm": + return "ARM" + case "arm64": + return "ARM64" + default: + return arch + } +} diff --git a/internal/wrapper/run.go b/internal/wrapper/run.go index e0ef47f..e61beea 100644 --- a/internal/wrapper/run.go +++ b/internal/wrapper/run.go @@ -70,9 +70,15 @@ func Run(tool string, args []string) int { var exitCode int switch { case s.rawStdout: - // MSBuild: skip all filtering/toolrelay, inherit stdio directly. + // MSBuild: skip all filtering/toolrelay, inherit stdio directly, and + // add the extra environment MSBuild's own toolset/SDK-detection + // props need on top of the generic INCLUDE/LIB/WINEPATH. cmd := exec.Command(wineBin, append([]string{toolExePath}, rewritten...)...) - cmd.Env = buildEnv(paths) + env := buildEnv(paths) + for k, v := range msbuildEnv(cfg, paths) { + env = append(env, k+"="+v) + } + cmd.Env = env cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr