mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-04 00:07:24 +00:00
Initial implementation of msvc-go-wine
A single-binary Go tool for cross compiling with the real MSVC toolchain on Linux via Wine. Behaves as cl/link/lib/rc/midl/mt/dumpbin/msbuild/ nmake/ml/ml64/armasm/armasm64/cmd/findstr depending on the name it's invoked as, plus download/install/env/version management subcommands. - download: fetches the MSVC/WinSDK installer manifest, resolves package selection and dependencies, downloads and verifies payloads, unpacks VSIX/MSI packages, and applies a handful of compatibility patches so VsDevCmd.bat and MSBuild's SDK detection work without a Windows Registry (which doesn't exist under Wine). - install: locates the installed toolchain/SDK versions, normalizes header/library name casing, lays out per-architecture tool symlinks with an env.json config each, and compiles a small native launcher (toolrelay.exe) that lets mt.exe's CMake-compatibility exit code survive Wine's own exit-code truncation. - The wrapper runtime rewrites absolute unix paths in tool arguments into Wine's z:\... form, runs the real .exe under wine, and rewrites the tool's output back to plain unix paths. Verified end-to-end against a real MSVC/WinSDK download: cl, link, mt and the resulting hello.exe all work under Wine, including through the toolrelay.exe relay path and with paths containing non-ASCII characters. Offline unit tests cover the wrapper's path-rewrite/output-filter logic, install-time header lowercasing, and download package-selection/ dependency-resolution.
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// fixtureManifest is a trimmed-down stand-in for a real installer manifest,
|
||||
// covering: a workload -> component -> {required, Optional, Recommended}
|
||||
// dependency chain, host-arch variants of the same package id, and
|
||||
// language variants - enough to exercise BuildIndex/collectDependencyClosure
|
||||
// offline, without ever hitting the network.
|
||||
const fixtureManifestJSON = `{
|
||||
"info": {"productDisplayVersion": "test"},
|
||||
"packages": [
|
||||
{
|
||||
"id": "Microsoft.VisualStudio.Workload.VCTools",
|
||||
"type": "Workload",
|
||||
"dependencies": {
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"type": "Component",
|
||||
"dependencies": {
|
||||
"Microsoft.VC.Tools.Core": "1.0",
|
||||
"Microsoft.VC.Tools.Optional": {"version": "1.0", "type": "Optional"},
|
||||
"Microsoft.VC.Tools.Recommended": {"version": "1.0", "type": "Recommended"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VC.Tools.Core",
|
||||
"type": "Msi",
|
||||
"machineArch": "x86",
|
||||
"payloads": [{"fileName": "core-x86.msi", "url": "https://example.invalid/core-x86.msi", "sha256": "aa", "size": 100}]
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VC.Tools.Core",
|
||||
"type": "Msi",
|
||||
"machineArch": "arm64",
|
||||
"payloads": [{"fileName": "core-arm64.msi", "url": "https://example.invalid/core-arm64.msi", "sha256": "bb", "size": 200}]
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VC.Tools.Optional",
|
||||
"type": "Msi",
|
||||
"payloads": [{"fileName": "optional.msi", "url": "https://example.invalid/optional.msi", "size": 10}]
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VC.Tools.Recommended",
|
||||
"type": "Msi",
|
||||
"payloads": [{"fileName": "recommended.msi", "url": "https://example.invalid/recommended.msi", "size": 20}]
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VisualStudio.Resources",
|
||||
"type": "Msi",
|
||||
"language": "en-US",
|
||||
"payloads": [{"fileName": "res-en.msi", "url": "https://example.invalid/res-en.msi", "size": 1}]
|
||||
},
|
||||
{
|
||||
"id": "Microsoft.VisualStudio.Resources",
|
||||
"type": "Msi",
|
||||
"language": "de-DE",
|
||||
"payloads": [{"fileName": "res-de.msi", "url": "https://example.invalid/res-de.msi", "size": 1}]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
func fixtureIndex(t *testing.T, hostArch string) Index {
|
||||
t.Helper()
|
||||
var m Manifest
|
||||
if err := json.Unmarshal([]byte(fixtureManifestJSON), &m); err != nil {
|
||||
t.Fatalf("parsing fixture manifest: %v", err)
|
||||
}
|
||||
return BuildIndex(&m, hostArch, "en")
|
||||
}
|
||||
|
||||
func TestBuildIndexPrioritizesHostArch(t *testing.T) {
|
||||
idx := fixtureIndex(t, "x86")
|
||||
p := idx.Find("Microsoft.VC.Tools.Core", nil)
|
||||
if p == nil {
|
||||
t.Fatal("expected to find Microsoft.VC.Tools.Core")
|
||||
}
|
||||
if p.MachineArch != "x86" {
|
||||
t.Errorf("expected x86 variant to sort first for host x86, got machineArch=%q", p.MachineArch)
|
||||
}
|
||||
|
||||
idx64 := fixtureIndex(t, "arm64")
|
||||
p64 := idx64.Find("Microsoft.VC.Tools.Core", nil)
|
||||
if p64.MachineArch != "arm64" {
|
||||
t.Errorf("expected arm64 variant to sort first for host arm64, got machineArch=%q", p64.MachineArch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildIndexPrioritizesLanguage(t *testing.T) {
|
||||
idx := fixtureIndex(t, "x86")
|
||||
p := idx.Find("Microsoft.VisualStudio.Resources", nil)
|
||||
if p == nil {
|
||||
t.Fatal("expected to find Microsoft.VisualStudio.Resources")
|
||||
}
|
||||
if p.Language != "en-US" {
|
||||
t.Errorf("expected en-US variant to sort first for language=en, got %q", p.Language)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateDependsDefaultExcludesOptionalOnly(t *testing.T) {
|
||||
// Optional deps are skipped unless --include-optional is passed, but
|
||||
// Recommended deps are pulled in unless --skip-recommended is passed -
|
||||
// it's "recommended", not "extra".
|
||||
idx := fixtureIndex(t, "x86")
|
||||
opts := &Options{
|
||||
Package: []string{"Microsoft.VisualStudio.Workload.VCTools"},
|
||||
HostArch: "x86",
|
||||
OnlyHost: true,
|
||||
}
|
||||
selected, err := ExpandSelection(idx, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := idsOf(selected)
|
||||
|
||||
mustContain(t, ids, "Microsoft.VisualStudio.Workload.VCTools")
|
||||
mustContain(t, ids, "Microsoft.VisualStudio.Component.VC.Tools.x86.x64")
|
||||
mustContain(t, ids, "Microsoft.VC.Tools.Core")
|
||||
mustContain(t, ids, "Microsoft.VC.Tools.Recommended")
|
||||
mustNotContain(t, ids, "Microsoft.VC.Tools.Optional")
|
||||
}
|
||||
|
||||
func TestAggregateDependsIncludeOptional(t *testing.T) {
|
||||
idx := fixtureIndex(t, "x86")
|
||||
opts := &Options{
|
||||
Package: []string{"Microsoft.VisualStudio.Workload.VCTools"},
|
||||
HostArch: "x86",
|
||||
OnlyHost: true,
|
||||
IncludeOptional: true,
|
||||
}
|
||||
selected, err := ExpandSelection(idx, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := idsOf(selected)
|
||||
mustContain(t, ids, "Microsoft.VC.Tools.Optional")
|
||||
// Recommended is included by default (only --skip-recommended excludes it).
|
||||
mustContain(t, ids, "Microsoft.VC.Tools.Recommended")
|
||||
}
|
||||
|
||||
func TestAggregateDependsSkipRecommended(t *testing.T) {
|
||||
idx := fixtureIndex(t, "x86")
|
||||
opts := &Options{
|
||||
Package: []string{"Microsoft.VisualStudio.Workload.VCTools"},
|
||||
HostArch: "x86",
|
||||
OnlyHost: true,
|
||||
SkipRecommended: true,
|
||||
}
|
||||
selected, err := ExpandSelection(idx, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := idsOf(selected)
|
||||
mustNotContain(t, ids, "Microsoft.VC.Tools.Recommended")
|
||||
}
|
||||
|
||||
func TestAggregateDependsRespectsIgnore(t *testing.T) {
|
||||
idx := fixtureIndex(t, "x86")
|
||||
opts := &Options{
|
||||
Package: []string{"Microsoft.VisualStudio.Workload.VCTools"},
|
||||
Ignore: []string{"microsoft.vc.tools.core"},
|
||||
HostArch: "x86",
|
||||
OnlyHost: true,
|
||||
}
|
||||
selected, err := ExpandSelection(idx, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := idsOf(selected)
|
||||
mustNotContain(t, ids, "Microsoft.VC.Tools.Core")
|
||||
mustContain(t, ids, "Microsoft.VisualStudio.Component.VC.Tools.x86.x64")
|
||||
}
|
||||
|
||||
func TestAggregateDependsHostArchMismatchExcludes(t *testing.T) {
|
||||
idx := fixtureIndex(t, "x86")
|
||||
opts := &Options{
|
||||
Package: []string{"Microsoft.VC.Tools.Core"},
|
||||
HostArch: "arm", // no arm variant exists, only x86/arm64
|
||||
OnlyHost: true,
|
||||
}
|
||||
selected, err := ExpandSelection(idx, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(selected) != 0 {
|
||||
t.Errorf("expected no packages selected for mismatched host arch, got %v", idsOf(selected))
|
||||
}
|
||||
}
|
||||
|
||||
func idsOf(pkgs []*Package) []string {
|
||||
var ids []string
|
||||
for _, p := range pkgs {
|
||||
ids = append(ids, p.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func mustContain(t *testing.T, ids []string, want string) {
|
||||
t.Helper()
|
||||
for _, id := range ids {
|
||||
if id == want {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("expected %v to contain %q", ids, want)
|
||||
}
|
||||
|
||||
func mustNotContain(t *testing.T, ids []string, unwanted string) {
|
||||
t.Helper()
|
||||
for _, id := range ids {
|
||||
if id == unwanted {
|
||||
t.Errorf("expected %v to NOT contain %q", ids, unwanted)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user