mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
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.
222 lines
6.5 KiB
Go
222 lines
6.5 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|