mirror of
https://github.com/Cheviiot/Vintner.git
synced 2026-08-03 15:57:24 +00:00
Add Windows Driver Kit (WDK) support for building KMDF/UMDF drivers
download --with-wdk fetches the WDK headers/libs/host-tools NuGet packages (nuget.org has no vsman-manifest entry for this content) and lays them out where the DriverKit.BuildTools PlatformToolset expects them. msbuildEnv wires WDKContentRoot/WDKBuildFolder through DisableRegistryUse the same way the SDK/toolset paths already are. Two WDK-package fixups were needed for a real driver to actually build under Wine: the bundled build-task assembly is versioned for an older VisualStudioVersion than ours, and the package ships no x86 host-tools directory at all (only x64/arm64), which StampInf hardcodes a path to. Also force TZ=UTC for msbuild invocations: StampInf stamps DriverVer using the local wall-clock date while Inf2Cat validates it against UTC "now", so any timezone east of UTC sees a "postdated DriverVer" failure for most of the day. Verified against a real KMDF sample driver (microsoft/Windows-driver- samples' echo_2): compiles, links, INF stamps and passes Inf2Cat's signability check with SignMode=off.
This commit is contained in:
@@ -43,7 +43,15 @@ type Options struct {
|
||||
SkipRecommended bool
|
||||
Language string
|
||||
|
||||
WithWDKInstallers string
|
||||
// WithWDK selects the PlatformToolset registration
|
||||
// (Component.Microsoft.Windows.DriverKit.BuildTools) that lets MSBuild
|
||||
// recognize WindowsKernelModeDriver10.0/WindowsUserModeDriver10.0
|
||||
// PlatformToolsets. It's not part of the manifest-driven default
|
||||
// selection - unlike everything else it depends on, it's opt-in via
|
||||
// --with-wdk, since the actual driver headers/libs come from a separate
|
||||
// NuGet package download handled outside ExpandSelection entirely (see
|
||||
// wdk.go and cmd/msvc-go-wine's runDownload).
|
||||
WithWDK bool
|
||||
}
|
||||
|
||||
func addIfWanted(opts *Options, flag TriState, pkg string) {
|
||||
@@ -242,7 +250,7 @@ func ResolveSelection(opts *Options, idx Index) error {
|
||||
addIfWanted(opts, opts.WithDevCmd, "Microsoft.VisualStudio.VC.vcvars")
|
||||
addIfWanted(opts, opts.WithDevCmd, "Microsoft.VisualStudio.PackageGroup.VsDevCmd")
|
||||
|
||||
if opts.WithWDKInstallers != "" {
|
||||
if opts.WithWDK {
|
||||
opts.Package = append(opts.Package, "Component.Microsoft.Windows.DriverKit.BuildTools")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
package download
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The actual driver headers/libs (ntddk.h, wdf, km/um import libs) aren't
|
||||
// part of the VS installer manifest at all - Component.Microsoft.Windows.
|
||||
// DriverKit.BuildTools (see select.go) only registers the
|
||||
// WindowsKernelModeDriver10.0/WindowsUserModeDriver10.0 PlatformToolsets in
|
||||
// the MSBuild tree. The content those toolsets actually need ships as a
|
||||
// separate per-architecture NuGet package, independently of the vsman
|
||||
// pipeline: https://www.nuget.org/packages/Microsoft.Windows.WDK.x64 (and
|
||||
// .ARM64). This file fetches and unpacks that package directly through the
|
||||
// NuGet v3 flat-container API, without needing nuget.exe or a project
|
||||
// restore.
|
||||
|
||||
const nugetFlatContainer = "https://api.nuget.org/v3-flatcontainer"
|
||||
|
||||
// WDKNuGetID returns the nuget.org package id providing WDK content for
|
||||
// arch ("x86"/"x64" both use the x64 package - there's no 32-bit WDK
|
||||
// package - "arm64" uses the ARM64 one).
|
||||
func WDKNuGetID(arch string) string {
|
||||
if arch == "arm64" {
|
||||
return "Microsoft.Windows.WDK.ARM64"
|
||||
}
|
||||
return "Microsoft.Windows.WDK.x64"
|
||||
}
|
||||
|
||||
// nugetVersionIndex is the "versions" list nuget.org's flat-container index
|
||||
// returns, oldest first.
|
||||
type nugetVersionIndex struct {
|
||||
Versions []string `json:"versions"`
|
||||
}
|
||||
|
||||
var reNuGetPrerelease = regexp.MustCompile(`-`)
|
||||
|
||||
// FetchLatestWDKVersion returns the newest stable (non-prerelease) version
|
||||
// of arch's WDK NuGet package, preferring one whose version starts with
|
||||
// sdkBuild (e.g. "10.0.26100", to match an already-selected Windows SDK) if
|
||||
// any such version exists; otherwise it returns the newest stable version
|
||||
// overall.
|
||||
func FetchLatestWDKVersion(arch, sdkBuild string) (string, error) {
|
||||
id := strings.ToLower(WDKNuGetID(arch))
|
||||
data, err := httpGet(nugetFlatContainer + "/" + id + "/index.json")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("listing %s versions: %w", WDKNuGetID(arch), err)
|
||||
}
|
||||
var idx nugetVersionIndex
|
||||
if err := json.Unmarshal(data, &idx); err != nil {
|
||||
return "", fmt.Errorf("parsing %s version index: %w", WDKNuGetID(arch), err)
|
||||
}
|
||||
|
||||
var latestMatching, latestAny string
|
||||
for _, v := range idx.Versions {
|
||||
if reNuGetPrerelease.MatchString(v) {
|
||||
continue
|
||||
}
|
||||
latestAny = v
|
||||
if sdkBuild != "" && strings.HasPrefix(v, sdkBuild) {
|
||||
latestMatching = v
|
||||
}
|
||||
}
|
||||
if latestMatching != "" {
|
||||
return latestMatching, nil
|
||||
}
|
||||
if latestAny != "" {
|
||||
return latestAny, nil
|
||||
}
|
||||
return "", fmt.Errorf("no stable release of %s found", WDKNuGetID(arch))
|
||||
}
|
||||
|
||||
// SDKBuildPrefix extracts the "10.0.26100"-style build prefix from a
|
||||
// selected Win10SDK/Win11SDK package's version (e.g. "10.0.26100.1742" ->
|
||||
// "10.0.26100"), used to pick a matching WDK NuGet version. Returns "" if
|
||||
// selected contains no such package.
|
||||
func SDKBuildPrefix(selected []*Package) string {
|
||||
for _, p := range selected {
|
||||
id := strings.ToLower(p.ID)
|
||||
if strings.HasPrefix(id, "win10sdk") || strings.HasPrefix(id, "win11sdk") {
|
||||
parts := strings.SplitN(p.Version, ".", 4)
|
||||
if len(parts) >= 3 {
|
||||
return strings.Join(parts[:3], ".")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DownloadWDK fetches (or reuses a cached copy of) arch's WDK NuGet package
|
||||
// at version, then unpacks its "c" directory - headers, libs, and the
|
||||
// per-SDK-build MSBuild props/targets the DriverKit.BuildTools PlatformToolset
|
||||
// registration imports via $(WDKContentRoot) - into destDir/wdk/<arch>/c.
|
||||
// Kept as its own self-contained tree rather than merged into the regular
|
||||
// SDK dirs, matching how $(WDKContentRoot) is meant to be used.
|
||||
//
|
||||
// vsVersion is the installed MSBuild's own $(VisualStudioVersion) (e.g.
|
||||
// "18.0"): the package's WindowsDriver.common.targets loads its custom
|
||||
// build tasks from an assembly named for that property
|
||||
// (Microsoft.DriverKit.Build.Tasks.$(VisualStudioVersion).dll), but the
|
||||
// package only ships one built for whatever (older) VS generation it
|
||||
// targeted - see duplicateVersionedBuildTaskAssemblies.
|
||||
func DownloadWDK(arch, version, cacheDir, destDir, vsVersion string) (string, error) {
|
||||
id := WDKNuGetID(arch)
|
||||
idLower := strings.ToLower(id)
|
||||
nupkgURL := fmt.Sprintf("%s/%s/%s/%s.%s.nupkg", nugetFlatContainer, idLower, version, idLower, version)
|
||||
|
||||
cacheFile := filepath.Join(cacheDir, fmt.Sprintf("%s-%s.nupkg", idLower, version))
|
||||
if !isFile(cacheFile) {
|
||||
fmt.Printf("Downloading %s %s\n", id, version)
|
||||
if err := httpDownloadFile(nupkgURL, cacheFile); err != nil {
|
||||
return "", fmt.Errorf("downloading %s %s: %w", id, version, err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Using existing file %s\n", filepath.Base(cacheFile))
|
||||
}
|
||||
|
||||
wdkDir := filepath.Join(destDir, "wdk", arch)
|
||||
cDir := filepath.Join(wdkDir, "c")
|
||||
if err := extractNuGetPackageDir(cacheFile, "c/", cDir); err != nil {
|
||||
return "", fmt.Errorf("unpacking %s %s: %w", id, version, err)
|
||||
}
|
||||
if err := duplicateVersionedBuildTaskAssemblies(cDir, vsVersion); err != nil {
|
||||
return "", fmt.Errorf("adapting %s %s to VisualStudioVersion %s: %w", id, version, vsVersion, err)
|
||||
}
|
||||
if err := fillMissingHostTools(cDir); err != nil {
|
||||
return "", fmt.Errorf("filling in missing x86 host tools for %s %s: %w", id, version, err)
|
||||
}
|
||||
return wdkDir, nil
|
||||
}
|
||||
|
||||
// fillMissingHostTools copies every file from each cDir/bin/<sdkver>/x64
|
||||
// directory into its sibling .../x86 directory, adding whatever's missing
|
||||
// without overwriting anything already there. The WDK NuGet package's x86
|
||||
// host-tool directory only ships a handful of genuinely x86-specific files
|
||||
// (Inf2Cat.exe among them); most driver build tools (stampinf.exe included)
|
||||
// only ship as x64 binaries, but WindowsDriver.Common.targets hardcodes an
|
||||
// x86 tool path (WDKBinRoot_x86) with no fallback. This isn't "faking" an
|
||||
// x86 binary - it's just running the real x64 PE binaries from a directory
|
||||
// MSBuild happens to call "x86"; Wine doesn't care what the folder is
|
||||
// named, only the PE header when it execs the file, and we're always on an
|
||||
// x64 host/Wine setup here.
|
||||
func fillMissingHostTools(cDir string) error {
|
||||
binDir := filepath.Join(cDir, "bin")
|
||||
entries, err := os.ReadDir(binDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
x64Dir := filepath.Join(binDir, e.Name(), "x64")
|
||||
if !isDir(x64Dir) {
|
||||
continue
|
||||
}
|
||||
x86Dir := filepath.Join(binDir, e.Name(), "x86")
|
||||
if err := os.MkdirAll(x86Dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
files, err := os.ReadDir(x64Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
dst := filepath.Join(x86Dir, f.Name())
|
||||
if isFile(dst) {
|
||||
continue
|
||||
}
|
||||
if err := copyFile(filepath.Join(x64Dir, f.Name()), dst, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var reVersionedDLL = regexp.MustCompile(`^(.*)\.\d+\.\d+\.dll$`)
|
||||
|
||||
// duplicateVersionedBuildTaskAssemblies copies every "*.<oldver>.dll" file
|
||||
// under cDir/build (the driver build task assemblies, e.g.
|
||||
// Microsoft.DriverKit.Build.Tasks.17.0.dll) to a sibling
|
||||
// "*.<targetVSVersion>.dll" when one doesn't already exist. The WDK NuGet
|
||||
// package bundles these named for whatever VS generation it was built
|
||||
// against; our installed MSBuild may be newer and looks them up by its own
|
||||
// $(VisualStudioVersion). The assemblies aren't VS-version-specific in
|
||||
// behavior - only the file name encodes an expected caller - so serving the
|
||||
// same bytes under the new name is safe.
|
||||
func duplicateVersionedBuildTaskAssemblies(cDir, targetVSVersion string) error {
|
||||
buildDir := filepath.Join(cDir, "build")
|
||||
if !isDir(buildDir) {
|
||||
return nil
|
||||
}
|
||||
return filepath.WalkDir(buildDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
m := reVersionedDLL.FindStringSubmatch(d.Name())
|
||||
if m == nil || strings.HasSuffix(d.Name(), "."+targetVSVersion+".dll") {
|
||||
return nil
|
||||
}
|
||||
target := filepath.Join(filepath.Dir(path), m[1]+"."+targetVSVersion+".dll")
|
||||
if isFile(target) {
|
||||
return nil
|
||||
}
|
||||
return copyFile(path, target, 0o644)
|
||||
})
|
||||
}
|
||||
|
||||
// extractNuGetPackageDir extracts every entry of nupkgFile whose name starts
|
||||
// with prefix into dest, stripping prefix from each resulting path.
|
||||
func extractNuGetPackageDir(nupkgFile, prefix, dest string) error {
|
||||
r, err := zip.OpenReader(nupkgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if err := os.MkdirAll(dest, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range r.File {
|
||||
name, err := url.PathUnescape(f.Name)
|
||||
if err != nil {
|
||||
name = f.Name
|
||||
}
|
||||
name = strings.ReplaceAll(name, `\`, "/")
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
continue
|
||||
}
|
||||
rel := strings.TrimPrefix(name, prefix)
|
||||
if rel == "" {
|
||||
continue
|
||||
}
|
||||
target := filepath.Join(dest, rel)
|
||||
if f.FileInfo().IsDir() {
|
||||
if err := os.MkdirAll(target, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := extractZipEntry(f, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package wrapper
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -19,12 +20,20 @@ var reToolsetDir = regexp.MustCompile(`^v(\d+)$`)
|
||||
// Windows Registry.
|
||||
func msbuildEnv(cfg *wineenv.Config, paths *wineenv.Paths) map[string]string {
|
||||
env := map[string]string{
|
||||
// WDK driver builds stamp the INF's DriverVer with StampInf (which
|
||||
// uses the local wall-clock date) and then validate it with
|
||||
// Inf2Cat (which checks against UTC "now"). For any timezone east
|
||||
// of UTC, local-vs-UTC disagree on the calendar date for most of
|
||||
// the day, so Inf2Cat rejects the just-stamped date as "postdated"
|
||||
// (MSB6006, "DriverVer set to a date in the future"). Forcing both
|
||||
// tools onto the same UTC clock removes the mismatch.
|
||||
"TZ": "UTC",
|
||||
|
||||
"DisableRegistryUse": "true",
|
||||
"VCToolsVersion": cfg.MSVCVer,
|
||||
"VsInstallRoot": paths.BaseWin + `\`,
|
||||
"VSInstallDir": paths.BaseWin + `\`,
|
||||
|
||||
"MicrosoftKitRoot": paths.BaseWin + `\`,
|
||||
"SDKReferenceDirectoryRoot": paths.BaseWin + `\`,
|
||||
"SDKExtensionDirectoryRoot": paths.BaseWin + `\`,
|
||||
"MSBUILDSDKREFERENCEDIRECTORY": paths.BaseWin + `\`,
|
||||
@@ -69,6 +78,19 @@ func msbuildEnv(cfg *wineenv.Config, paths *wineenv.Paths) map[string]string {
|
||||
env["PreferredToolArchitecture"] = "x64"
|
||||
}
|
||||
|
||||
// The WindowsKernelModeDriver10.0/WindowsUserModeDriver10.0
|
||||
// PlatformToolsets (registered by `download --with-wdk`, see
|
||||
// internal/download/wdk.go) resolve WDKContentRoot through the
|
||||
// (nonexistent, under Wine) registry unless it's already set - same
|
||||
// DisableRegistryUse workaround as WindowsSdkDir_10 above. WDKBuildFolder
|
||||
// picks the per-SDK-build subtree (c/build/<ver>/...) the NuGet
|
||||
// package's content is organized under.
|
||||
wdkContentRoot := filepath.Join(paths.BaseUnix, "wdk", cfg.Arch, "c")
|
||||
if fi, err := os.Stat(wdkContentRoot); err == nil && fi.IsDir() {
|
||||
env["WDKContentRoot"] = wineenv.ToWinPath(wdkContentRoot) + `\`
|
||||
env["WDKBuildFolder"] = cfg.SDKVer
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user