mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25f751e874 | ||
|
|
4795c7c105 |
@@ -8,8 +8,9 @@ vintner cross-compiles with the real MSVC toolchain on Linux, using Wine.
|
|||||||
One Go binary drops in as `cl`, `link`, `lib`, `rc`, `midl`, `mc`, `mt`,
|
One Go binary drops in as `cl`, `link`, `lib`, `rc`, `midl`, `mc`, `mt`,
|
||||||
`dumpbin`, `msbuild`, `nmake`, `ml`, `ml64`, `armasm`, `armasm64`, plus
|
`dumpbin`, `msbuild`, `nmake`, `ml`, `ml64`, `armasm`, `armasm64`, plus
|
||||||
`cmd`/`findstr` shims, so once installed you invoke the real Microsoft
|
`cmd`/`findstr` shims, so once installed you invoke the real Microsoft
|
||||||
tools exactly like on Windows. It handles full MSBuild projects, and with
|
tools exactly like on Windows. It handles full MSBuild projects, with
|
||||||
`--with-wdk`, real KMDF/UMDF Windows drivers.
|
`--with-wdk` real KMDF/UMDF Windows drivers, and with `--with-dxsdk` the
|
||||||
|
real D3DX9 headers/libs.
|
||||||
|
|
||||||
Inspired by [mstorsjo/msvc-wine](https://github.com/mstorsjo/msvc-wine)'s
|
Inspired by [mstorsjo/msvc-wine](https://github.com/mstorsjo/msvc-wine)'s
|
||||||
approach: download the real MSVC/WinSDK, wrap the compiler under Wine.
|
approach: download the real MSVC/WinSDK, wrap the compiler under Wine.
|
||||||
@@ -21,6 +22,7 @@ approach: download the real MSVC/WinSDK, wrap the compiler under Wine.
|
|||||||
- [Quick start](#quick-start)
|
- [Quick start](#quick-start)
|
||||||
- [Commands](#commands)
|
- [Commands](#commands)
|
||||||
- [Building drivers (WDK)](#building-drivers-wdk)
|
- [Building drivers (WDK)](#building-drivers-wdk)
|
||||||
|
- [Building against D3DX9 (DirectX SDK)](#building-against-d3dx9-directx-sdk)
|
||||||
- [Language](#language)
|
- [Language](#language)
|
||||||
- [Shell completion](#shell-completion)
|
- [Shell completion](#shell-completion)
|
||||||
- [Using clang-cl/lld-link instead of Wine](#using-clang-cllld-link-instead-of-wine)
|
- [Using clang-cl/lld-link instead of Wine](#using-clang-cllld-link-instead-of-wine)
|
||||||
@@ -112,12 +114,13 @@ vintner completion bash|zsh print a shell
|
|||||||
|
|
||||||
`download`'s main options: `--msvc-version`, `--sdk-version`,
|
`download`'s main options: `--msvc-version`, `--sdk-version`,
|
||||||
`--architecture` (repeatable: `x86`/`x64`/`arm`/`arm64`/`host`),
|
`--architecture` (repeatable: `x86`/`x64`/`arm`/`arm64`/`host`),
|
||||||
`--host-arch`, `--only-host`, `--with-wdk` (see below), `--ignore`
|
`--host-arch`, `--only-host`, `--with-wdk` (see below), `--with-dxsdk`
|
||||||
(repeatable), `--only-download`, `--only-unpack`, `--keep-unpack`,
|
(see below), `--ignore` (repeatable), `--only-download`, `--only-unpack`,
|
||||||
`--skip-patch`, `--cache`, `--language`, `--include-optional`,
|
`--keep-unpack`, `--skip-patch`, `--cache`, `--language`,
|
||||||
`--skip-recommended`, `--major`, `--preview`, `--manifest`,
|
`--include-optional`, `--skip-recommended`, `--major`, `--preview`,
|
||||||
`--list-workloads`, `--list-components`, `--print-deps-tree`. Run
|
`--manifest`, `--list-workloads`, `--list-components`,
|
||||||
`vintner download -h` for the full list with descriptions.
|
`--print-deps-tree`. Run `vintner download -h` for the full list with
|
||||||
|
descriptions.
|
||||||
|
|
||||||
`--list-workloads`/`--list-components` print every workload/component id
|
`--list-workloads`/`--list-components` print every workload/component id
|
||||||
and its human-readable title from the fetched manifest, then exit
|
and its human-readable title from the fetched manifest, then exit
|
||||||
@@ -143,6 +146,22 @@ work under Wine. Tested against a real sample driver from
|
|||||||
Only x64 and arm64 targets have a WDK package upstream; there's no x86 or
|
Only x64 and arm64 targets have a WDK package upstream; there's no x86 or
|
||||||
arm one.
|
arm one.
|
||||||
|
|
||||||
|
## Building against D3DX9 (DirectX SDK)
|
||||||
|
|
||||||
|
`--with-dxsdk` fetches the DirectX SDK (June 2010) — the last standalone
|
||||||
|
release of D3DX9/10/11, XInput and XAudio2, dropped from the Windows SDK
|
||||||
|
entirely once D3DX was deprecated. It unpacks the real headers and x86/x64
|
||||||
|
import libs (`d3dx9.h`/`d3dx9.lib` included) to `<dest>/DXSDK`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vintner download --accept-license --with-dxsdk
|
||||||
|
```
|
||||||
|
|
||||||
|
Point your project's `IncludePath`/`LibraryPath` at
|
||||||
|
`<dest>/DXSDK/Include` and `<dest>/DXSDK/Lib/x86` or `<dest>/DXSDK/Lib/x64`.
|
||||||
|
Requires `cabextract` on `PATH` (the installer is a self-extracting CAB
|
||||||
|
archive).
|
||||||
|
|
||||||
## Language
|
## Language
|
||||||
|
|
||||||
CLI text (usage, progress lines, prompts) defaults to English. Set
|
CLI text (usage, progress lines, prompts) defaults to English. Set
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func runDownload(args []string) int {
|
|||||||
listComponents := fs.Bool("list-components", false, "list available components from the manifest and exit, without downloading anything")
|
listComponents := fs.Bool("list-components", false, "list available components from the manifest and exit, without downloading anything")
|
||||||
printDepsTree := fs.Bool("print-deps-tree", false, "print the dependency tree of the selected packages and exit, without downloading anything")
|
printDepsTree := fs.Bool("print-deps-tree", false, "print the dependency tree of the selected packages and exit, without downloading anything")
|
||||||
withWDK := fs.Bool("with-wdk", false, "also fetch and install the Windows Driver Kit (headers, libs and MSBuild driver PlatformToolsets, for building KMDF/UMDF drivers)")
|
withWDK := fs.Bool("with-wdk", false, "also fetch and install the Windows Driver Kit (headers, libs and MSBuild driver PlatformToolsets, for building KMDF/UMDF drivers)")
|
||||||
|
withDXSDK := fs.Bool("with-dxsdk", false, "also fetch and install the DirectX SDK (June 2010): real D3DX9/10/11, XInput and XAudio2 headers and import libs, dropped from the modern Windows SDK")
|
||||||
var archsFlag stringList
|
var archsFlag stringList
|
||||||
fs.Var(&archsFlag, "architecture", "target architecture to include (x86, x64, arm, arm64, host); repeatable")
|
fs.Var(&archsFlag, "architecture", "target architecture to include (x86, x64, arm, arm64, host); repeatable")
|
||||||
var ignoreFlag stringList
|
var ignoreFlag stringList
|
||||||
@@ -219,6 +220,13 @@ func runDownload(args []string) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *withDXSDK && !*onlyUnpack {
|
||||||
|
if err := downloadDXSDK(cache, destAbs); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "vintner download:", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println(i18n.T("download.done", destAbs))
|
fmt.Println(i18n.T("download.done", destAbs))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -255,6 +263,18 @@ func downloadWDK(opts *download.Options, selected []*download.Package, cache, de
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadDXSDK fetches and unpacks the DirectX SDK (June 2010) into
|
||||||
|
// destAbs/DXSDK. See internal/download/dxsdk.go for why this is a separate
|
||||||
|
// download path from the rest of ExpandSelection/FetchPayloads/Unpack.
|
||||||
|
func downloadDXSDK(cache, destAbs string) error {
|
||||||
|
dxsdkDir, err := download.DownloadDXSDK(cache, destAbs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Print(i18n.T("download.dxsdk_installed", dxsdkDir))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func contains(list []string, v string) bool {
|
func contains(list []string, v string) bool {
|
||||||
for _, s := range list {
|
for _, s := range list {
|
||||||
if s == v {
|
if s == v {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The DirectX SDK (June 2010) is the last standalone release of D3DX9 (and
|
||||||
|
// D3DX10/11, XInput, XAudio2, ...). Microsoft never carried D3DX forward
|
||||||
|
// into the Windows 10/11 SDK - it's deprecated in favor of D3DCompiler/
|
||||||
|
// WICTextureLoader/DirectXTex, but plenty of legacy code (this included)
|
||||||
|
// still links against the real d3dx9.h/d3dx9.lib. Like the WDK (see
|
||||||
|
// wdk.go), it isn't part of the VS installer manifest or any package feed
|
||||||
|
// vsman knows about, so this is a separate, self-contained download path
|
||||||
|
// outside ExpandSelection/FetchPayloads/UnpackSelectedPackages.
|
||||||
|
//
|
||||||
|
// The installer is a self-extracting PE with an appended CAB archive.
|
||||||
|
// cabextract (already a vintner prerequisite - see runMsiExtract's sibling
|
||||||
|
// extractWindowsSDKPackage) unpacks it directly, without needing Wine or a
|
||||||
|
// separate archive tool. Its -F/--filter flag (repeatable) restricts
|
||||||
|
// extraction to the Include and Lib subtrees actually needed for building -
|
||||||
|
// about 21MB out of the installer's 1.2GB uncompressed payload.
|
||||||
|
|
||||||
|
const dxsdkURL = "https://download.microsoft.com/download/A/E/7/AE743F1F-632B-4809-87A9-AA1BB3458E31/DXSDK_Jun10.exe"
|
||||||
|
|
||||||
|
// dxsdkSHA256 pins the exact installer build this code was written against
|
||||||
|
// (verified by fully extracting it with both cabextract and 7z and cross
|
||||||
|
// checking the file lists) - the June 2010 DirectX SDK is a frozen legacy
|
||||||
|
// artifact Microsoft is not going to rebuild. A var, not a const, so tests
|
||||||
|
// can point it at a small fake payload instead of the real 600MB installer.
|
||||||
|
var dxsdkSHA256 = "705271dc83bfee54d9b94e028426e288d5f070784b7446d164f48ecfbb2a02cb"
|
||||||
|
|
||||||
|
// DownloadDXSDK fetches (or reuses a cached copy of) the DirectX SDK (June
|
||||||
|
// 2010) installer into cacheDir, then unpacks its Include and Lib trees -
|
||||||
|
// headers and x86/x64 import libs for D3DX9/10/11, XInput, XAudio2, and the
|
||||||
|
// rest - into destDir/DXSDK. Returns that directory.
|
||||||
|
func DownloadDXSDK(cacheDir, destDir string) (string, error) {
|
||||||
|
if _, err := exec.LookPath("cabextract"); err != nil {
|
||||||
|
return "", fmt.Errorf("cabextract not found in PATH (install the cabextract package): %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFile := filepath.Join(cacheDir, "DXSDK_Jun10.exe")
|
||||||
|
if !isFile(cacheFile) {
|
||||||
|
fmt.Println("Downloading DirectX SDK (June 2010)")
|
||||||
|
if err := httpDownloadFile(dxsdkURL, cacheFile); err != nil {
|
||||||
|
return "", fmt.Errorf("downloading DirectX SDK: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("Using existing file", filepath.Base(cacheFile))
|
||||||
|
}
|
||||||
|
sum, err := sha256File(cacheFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !equalFoldHex(sum, dxsdkSHA256) {
|
||||||
|
return "", fmt.Errorf("incorrect hash for downloaded file %s, aborting", filepath.Base(cacheFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
scratch, err := os.MkdirTemp(destDir, "dxsdk-unpack-")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(scratch)
|
||||||
|
|
||||||
|
if err := runCabextract(cacheFile, scratch, "DXSDK/Include/*", "DXSDK/Lib/*"); err != nil {
|
||||||
|
return "", fmt.Errorf("extracting DirectX SDK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dxsdkDir := filepath.Join(destDir, "DXSDK")
|
||||||
|
if err := combineDirTrees(filepath.Join(scratch, "DXSDK"), dxsdkDir); err != nil {
|
||||||
|
return "", fmt.Errorf("moving DirectX SDK content into place: %w", err)
|
||||||
|
}
|
||||||
|
return dxsdkDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCabextract extracts srcFile into destDir, restricted to entries
|
||||||
|
// matching any of patterns (cabextract's -F, repeatable, glob-matched
|
||||||
|
// against the full in-archive path).
|
||||||
|
func runCabextract(srcFile, destDir string, patterns ...string) error {
|
||||||
|
if err := os.MkdirAll(destDir, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
args := []string{"-q", "-d", destDir}
|
||||||
|
for _, p := range patterns {
|
||||||
|
args = append(args, "-F", p)
|
||||||
|
}
|
||||||
|
args = append(args, srcFile)
|
||||||
|
cmd := exec.Command("cabextract", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// withFakeCabextract prepends a directory containing a fake "cabextract"
|
||||||
|
// script to PATH, so tests can exercise runCabextract/DownloadDXSDK without
|
||||||
|
// the real tool (or a real DXSDK installer) present. The fake script
|
||||||
|
// records the arguments it was invoked with to argsFile and creates an
|
||||||
|
// empty DXSDK/Include and DXSDK/Lib under whatever -d directory it was
|
||||||
|
// given, mimicking a successful (if empty) extraction.
|
||||||
|
func withFakeCabextract(t *testing.T, argsFile string) {
|
||||||
|
t.Helper()
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("fake cabextract script requires a POSIX shell")
|
||||||
|
}
|
||||||
|
bin := t.TempDir()
|
||||||
|
script := `#!/bin/sh
|
||||||
|
echo "$@" > "` + argsFile + `"
|
||||||
|
dest=""
|
||||||
|
prev=""
|
||||||
|
for a in "$@"; do
|
||||||
|
if [ "$prev" = "-d" ]; then
|
||||||
|
dest="$a"
|
||||||
|
fi
|
||||||
|
prev="$a"
|
||||||
|
done
|
||||||
|
mkdir -p "$dest/DXSDK/Include" "$dest/DXSDK/Lib"
|
||||||
|
`
|
||||||
|
path := filepath.Join(bin, "cabextract")
|
||||||
|
if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("PATH", bin+":"+os.Getenv("PATH"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCabextractPassesFilterArgs(t *testing.T) {
|
||||||
|
dest := t.TempDir()
|
||||||
|
argsFile := filepath.Join(t.TempDir(), "args")
|
||||||
|
withFakeCabextract(t, argsFile)
|
||||||
|
|
||||||
|
src := filepath.Join(t.TempDir(), "installer.exe")
|
||||||
|
writeFile(t, src, "fake-installer-bytes")
|
||||||
|
|
||||||
|
if err := runCabextract(src, dest, "DXSDK/Include/*", "DXSDK/Lib/*"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := os.ReadFile(argsFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected the fake cabextract to have run: %v", err)
|
||||||
|
}
|
||||||
|
want := "-q -d " + dest + " -F DXSDK/Include/* -F DXSDK/Lib/* " + src + "\n"
|
||||||
|
if string(got) != want {
|
||||||
|
t.Errorf("cabextract args = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadDXSDKReusesCachedFileAndRejectsHashMismatch(t *testing.T) {
|
||||||
|
argsFile := filepath.Join(t.TempDir(), "args")
|
||||||
|
withFakeCabextract(t, argsFile)
|
||||||
|
|
||||||
|
cacheDir := t.TempDir()
|
||||||
|
destDir := t.TempDir()
|
||||||
|
writeFile(t, filepath.Join(cacheDir, "DXSDK_Jun10.exe"), "not the real installer")
|
||||||
|
|
||||||
|
_, err := DownloadDXSDK(cacheDir, destDir)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error for a cached file that doesn't match dxsdkSHA256")
|
||||||
|
}
|
||||||
|
if _, statErr := os.Stat(argsFile); statErr == nil {
|
||||||
|
t.Error("cabextract should not have run before the hash was verified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDownloadDXSDKMovesExtractedContentIntoPlace(t *testing.T) {
|
||||||
|
argsFile := filepath.Join(t.TempDir(), "args")
|
||||||
|
withFakeCabextract(t, argsFile)
|
||||||
|
|
||||||
|
cacheDir := t.TempDir()
|
||||||
|
destDir := t.TempDir()
|
||||||
|
cacheFile := filepath.Join(cacheDir, "DXSDK_Jun10.exe")
|
||||||
|
writeFile(t, cacheFile, "fake-installer-bytes-for-hash-test")
|
||||||
|
|
||||||
|
// Patch the expected hash to match our fake cached file, since we can't
|
||||||
|
// (and shouldn't) fetch or embed the real 600MB installer in a test.
|
||||||
|
sum, err := sha256File(cacheFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
restore := dxsdkSHA256
|
||||||
|
dxsdkSHA256 = sum
|
||||||
|
defer func() { dxsdkSHA256 = restore }()
|
||||||
|
|
||||||
|
dxsdkDir, err := DownloadDXSDK(cacheDir, destDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if dxsdkDir != filepath.Join(destDir, "DXSDK") {
|
||||||
|
t.Errorf("DownloadDXSDK returned %q, want %q", dxsdkDir, filepath.Join(destDir, "DXSDK"))
|
||||||
|
}
|
||||||
|
for _, sub := range []string{"Include", "Lib"} {
|
||||||
|
if !isDir(filepath.Join(dxsdkDir, sub)) {
|
||||||
|
t.Errorf("expected %s/%s to exist after extraction", dxsdkDir, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,8 +77,8 @@ Usage:
|
|||||||
vintner completion bash|zsh print a shell completion script
|
vintner completion bash|zsh print a shell completion script
|
||||||
|
|
||||||
Run "vintner <command> --help" for that command's own options - download
|
Run "vintner <command> --help" for that command's own options - download
|
||||||
has many, including --with-wdk, --list-workloads, --list-components and
|
has many, including --with-wdk, --with-dxsdk, --list-workloads,
|
||||||
--print-deps-tree.
|
--list-components and --print-deps-tree.
|
||||||
|
|
||||||
--dest/[dir] default to ~/.vintner if omitted.
|
--dest/[dir] default to ~/.vintner if omitted.
|
||||||
Language: set VINTNER_LANG=ru (or LANG=ru_RU...) for Russian output.
|
Language: set VINTNER_LANG=ru (or LANG=ru_RU...) for Russian output.
|
||||||
@@ -99,8 +99,8 @@ Once installed, add <dir>/bin/<arch> to PATH and invoke the tools directly:
|
|||||||
vintner completion bash|zsh вывести скрипт автодополнения для оболочки
|
vintner completion bash|zsh вывести скрипт автодополнения для оболочки
|
||||||
|
|
||||||
Запустите «vintner <команда> --help» для параметров конкретной команды —
|
Запустите «vintner <команда> --help» для параметров конкретной команды —
|
||||||
у download их много, включая --with-wdk, --list-workloads, --list-components
|
у download их много, включая --with-wdk, --with-dxsdk, --list-workloads,
|
||||||
и --print-deps-tree.
|
--list-components и --print-deps-tree.
|
||||||
|
|
||||||
--dest/[каталог] по умолчанию — ~/.vintner.
|
--dest/[каталог] по умолчанию — ~/.vintner.
|
||||||
Язык: установите VINTNER_LANG=en (или LANG=en_US...) для вывода на английском.
|
Язык: установите VINTNER_LANG=en (или LANG=en_US...) для вывода на английском.
|
||||||
@@ -161,6 +161,10 @@ Once installed, add <dir>/bin/<arch> to PATH and invoke the tools directly:
|
|||||||
EN: "Installed WDK (%s) %s at %s\n",
|
EN: "Installed WDK (%s) %s at %s\n",
|
||||||
RU: "WDK (%s) %s установлен в %s\n",
|
RU: "WDK (%s) %s установлен в %s\n",
|
||||||
},
|
},
|
||||||
|
"download.dxsdk_installed": {
|
||||||
|
EN: "Installed DirectX SDK (June 2010) at %s\n",
|
||||||
|
RU: "DirectX SDK (июнь 2010) установлен в %s\n",
|
||||||
|
},
|
||||||
"download.workloads_header": {
|
"download.workloads_header": {
|
||||||
EN: "Available Workloads (%d):\n",
|
EN: "Available Workloads (%d):\n",
|
||||||
RU: "Доступные рабочие нагрузки (Workload) (%d):\n",
|
RU: "Доступные рабочие нагрузки (Workload) (%d):\n",
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ func Install(dest, selfBinary string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := aliasPlatformToolsets(dest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
includeDir := filepath.Join(msvcDir, "include")
|
includeDir := filepath.Join(msvcDir, "include")
|
||||||
if err := Lowercase(includeDir, LowercaseOptions{Symlink: true}); err != nil {
|
if err := Lowercase(includeDir, LowercaseOptions{Symlink: true}); err != nil {
|
||||||
return fmt.Errorf("lowercasing %s: %w", includeDir, err)
|
return fmt.Errorf("lowercasing %s: %w", includeDir, err)
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package install
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/Cheviiot/vintner/internal/wineenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rePlatformToolsetDir = regexp.MustCompile(`^v(\d+)$`)
|
||||||
|
|
||||||
|
// aliasPlatformToolsets makes every historical PlatformToolset name in
|
||||||
|
// wineenv.KnownPlatformToolsets resolve to the one compiler `download`
|
||||||
|
// actually fetched.
|
||||||
|
//
|
||||||
|
// MSBuild decides whether a PlatformToolset is "installed" at all - the
|
||||||
|
// check behind MSB8020 - by testing whether
|
||||||
|
// MSBuild/Microsoft/VC/v<schema>/Platforms/<arch>/PlatformToolsets/<toolset>/
|
||||||
|
// exists on disk (Microsoft.Cpp.props, via
|
||||||
|
// ToolLocationHelper.FindRootFolderWhereAllFilesExist). That's a plain file
|
||||||
|
// lookup, not influenced by any environment variable - unlike the later,
|
||||||
|
// env-var-driven VCInstallDir_<N> checks internal/wrapper's msbuildEnv
|
||||||
|
// covers, this one needs the actual directory to exist under dest.
|
||||||
|
// Microsoft's own downloaded MSBuild package only ships a PlatformToolsets
|
||||||
|
// entry for the exact generation matching the fetched compiler, so any
|
||||||
|
// project pinned to an older PlatformToolset (v142 for a project last saved
|
||||||
|
// under VS2019, say) fails this check outright even though the one real
|
||||||
|
// toolchain installed could easily build it.
|
||||||
|
//
|
||||||
|
// Toolset.props/Toolset.targets don't hardcode a version number (they just
|
||||||
|
// import version-agnostic files like Microsoft.Cpp.MSVC.Toolset.<arch>.props),
|
||||||
|
// so a symlink under any other historical name is a correct, transparent
|
||||||
|
// alias rather than a divergent copy.
|
||||||
|
func aliasPlatformToolsets(dest string) error {
|
||||||
|
schemaDirs, err := filepath.Glob(filepath.Join(dest, "MSBuild", "Microsoft", "VC", "v*"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, schemaDir := range schemaDirs {
|
||||||
|
archDirs, err := filepath.Glob(filepath.Join(schemaDir, "Platforms", "*", "PlatformToolsets"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, toolsetsDir := range archDirs {
|
||||||
|
if err := aliasOneDir(toolsetsDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliasOneDir symlinks every name in wineenv.KnownPlatformToolsets that
|
||||||
|
// doesn't already exist in toolsetsDir onto whichever real v<N> toolset
|
||||||
|
// subdirectory is actually present there.
|
||||||
|
func aliasOneDir(toolsetsDir string) error {
|
||||||
|
entries, err := os.ReadDir(toolsetsDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var real string
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rePlatformToolsetDir.MatchString(e.Name()) {
|
||||||
|
real = e.Name()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if real == "" {
|
||||||
|
// Nothing numeric here (e.g. only the WindowsKernelModeDriver10.0-style
|
||||||
|
// WDK toolsets) - nothing to alias.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range wineenv.KnownPlatformToolsets {
|
||||||
|
alias := "v" + n
|
||||||
|
if alias == real {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
aliasPath := filepath.Join(toolsetsDir, alias)
|
||||||
|
if exists(aliasPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Symlink(real, aliasPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package install
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Cheviiot/vintner/internal/wineenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAliasPlatformToolsetsAliasesRealToolset(t *testing.T) {
|
||||||
|
dest := t.TempDir()
|
||||||
|
toolsetsDir := filepath.Join(dest, "MSBuild", "Microsoft", "VC", "v180", "Platforms", "x64", "PlatformToolsets")
|
||||||
|
realDir := filepath.Join(toolsetsDir, "v145")
|
||||||
|
if err := os.MkdirAll(realDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(realDir, "Toolset.props"), []byte("<Project/>"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// A WDK toolset entry alongside it - must not be mistaken for the real
|
||||||
|
// numeric toolset or itself get aliased over.
|
||||||
|
if err := os.MkdirAll(filepath.Join(toolsetsDir, "WindowsKernelModeDriver10.0"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := aliasPlatformToolsets(dest); err != nil {
|
||||||
|
t.Fatalf("aliasPlatformToolsets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range wineenv.KnownPlatformToolsets {
|
||||||
|
alias := filepath.Join(toolsetsDir, "v"+n)
|
||||||
|
fi, err := os.Lstat(alias)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected v%s alias to exist: %v", n, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
t.Errorf("v%s should be a symlink, got mode %v", n, fi.Mode())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target, err := os.Readlink(alias)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if target != "v145" {
|
||||||
|
t.Errorf("v%s symlink target = %q, want \"v145\"", n, target)
|
||||||
|
}
|
||||||
|
// Follow the alias and confirm it actually reaches the real content.
|
||||||
|
if !isFile(filepath.Join(toolsetsDir, "v"+n, "Toolset.props")) {
|
||||||
|
t.Errorf("v%s/Toolset.props not reachable through the alias", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists(filepath.Join(toolsetsDir, "WindowsKernelModeDriver10.0", "v"+wineenv.KnownPlatformToolsets[0])) {
|
||||||
|
t.Error("WDK toolset directory should not have been touched")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAliasPlatformToolsetsDoesNotOverwriteExisting(t *testing.T) {
|
||||||
|
dest := t.TempDir()
|
||||||
|
toolsetsDir := filepath.Join(dest, "MSBuild", "Microsoft", "VC", "v180", "Platforms", "x64", "PlatformToolsets")
|
||||||
|
if err := os.MkdirAll(filepath.Join(toolsetsDir, "v145"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// v142 already genuinely installed (e.g. a real VS install with several
|
||||||
|
// side-by-side toolsets) - must be left alone, not replaced with an alias.
|
||||||
|
real142 := filepath.Join(toolsetsDir, "v142")
|
||||||
|
if err := os.MkdirAll(real142, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(real142, "Toolset.props"), []byte("<!-- real v142 -->"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := aliasPlatformToolsets(dest); err != nil {
|
||||||
|
t.Fatalf("aliasPlatformToolsets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Lstat(real142)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
t.Error("pre-existing v142 directory should not have been replaced with a symlink")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAliasPlatformToolsetsNoNumericToolset(t *testing.T) {
|
||||||
|
dest := t.TempDir()
|
||||||
|
// Only a WDK-style toolset present, nothing numeric to alias from.
|
||||||
|
toolsetsDir := filepath.Join(dest, "MSBuild", "Microsoft", "VC", "v180", "Platforms", "x64", "PlatformToolsets")
|
||||||
|
if err := os.MkdirAll(filepath.Join(toolsetsDir, "WindowsUserModeDriver10.0"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := aliasPlatformToolsets(dest); err != nil {
|
||||||
|
t.Fatalf("aliasPlatformToolsets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range wineenv.KnownPlatformToolsets {
|
||||||
|
if exists(filepath.Join(toolsetsDir, "v"+n)) {
|
||||||
|
t.Errorf("v%s should not have been created with no real numeric toolset present", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package wineenv
|
||||||
|
|
||||||
|
// KnownPlatformToolsets are every numeric PlatformToolset short name
|
||||||
|
// Microsoft.Cpp.Default.props has ever defined a
|
||||||
|
// _PlatformToolsetShortNameFor_v<N> entry for (VS2013 through the VS2022
|
||||||
|
// initial release; excludes the _xp/_wp80/_wp81 variants, which aren't
|
||||||
|
// purely numeric). vintner only ever installs one compiler generation, but
|
||||||
|
// real .vcxproj files in the wild are pinned to whichever generation they
|
||||||
|
// were last edited under - v142 (VS2019) for anything not yet retargeted is
|
||||||
|
// extremely common. Shared between internal/install (which symlinks these
|
||||||
|
// names onto the one real MSBuild PlatformToolsets directory) and
|
||||||
|
// internal/wrapper (which mirrors the same names onto VCInstallDir_<N>/
|
||||||
|
// VCToolsInstallDir_<N> for the older, environment-variable-driven toolset
|
||||||
|
// redirect chain) - see doc comments there for why both are needed.
|
||||||
|
var KnownPlatformToolsets = []string{"90", "100", "110", "120", "140", "141", "142", "143"}
|
||||||
+142
-18
@@ -29,10 +29,31 @@ func msbuildEnv(cfg *wineenv.Config, paths *wineenv.Paths) map[string]string {
|
|||||||
// tools onto the same UTC clock removes the mismatch.
|
// tools onto the same UTC clock removes the mismatch.
|
||||||
"TZ": "UTC",
|
"TZ": "UTC",
|
||||||
|
|
||||||
"DisableRegistryUse": "true",
|
// VCToolsVersion must be a real version string, not left unset:
|
||||||
"VCToolsVersion": cfg.MSVCVer,
|
// Microsoft.Cpp.VCTools.props itself falls back to the literal
|
||||||
"VsInstallRoot": paths.BaseWin + `\`,
|
// placeholder "VCToolsVersion_is_not_defined" whenever it's empty,
|
||||||
"VSInstallDir": paths.BaseWin + `\`,
|
// and that placeholder then reaches unconditional (not gated behind
|
||||||
|
// CheckMSVCComponents) version-string comparisons elsewhere in
|
||||||
|
// Microsoft.CppBuild.targets, e.g. the SegmentHeap manifest check's
|
||||||
|
// VersionGreaterThanOrEquals(), which throws MSB4184 on a
|
||||||
|
// non-version string.
|
||||||
|
//
|
||||||
|
// CheckMSVCComponents=false is what actually makes an aliased
|
||||||
|
// PlatformToolset safe to combine with that real version: without
|
||||||
|
// it, CheckVCToolsetVersion (Microsoft.CppBuild.targets, MSB8052)
|
||||||
|
// rejects the combination whenever VCToolsVersion's numeric
|
||||||
|
// generation doesn't match PlatformToolset's - exactly the legacy-
|
||||||
|
// project case toolsetSuffixes/aliasPlatformToolsets exist for (a
|
||||||
|
// v142 project against the one real, newer compiler actually
|
||||||
|
// installed). Everything else CheckMSVCComponents gates
|
||||||
|
// (Microsoft.CppBuild.targets ~495-535) is diagnostic-only - MFC/ATL/
|
||||||
|
// Spectre component presence warnings, none of it feeding into the
|
||||||
|
// actual compile/link - so disabling it costs nothing here.
|
||||||
|
"DisableRegistryUse": "true",
|
||||||
|
"CheckMSVCComponents": "false",
|
||||||
|
"VCToolsVersion": cfg.MSVCVer,
|
||||||
|
"VsInstallRoot": paths.BaseWin + `\`,
|
||||||
|
"VSInstallDir": paths.BaseWin + `\`,
|
||||||
|
|
||||||
"SDKReferenceDirectoryRoot": paths.BaseWin + `\`,
|
"SDKReferenceDirectoryRoot": paths.BaseWin + `\`,
|
||||||
"SDKExtensionDirectoryRoot": paths.BaseWin + `\`,
|
"SDKExtensionDirectoryRoot": paths.BaseWin + `\`,
|
||||||
@@ -58,20 +79,29 @@ func msbuildEnv(cfg *wineenv.Config, paths *wineenv.Paths) map[string]string {
|
|||||||
"Platform": msbuildPlatform(cfg.Arch),
|
"Platform": msbuildPlatform(cfg.Arch),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Microsoft.Cpp.props resolves the compiler/toolset location through
|
// VCInstallDir_<N>/VCToolsInstallDir_<N> are consulted under two
|
||||||
// VCInstallDir_<N>/VCToolsInstallDir_<N>, where <N> is whatever numeric
|
// completely different numbering schemes, both needing the single real
|
||||||
// suffix the installed MSBuild toolset property sheets use (e.g.
|
// toolchain behind every <N> they might ask for:
|
||||||
// .../MSBuild/Microsoft/VC/v180 -> "180"). Populate every one actually
|
//
|
||||||
// present, so a project pinned to any of them resolves to the one real
|
// - Microsoft.Cpp.Default.props keys its early "is this toolset even
|
||||||
// toolchain that's installed.
|
// installed" check (the one MSB8020 comes from) off <N> = the
|
||||||
matches, _ := filepath.Glob(filepath.Join(paths.BaseUnix, "MSBuild", "Microsoft", "VC", "v*"))
|
// PlatformToolset suffix a .vcxproj actually declares (v142, v143,
|
||||||
for _, m := range matches {
|
// v145, ...) - the same short name Microsoft stamps on
|
||||||
sub := reToolsetDir.FindStringSubmatch(filepath.Base(m))
|
// VC/Auxiliary/Build/Microsoft.VCToolsVersion.v<N>.default.props for
|
||||||
if sub == nil {
|
// the downloaded compiler.
|
||||||
continue
|
// - Microsoft.CppBuild.targets (MSB8070) instead keys off <N> = the
|
||||||
}
|
// MSBuild targets-schema version whose Microsoft.Cpp.props ended up
|
||||||
env["VCInstallDir_"+sub[1]] = paths.MSVCBaseWin + `\`
|
// imported for this run (MSBuild/Microsoft/VC/v150|v160|v170|v180 -
|
||||||
env["VCToolsInstallDir_"+sub[1]] = paths.MSVCDirWin + `\`
|
// fixed, shipped identically with every MSBuild release, unrelated to
|
||||||
|
// which compiler is installed), to locate the specific toolset
|
||||||
|
// version subfolder.
|
||||||
|
//
|
||||||
|
// Populate every <N> from both sources, all pointing at the one real
|
||||||
|
// toolchain that's installed, so a project pinned to any PlatformToolset
|
||||||
|
// resolves at every stage MSBuild checks it.
|
||||||
|
for _, n := range toolsetSuffixes(paths.BaseUnix) {
|
||||||
|
env["VCInstallDir_"+n] = paths.MSVCBaseWin + `\`
|
||||||
|
env["VCToolsInstallDir_"+n] = paths.MSVCDirWin + `\`
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(paths.MSBuildBinDir, "amd64") {
|
if strings.HasSuffix(paths.MSBuildBinDir, "amd64") {
|
||||||
@@ -94,6 +124,100 @@ func msbuildEnv(cfg *wineenv.Config, paths *wineenv.Paths) map[string]string {
|
|||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toolsetSuffixes collects every numeric <N> that either VCInstallDir_<N>
|
||||||
|
// lookup mechanism (see msbuildEnv) might be asked to resolve for this
|
||||||
|
// installation: PlatformToolset short names from
|
||||||
|
// vc/Auxiliary/Build/Microsoft.VCToolsVersion.v<N>.default.props, MSBuild
|
||||||
|
// targets-schema versions from MSBuild/Microsoft/VC/v<N>, and every
|
||||||
|
// historical PlatformToolset name (see wineenv.KnownPlatformToolsets) - a
|
||||||
|
// project pinned to any of them all resolves to the one real toolchain
|
||||||
|
// installed.
|
||||||
|
func toolsetSuffixes(baseUnix string) []string {
|
||||||
|
seen := map[string]bool{}
|
||||||
|
var suffixes []string
|
||||||
|
|
||||||
|
record := func(n string) {
|
||||||
|
if seen[n] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[n] = true
|
||||||
|
suffixes = append(suffixes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
add := func(dir, prefix, suffix string) {
|
||||||
|
matches, _ := filepath.Glob(filepath.Join(dir, "*"))
|
||||||
|
for _, m := range matches {
|
||||||
|
name := filepath.Base(m)
|
||||||
|
if prefix != "" {
|
||||||
|
if !strings.HasPrefix(name, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name = strings.TrimPrefix(name, prefix)
|
||||||
|
}
|
||||||
|
name = strings.TrimSuffix(name, suffix)
|
||||||
|
sub := reToolsetDir.FindStringSubmatch(name)
|
||||||
|
if sub == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
record(sub[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(filepath.Join(baseUnix, "vc", "Auxiliary", "Build"), "Microsoft.VCToolsVersion.", ".default.props")
|
||||||
|
add(filepath.Join(baseUnix, "MSBuild", "Microsoft", "VC"), "", "")
|
||||||
|
for _, n := range wineenv.KnownPlatformToolsets {
|
||||||
|
record(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return suffixes
|
||||||
|
}
|
||||||
|
|
||||||
|
// reGlobalProp matches an MSBuild global-property command-line switch
|
||||||
|
// ("/p:Name=...", "-property:Name=...", case-insensitive on both the
|
||||||
|
// -p/-property spelling and the property name) so msbuildGlobalArgs can tell
|
||||||
|
// whether the caller already pinned a given property themselves.
|
||||||
|
func reGlobalProp(name string) *regexp.Regexp {
|
||||||
|
return regexp.MustCompile(`(?i)^[-/](p|property):` + regexp.QuoteMeta(name) + `=`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// msbuildGlobalArgs returns /p: switches to prepend to an MSBuild invocation,
|
||||||
|
// one per forced property not already present in args.
|
||||||
|
//
|
||||||
|
// WindowsTargetPlatformVersion is the one property that needs this rather
|
||||||
|
// than an env var: unlike VCInstallDir_<N> (an input to a props-file
|
||||||
|
// *lookup*, so any value msbuildEnv sets is visible no matter what a project
|
||||||
|
// pins its PlatformToolset to), WindowsTargetPlatformVersion is itself the
|
||||||
|
// value most legacy .vcxproj files hardcode directly in a PropertyGroup -
|
||||||
|
// and an explicit PropertyGroup assignment always wins over an inherited
|
||||||
|
// environment variable of the same name. A command-line global property is
|
||||||
|
// the one thing a project file can't override, which is exactly what's
|
||||||
|
// needed here: vintner only ever installs one Windows SDK version, so - same
|
||||||
|
// reasoning as the PlatformToolset fallback above - any project should
|
||||||
|
// transparently build against that one installed version rather than fail
|
||||||
|
// outright over an exact version string it happened to be pinned to when
|
||||||
|
// last saved from a real Windows SDK selector dropdown.
|
||||||
|
func msbuildGlobalArgs(cfg *wineenv.Config, args []string) []string {
|
||||||
|
forced := map[string]string{
|
||||||
|
"WindowsTargetPlatformVersion": cfg.SDKVer,
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for name, value := range forced {
|
||||||
|
re := reGlobalProp(name)
|
||||||
|
alreadySet := false
|
||||||
|
for _, a := range args {
|
||||||
|
if re.MatchString(a) {
|
||||||
|
alreadySet = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !alreadySet {
|
||||||
|
out = append(out, "/p:"+name+"="+value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func msbuildPlatform(arch string) string {
|
func msbuildPlatform(arch string) string {
|
||||||
switch arch {
|
switch arch {
|
||||||
case "x86":
|
case "x86":
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ func TestMsbuildEnvBasics(t *testing.T) {
|
|||||||
if env["DisableRegistryUse"] != "true" {
|
if env["DisableRegistryUse"] != "true" {
|
||||||
t.Errorf(`env["DisableRegistryUse"] = %q, want "true"`, env["DisableRegistryUse"])
|
t.Errorf(`env["DisableRegistryUse"] = %q, want "true"`, env["DisableRegistryUse"])
|
||||||
}
|
}
|
||||||
|
if env["CheckMSVCComponents"] != "false" {
|
||||||
|
t.Errorf(`env["CheckMSVCComponents"] = %q, want "false" (else CheckVCToolsetVersion errors on an aliased PlatformToolset)`, env["CheckMSVCComponents"])
|
||||||
|
}
|
||||||
|
// VCToolsVersion must be a real version string (see msbuildEnv's doc
|
||||||
|
// comment on it: leaving it unset makes Microsoft.Cpp.VCTools.props
|
||||||
|
// substitute a placeholder that then breaks unconditional version
|
||||||
|
// comparisons elsewhere). CheckMSVCComponents=false is what keeps this
|
||||||
|
// safe to combine with an aliased PlatformToolset.
|
||||||
if env["VCToolsVersion"] != cfg.MSVCVer {
|
if env["VCToolsVersion"] != cfg.MSVCVer {
|
||||||
t.Errorf(`env["VCToolsVersion"] = %q, want %q`, env["VCToolsVersion"], cfg.MSVCVer)
|
t.Errorf(`env["VCToolsVersion"] = %q, want %q`, env["VCToolsVersion"], cfg.MSVCVer)
|
||||||
}
|
}
|
||||||
@@ -61,7 +69,24 @@ func TestMsbuildEnvDiscoversEveryToolsetVersion(t *testing.T) {
|
|||||||
cfg := &wineenv.Config{Arch: "x64", Host: "x64", DotnetHost: "amd64", MSVCVer: "14.51.36231", SDKVer: "10.0.26100.0"}
|
cfg := &wineenv.Config{Arch: "x64", Host: "x64", DotnetHost: "amd64", MSVCVer: "14.51.36231", SDKVer: "10.0.26100.0"}
|
||||||
paths, base := newTestPaths(t, cfg)
|
paths, base := newTestPaths(t, cfg)
|
||||||
|
|
||||||
for _, v := range []string{"v145", "v180", "not-a-version"} {
|
// Real layout has two independent sources feeding VCInstallDir_<N>/
|
||||||
|
// VCToolsInstallDir_<N> (see toolsetSuffixes' doc comment for why both
|
||||||
|
// are needed): the PlatformToolset short names a downloaded compiler
|
||||||
|
// ships default-props for, and MSBuild's own fixed schema-version dirs.
|
||||||
|
buildDir := filepath.Join(base, "vc", "Auxiliary", "Build")
|
||||||
|
if err := os.MkdirAll(buildDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, name := range []string{
|
||||||
|
"Microsoft.VCToolsVersion.v145.default.props",
|
||||||
|
"Microsoft.VCToolsVersion.v143.default.props",
|
||||||
|
"Microsoft.VCToolsVersion.default.props", // no version suffix - must not match
|
||||||
|
} {
|
||||||
|
if err := os.WriteFile(filepath.Join(buildDir, name), nil, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range []string{"v180", "not-a-version"} {
|
||||||
if err := os.MkdirAll(filepath.Join(base, "MSBuild", "Microsoft", "VC", v), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(base, "MSBuild", "Microsoft", "VC", v), 0o755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -69,7 +94,7 @@ func TestMsbuildEnvDiscoversEveryToolsetVersion(t *testing.T) {
|
|||||||
|
|
||||||
env := msbuildEnv(cfg, paths)
|
env := msbuildEnv(cfg, paths)
|
||||||
|
|
||||||
for _, n := range []string{"145", "180"} {
|
for _, n := range []string{"145", "143", "180"} {
|
||||||
if _, ok := env["VCInstallDir_"+n]; !ok {
|
if _, ok := env["VCInstallDir_"+n]; !ok {
|
||||||
t.Errorf("expected VCInstallDir_%s to be set", n)
|
t.Errorf("expected VCInstallDir_%s to be set", n)
|
||||||
}
|
}
|
||||||
@@ -82,6 +107,64 @@ func TestMsbuildEnvDiscoversEveryToolsetVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToolsetSuffixesDedupsOverlap(t *testing.T) {
|
||||||
|
base := t.TempDir()
|
||||||
|
buildDir := filepath.Join(base, "vc", "Auxiliary", "Build")
|
||||||
|
if err := os.MkdirAll(buildDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(buildDir, "Microsoft.VCToolsVersion.v180.default.props"), nil, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Join(base, "MSBuild", "Microsoft", "VC", "v180"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := toolsetSuffixes(base)
|
||||||
|
count := 0
|
||||||
|
for _, n := range got {
|
||||||
|
if n == "180" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
t.Errorf("toolsetSuffixes() returned %q with %d entries for \"180\" (from both sources), want exactly 1", got, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsbuildGlobalArgsForcesWindowsTargetPlatformVersion(t *testing.T) {
|
||||||
|
cfg := &wineenv.Config{SDKVer: "10.0.26100.0"}
|
||||||
|
|
||||||
|
got := msbuildGlobalArgs(cfg, []string{"Foo.sln", "/p:Configuration=Release"})
|
||||||
|
want := "/p:WindowsTargetPlatformVersion=10.0.26100.0"
|
||||||
|
found := false
|
||||||
|
for _, a := range got {
|
||||||
|
if a == want {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("msbuildGlobalArgs(...) = %v, want it to contain %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsbuildGlobalArgsRespectsExplicitOverride(t *testing.T) {
|
||||||
|
cfg := &wineenv.Config{SDKVer: "10.0.26100.0"}
|
||||||
|
|
||||||
|
for _, explicit := range []string{
|
||||||
|
"/p:WindowsTargetPlatformVersion=10.0.19041.0",
|
||||||
|
"-p:WindowsTargetPlatformVersion=10.0.19041.0",
|
||||||
|
"/property:WindowsTargetPlatformVersion=10.0.19041.0",
|
||||||
|
} {
|
||||||
|
got := msbuildGlobalArgs(cfg, []string{"Foo.sln", explicit})
|
||||||
|
for _, a := range got {
|
||||||
|
if reGlobalProp("WindowsTargetPlatformVersion").MatchString(a) {
|
||||||
|
t.Errorf("msbuildGlobalArgs with explicit %q also injected %q - should have left the caller's value alone", explicit, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMsbuildEnvDetectsWDKContentRoot(t *testing.T) {
|
func TestMsbuildEnvDetectsWDKContentRoot(t *testing.T) {
|
||||||
cfg := &wineenv.Config{Arch: "x64", Host: "x64", DotnetHost: "amd64", MSVCVer: "14.51.36231", SDKVer: "10.0.26100.0"}
|
cfg := &wineenv.Config{Arch: "x64", Host: "x64", DotnetHost: "amd64", MSVCVer: "14.51.36231", SDKVer: "10.0.26100.0"}
|
||||||
paths, base := newTestPaths(t, cfg)
|
paths, base := newTestPaths(t, cfg)
|
||||||
|
|||||||
@@ -83,8 +83,10 @@ func Run(tool string, args []string) int {
|
|||||||
// MSBuild: skip all filtering/toolrelay (its output is meant to be
|
// MSBuild: skip all filtering/toolrelay (its output is meant to be
|
||||||
// read as-is), and add the extra environment MSBuild's own
|
// read as-is), and add the extra environment MSBuild's own
|
||||||
// toolset/SDK-detection props need on top of the generic
|
// toolset/SDK-detection props need on top of the generic
|
||||||
// INCLUDE/LIB/WINEPATH.
|
// INCLUDE/LIB/WINEPATH, plus any global properties a project file
|
||||||
cmd := exec.Command(wineBin, append([]string{toolExePath}, rewritten...)...)
|
// itself could otherwise override (see msbuildGlobalArgs).
|
||||||
|
msArgs := append(msbuildGlobalArgs(cfg, rewritten), rewritten...)
|
||||||
|
cmd := exec.Command(wineBin, append([]string{toolExePath}, msArgs...)...)
|
||||||
env := buildEnv(paths)
|
env := buildEnv(paths)
|
||||||
for k, v := range msbuildEnv(cfg, paths) {
|
for k, v := range msbuildEnv(cfg, paths) {
|
||||||
env = append(env, k+"="+v)
|
env = append(env, k+"="+v)
|
||||||
|
|||||||
Reference in New Issue
Block a user