blob: e2df3cb122ac8569fb5bca6be6328627b1bfd060 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is a tool to pack a minimal Windows SDK package that allows clang to compile or
// cross compile a binary targeting Windows. Before running this tool, please make sure
// you have Visual Studio or Visual Studio BuildTools and Windows SDK installed. The
// tool was tested with following
// installation procedures:
//
// - Download Visual Studio or Visual Studio BuildTools installer
// - Install Visual Studio Build Tools and Windows SDK.
//
// To install the necessary components, you can use the following command:
//
// .\vs_BuildTools.exe --passive ^
// --add Microsoft.VisualStudio.Component.VC.CoreBuildTools ^
// --add Microsoft.VisualStudio.Component.VC.CoreIde ^
// --add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core ^
// --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ^
// --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 ^
// --add Microsoft.VisualStudio.Component.Windows10SDK ^
// --add Microsoft.VisualStudio.Component.Windows10SDK.19041 ^
// --add Microsoft.VisualStudio.Component.VC.Redist.14.Latest ^
// --add Microsoft.VisualStudio.Component.VC.ATLMFC ^
//
// To make this go script runnable from anywhere, please avoid adding third-party
// dependencies.
package main
import (
"archive/zip"
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"hash"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"text/template" // NOLINT
"time"
)
const (
// VC Runtime versions from VS2022 and 2019 are started with 14.
// NOTE: this value may need to be updated in the future.
VcrtVersionWildcard = "14.*.*"
// When using a newer WinSDK which primarily targets Windows 11,
// this number will be 11.
WinKitVersion = "10"
DefaultSDKVersion = "10.0.19041.0"
DefaultVSVersion = "2022"
)
var (
sdkVersion string
dryRun bool
outputPath string
vsVersion string
archivePath string
help bool
)
func init() {
flag.StringVar(&sdkVersion, "sdkversion", DefaultSDKVersion, fmt.Sprintf("Windows SDK version, such as \"%s\"", DefaultSDKVersion))
flag.BoolVar(&dryRun, "dryrun", false, "scan for file existence and prints statistics")
flag.StringVar(&outputPath, "output", "", "output directory, such as \"winsdk\"")
flag.StringVar(&vsVersion, "vs", DefaultVSVersion, fmt.Sprintf("Visual Studio version, such as \"%s\"", DefaultVSVersion))
flag.StringVar(&archivePath, "archive", "", "generate the SDK package as an archive instead of a package, cannot be used when \"output\" is already defined")
flag.BoolVar(&help, "help", false, "show help information")
}
type Lock struct {
Filename string `json:"filename"`
Hash string `json:"hash"`
}
type Locks []Lock
func (l Locks) Len() int {
return len(l)
}
func (l Locks) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
func (l Locks) Less(i, j int) bool {
return l[i].Filename < l[j].Filename
}
// The lock file generated from this script saves the time stamp, hash for the
// entire SDK archive and hashes of the content of the top level directories
// inside the SDK archive. Example of the content of a lock file:
// {
//
// "updated": "2022-06-24T15:55:38.3696424-07:00",
// "hash": "daf5d8e93d54333f0b8690bf38bd4316eb4f81076c227e7b9dac163dae7ebe09",
// "files": [
// {
// "filename": "VC",
// "hash": "95bee54f7804be006e19c899c8c04034cd13c4ca98bf53078055534a748655ca"
// },
// {
// "filename": "Windows Kits",
// "hash": "2a32f7edbe257fe69c6f9fb7347bd58c6277180090c3e1381e236fac1a77afdf"
// },
// {
// "filename": "bin",
// "hash": "f316714a5dd38e09b815cfed682224fc62b0fc701dac88c268fb2b463412ccbc"
// },
// {
// "filename": "redist",
// "hash": "cff8e1746dc357fe6c732bfe05b881b2df97d215e77f3425b8c142e964d7b72f"
// },
// {
// "filename": "sysarm64",
// "hash": "424a42f754b254da10793b60d289eaa2cbcd77dc2e8a2ec62601200ee5cc4416"
// }
// ]
//
// }
type VFSOverlay struct {
Version int `json:"version"`
CaseSensitive bool `json:"case-sensitive"`
RootRelative string `json:"root-relative"`
OverlayRelative bool `json:"overlay-relative"`
Roots []ContentEntry `json:"roots"`
}
type ContentEntry struct {
Name string `json:"name"`
Type string `json:"type"`
Contents []ContentEntry `json:"contents,omitempty"`
ExternalContents string `json:"external-contents,omitempty"`
}
type LockFile struct {
Updated time.Time `json:"updated"`
Hash string `json:"hash"`
Files Locks `json:"files"`
}
type lockFileCreator struct {
lockfile *LockFile
hashFunc map[string]hash.Hash
}
type packedFile struct {
origin string
target string
}
func newLockFileCreator() *lockFileCreator {
return &lockFileCreator{
lockfile: &LockFile{
Updated: time.Now(),
Files: make(Locks, 0),
},
hashFunc: make(map[string]hash.Hash),
}
}
func (c *lockFileCreator) addFile(filename packedFile, file *os.File) error {
pathList := strings.Split(filename.target, string(filepath.Separator))
if len(pathList) == 0 {
return fmt.Errorf("target filename should not be empty: %s", filename.origin)
}
topLevelDir := pathList[0]
if _, ok := c.hashFunc[topLevelDir]; !ok {
c.hashFunc[topLevelDir] = sha256.New()
}
file.Seek(0, 0)
if _, err := io.Copy(c.hashFunc[topLevelDir], file); err != nil {
return err
}
return nil
}
func (c *lockFileCreator) generateLockFile() ([]byte, error) {
for entry, val := range c.hashFunc {
hashValue := fmt.Sprintf("%x", val.Sum(nil))
c.lockfile.Files = append(c.lockfile.Files, Lock{Filename: entry, Hash: hashValue})
}
h := sha256.New()
sort.Sort(c.lockfile.Files)
for _, entry := range c.lockfile.Files {
// Convert to binary data.
data, err := hex.DecodeString(entry.Hash)
if err != nil {
return nil, err
}
h.Write(data)
}
c.lockfile.Hash = fmt.Sprintf("%x", h.Sum(nil))
return json.MarshalIndent(c.lockfile, "", " ")
}
func (c *VFSOverlay) addFile(path string) error {
if filepath.IsAbs(path) {
return fmt.Errorf("vfs path %q should be relative", path)
}
pathList := strings.Split(path, string(filepath.Separator))
var currentEntry *ContentEntry
PathLoop:
for i, name := range pathList {
if i == 0 {
for j := range c.Roots {
entry := &c.Roots[j]
if strings.EqualFold(entry.Name, name) {
currentEntry = entry
continue PathLoop
}
}
} else {
for j := range currentEntry.Contents {
entry := &currentEntry.Contents[j]
if strings.EqualFold(entry.Name, name) {
currentEntry = entry
continue PathLoop
}
}
}
// name is not in VFSOverlay prefix tree
newEntry := ContentEntry{
Name: name,
}
if i == len(pathList)-1 {
newEntry.Type = "file"
// LLVM VFS library has undefined behaviors when using a non native
// path seperator in the VFS overlay file. Using forward slash here as
// the produced VFS overlay file will not be used on Windows.
newEntry.ExternalContents = filepath.ToSlash(path)
} else {
newEntry.Type = "directory"
}
if i == 0 {
c.Roots = append(c.Roots, newEntry)
currentEntry = &c.Roots[len(c.Roots)-1]
} else {
currentEntry.Contents = append(currentEntry.Contents, newEntry)
currentEntry = &currentEntry.Contents[len(currentEntry.Contents)-1]
}
}
return nil
}
func (c *VFSOverlay) generateVFSOverlay(w io.Writer) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
return encoder.Encode(c)
}
func getVSPath() (string, error) {
const (
// Path to vswhere.exe is fixed regardless of bitwise of the OS on x86 or x64.
// NOTE: With arm64 buildtools coming out, it might change in the future.
vswherePath = `C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe`
vsPathMarker = "installationPath: "
vsVersionMarker = "catalog_productLineVersion: "
)
cmd := exec.Command(vswherePath, "-prerelease")
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("vswhere.exe failed with %s, %v", stderrBuf.String(), err)
}
scanner := bufio.NewScanner(&stdoutBuf)
var installationPath string
var matchingVsPath string
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, vsPathMarker) {
installationPath = line[len(vsPathMarker):]
}
if strings.HasPrefix(line, vsVersionMarker) {
if line[len(vsVersionMarker):] == vsVersion {
matchingVsPath = installationPath
}
}
}
if len(matchingVsPath) > 0 {
return matchingVsPath, nil
}
// If MSVC and Windows SDK were installed through VS BuildTools installer,
// vswhere.exe will not return the paths of them. In this case, by default,
// the VSPath will points to
// "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools".
// Use the hard coded path for now until we have a better option.
BuildToolsDir := `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools`
if info, err := os.Stat(BuildToolsDir); err == nil && info.IsDir() {
return BuildToolsDir, nil
}
return "", fmt.Errorf("no matching VSPath was found")
}
func expandWildcards(root, subDir string) (string, error) {
normPath := filepath.Clean(filepath.Join(root, subDir))
matches, err := filepath.Glob(normPath)
if err != nil {
return "", err
}
if len(matches) != 1 {
return "", fmt.Errorf("%s had %d matches, should be only one", normPath, len(matches))
}
return matches[0], nil
}
func buildFileList(vsPath, vcToolsPath, vcrtVersion string) ([]packedFile, error) {
result := make([]packedFile, 0)
// Subset of VS corresponding to VC.
candidatePaths := []packedFile{
{`DIA SDK\bin`, ""},
{`DIA SDK\idl`, ""},
{`DIA SDK\include`, ""},
{`DIA SDK\lib`, ""},
// VC Tools and link time libraries, which are largest components
// in the SDK package.
{filepath.Join(vcToolsPath, "crt"), ""},
{filepath.Join(vcToolsPath, "bin"), ""},
{filepath.Join(vcToolsPath, "include"), ""},
{filepath.Join(vcToolsPath, "atlmfc"), ""},
// "onecore" lib will be removed later.
{filepath.Join(vcToolsPath, "lib"), ""},
// VC Runtime x64.
{`VC\redist`, ""},
{fmt.Sprintf(`VC\redist\MSVC\%s\x86\Microsoft.VC*.CRT`, vcrtVersion), "sys32"},
{fmt.Sprintf(`VC\redist\MSVC\%s\x86\Microsoft.VC*.CRT`, vcrtVersion), `Windows Kits\10\bin\x86`},
{fmt.Sprintf(`VC\redist\MSVC\%s\debug_nonredist\x86\Microsoft.VC*.DebugCRT`, vcrtVersion), "sys32"},
{fmt.Sprintf(`VC\redist\MSVC\%s\x64\Microsoft.VC*.CRT`, vcrtVersion), "sys64"},
{fmt.Sprintf(`VC\redist\MSVC\%s\x64\Microsoft.VC*.CRT`, vcrtVersion), `VC\bin\amd64_x86`},
{fmt.Sprintf(`VC\redist\MSVC\%s\x64\Microsoft.VC*.CRT`, vcrtVersion), `VC\bin\amd64`},
{fmt.Sprintf(`VC\redist\MSVC\%s\x64\Microsoft.VC*.CRT`, vcrtVersion), `Windows Kits\10\bin\x64`},
{fmt.Sprintf(`VC\redist\MSVC\%s\debug_nonredist\x64\Microsoft.VC*.DebugCRT`, vcrtVersion), "sys64"},
// VC Runtime ARM64.
{fmt.Sprintf(`VC\redist\MSVC\%s\arm64\Microsoft.VC*.CRT`, vcrtVersion), "sysarm64"},
{fmt.Sprintf(`VC\redist\MSVC\%s\arm64\Microsoft.VC*.CRT`, vcrtVersion), `VC\bin\amd64_arm64`},
{fmt.Sprintf(`VC\redist\MSVC\%s\arm64\Microsoft.VC*.CRT`, vcrtVersion), `VC\bin\arm64`},
{fmt.Sprintf(`VC\redist\MSVC\%s\arm64\Microsoft.VC*.CRT`, vcrtVersion), `Windows Kits\10\bin\arm64`},
{fmt.Sprintf(`VC\redist\MSVC\%s\debug_nonredist\arm64\Microsoft.VC*.DebugCRT`, vcrtVersion), "sysarm64"},
}
appendToResult := func(resultSlice []packedFile, items ...packedFile) []packedFile {
for _, item := range items {
// Skip .msi and .msm files because we don't need installers and samples.
if strings.HasSuffix(item.origin, ".msi") || strings.HasSuffix(item.origin, ".msm") {
continue
}
if filepath.Base(item.origin) == "vctip.exe" {
// vctip.exe doesn't shutdown, leaving locks on directories. It's
// optional so let's avoid this problem by not packaging it.
// See https://crbug.com/735226 for more details.
continue
}
// Special case for onecore vcrt. We don't use it for linking
// so we skip it to save space.
originList := strings.Split(item.origin, string(filepath.Separator))
onecoreDetected := false
for _, pathElement := range originList {
if strings.ToLower(pathElement) == "onecore" {
onecoreDetected = true
break
}
}
if onecoreDetected {
continue
}
resultSlice = append(resultSlice, item)
}
return resultSlice
}
for _, candidatePath := range candidatePaths {
combinedPath, err := expandWildcards(vsPath, candidatePath.origin)
if err != nil {
return result, err
}
if info, err := os.Stat(combinedPath); os.IsNotExist(err) || !info.IsDir() {
if os.IsNotExist(err) {
return result, fmt.Errorf("%s missing", combinedPath)
}
if !info.IsDir() {
return result, fmt.Errorf("%s is not a directory", combinedPath)
}
}
if err := filepath.Walk(combinedPath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// Skip the file if it is a directory.
if info.IsDir() {
return nil
}
if len(candidatePath.target) != 0 {
// Target directory in the SDK archive is explicitly defined.
// Calculate the relative path and save the info.
if !strings.HasPrefix(path, combinedPath) {
panic(fmt.Sprintf("%s should be in subdirectory of %s", path, combinedPath))
}
dest := path[len(combinedPath)+1:]
result = appendToResult(result, packedFile{path, filepath.Clean(filepath.Join(candidatePath.target, dest))})
} else {
// Target directory in the SDK archive is implicit. Use its relative path
// to Visual Studio installation path as its target path.
if !strings.HasPrefix(path, vsPath) {
panic(fmt.Sprintf("%s should be in subdirectory of %s", path, vsPath))
}
dest := path[len(vsPath)+1:]
result = appendToResult(result, packedFile{path, dest})
}
return nil
}); err != nil {
return result, err
}
}
// Read reg table to locate Windows SDK path.
regCommand := exec.Command("reg", "query", `HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots`, "/v", "KitsRoot10")
var stdoutBuf, stderrBuf bytes.Buffer
regCommand.Stdout = &stdoutBuf
regCommand.Stderr = &stderrBuf
if err := regCommand.Run(); err != nil {
return result, fmt.Errorf("command failed with msg: %s and %v", stderrBuf.String(), err)
}
scanner := bufio.NewScanner(&stdoutBuf)
const marker = " KitsRoot10 REG_SZ "
sdkPath := ""
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, marker) {
sdkPath = line[len(marker):]
}
}
if sdkPath == "" {
return result, fmt.Errorf("Windows SDK path cannot be found")
}
if strings.HasSuffix(sdkPath, string(filepath.Separator)) {
sdkPath = sdkPath[:len(sdkPath)-1]
}
sdkDirList := []string{
// Skip debugger(windbg) since we don't use it for building anything.
`References\`,
`Windows Performance Toolkit\`,
`Testing\`,
`App Certification Kit\`,
`Extension SDKs\`,
`Assessment and Deployment Kit\`,
}
if err := filepath.Walk(sdkPath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// Skip the file if it is a directory.
if info.IsDir() {
return nil
}
combinedPath := filepath.Clean(path)
// Skip files we don't need. These files may also be very long (and exceed _MAX_PATH).
tailPath := combinedPath[len(sdkPath)+1:]
for _, dir := range sdkDirList {
if strings.HasPrefix(tailPath, dir) {
return nil
}
}
// Skip Include and Library files that are not matching to the supplied SDK version.
if strings.HasPrefix(tailPath, `Include\`) || strings.HasPrefix(tailPath, `Lib\`) || strings.HasPrefix(tailPath, `Source\`) || strings.HasPrefix(tailPath, `bin\`) {
if !strings.Contains(tailPath, sdkVersion) {
return nil
}
}
destPath := filepath.Join("Windows Kits", WinKitVersion, tailPath)
result = appendToResult(result, packedFile{combinedPath, destPath})
return nil
}); err != nil {
return result, err
}
// Copy ucrt DLLs.
addUCRTFiles := func(sdkPath, arch string) ([]packedFile, error) {
ucrtDir := filepath.Join(sdkPath, "redist", sdkVersion, "ucrt", "dlls", arch)
if _, err := os.Stat(ucrtDir); os.IsNotExist(err) {
ucrtDir = filepath.Join(sdkPath, "redist", "ucrt", "dlls", arch)
}
ucrtPaths, err := filepath.Glob(filepath.Join(ucrtDir, "*"))
if err != nil {
return nil, err
}
if len(ucrtPaths) == 0 {
return nil, fmt.Errorf("%s is emptry, ucrt dlls could not be located", ucrtDir)
}
tmpResult := make([]packedFile, 0)
for _, ucrtPath := range ucrtPaths {
// Use a different implementation than the chromium packer.
dest := ucrtPath[len(sdkPath)+1:]
tmpResult = append(tmpResult, packedFile{ucrtPath, dest})
}
return tmpResult, nil
}
ucrtX64Files, err := addUCRTFiles(sdkPath, "x64")
if err != nil {
return result, err
}
result = appendToResult(result, ucrtX64Files...)
ucrtARMFiles, err := addUCRTFiles(sdkPath, "arm")
if err != nil {
return result, err
}
result = appendToResult(result, ucrtARMFiles...)
systemCRTFiles := []string{
"ucrtbased.dll",
}
archList := []string{
"x86",
"x64",
"arm64",
}
for _, systemCRTFile := range systemCRTFiles {
for _, arch := range archList {
srcPath := filepath.Join(sdkPath, "bin", sdkVersion, arch, "ucrt", systemCRTFile)
destPath := srcPath[len(sdkPath)+1:]
result = appendToResult(result, packedFile{srcPath, destPath})
}
}
return result, nil
}
func addEnvSetup(files *[]packedFile, vcToolsPath string) (string, error) {
tmpDir, err := os.MkdirTemp(os.TempDir(), "winsdk*")
if err != nil {
return "", err
}
vcToolsParts := strings.Split(vcToolsPath, string(filepath.Separator))
includeDirs := [][]string{
{"Windows Kits", WinKitVersion, "Include", sdkVersion, "um"},
{"Windows Kits", WinKitVersion, "Include", sdkVersion, "shared"},
{"Windows Kits", WinKitVersion, "Include", sdkVersion, "winrt"},
{"Windows Kits", WinKitVersion, "Include", sdkVersion, "ucrt"},
append(vcToolsParts, "include"),
append(vcToolsParts, "atlmfc", "include"),
}
libPathDirs := [][]string{
append(vcToolsParts, "lib", "x86", "store", "reference"),
{"Windows Kits", WinKitVersion, "UnionMetadata", sdkVersion},
}
// Common entries to all platforms.
// vcToolsInstallDir needs to end with a path separator.
vcToolsInstallDir := vcToolsParts
vcToolsInstallDir[len(vcToolsInstallDir)-1] += string(filepath.Separator)
env := map[string][][]string{
"VSINSTALLDIR": {{`.\`}},
"VCINSTALLDIR": {{`VC\`}},
"INCLUDE": includeDirs,
"LIBPATH": libPathDirs,
"VCToolsInstallDir": {vcToolsInstallDir},
}
envX86 := map[string][][]string{
"PATH": {
{"Windows Kits", WinKitVersion, "bin", sdkVersion, "x64"},
append(vcToolsParts, "bin", "HostX64", "x86"),
append(vcToolsParts, "bin", "HostX64", "x64"), // Needed for mspdb1x0.dll.
},
"LIB": {
append(vcToolsParts, "lib", "x86"),
append(vcToolsParts, "atlmfc", "lib", "x86"),
{"Windows Kits", WinKitVersion, "Lib", sdkVersion, "um", "x86"},
{"Windows Kits", WinKitVersion, "Lib", sdkVersion, "ucrt", "x86"},
},
}
envX64 := map[string][][]string{
"PATH": {
{"Windows Kits", WinKitVersion, "bin", sdkVersion, "x64"},
append(vcToolsParts, "bin", "HostX64", "x64"),
},
"LIB": {
append(vcToolsParts, "lib", "x64"),
append(vcToolsParts, "atlmfc", "lib", "x64"),
{"Windows Kits", WinKitVersion, "Lib", sdkVersion, "um", "x64"},
{"Windows Kits", WinKitVersion, "Lib", sdkVersion, "ucrt", "x64"},
},
}
envARM64 := map[string][][]string{
"PATH": {
{"Windows Kits", WinKitVersion, "bin", sdkVersion, "x64"},
append(vcToolsParts, "bin", "HostX64", "arm64"),
append(vcToolsParts, "bin", "HostX64", "x64"),
},
"LIB": {
append(vcToolsParts, "lib", "arm64"),
{"Windows Kits", WinKitVersion, "Lib", sdkVersion, "um", "arm64"},
{"Windows Kits", WinKitVersion, "Lib", sdkVersion, "ucrt", "arm64"},
},
}
genPath := func(dirs [][]string) string {
retStr := ""
for _, dirEntry := range dirs {
retStr += `%cd%` + string(filepath.Separator)
retStr += filepath.Join(dirEntry...)
retStr += ";"
}
if strings.HasSuffix(retStr, ";") {
return retStr[:len(retStr)-1]
}
return retStr
}
setEnvPrefix := filepath.Join(tmpDir, "SetEnv")
// Write cmd file.
setCmd, err := os.Create(setEnvPrefix + ".cmd")
if err != nil {
return "", err
}
defer setCmd.Close()
genEnv := func(dirMap map[string][][]string) string {
var buffer bytes.Buffer
keys := make([]string, 0)
for envvar := range dirMap {
keys = append(keys, envvar)
}
sort.Strings(keys)
for _, envvar := range keys {
dirs := dirMap[envvar]
if envvar == "PATH" {
fmt.Fprintf(&buffer, "set %s=%s;%%PATH%%;\n", envvar, genPath(dirs))
} else {
fmt.Fprintf(&buffer, "set %s=%s\n", envvar, genPath(dirs))
}
}
return buffer.String()
}
templateVars := struct {
SharedEnv string
X86Env string
X64Env string
ARM64Env string
}{
SharedEnv: genEnv(env),
X86Env: genEnv(envX86),
X64Env: genEnv(envX64),
ARM64Env: genEnv(envARM64),
}
templateText := `@echo off
:: Generated by winsdk, do not modify
pushd %~dp0..\..\..
{{.SharedEnv}}
if "%1"=="/x64" goto x64
if "%1"=="/arm64" goto arm64
{{.X86Env}}
goto :END
:x64
{{.X64Env}}
goto :END
:arm64
{{.ARM64Env}}
goto :END
:END
popd
`
tmpl, err := template.New("batch").Parse(templateText)
if err != nil {
return "", err
}
if err := tmpl.Execute(setCmd, templateVars); err != nil {
return "", err
}
// Write JSON files which will be used by Fuchsia windows_sdk recipe module
// located at https://fuchsia.googlesource.com/infra/recipes/+/refs/heads/main/recipe_modules/windows_sdk/api.py
mapMerge := func(envvarA, envvarB map[string][][]string) (map[string][][]string, error) {
retMap := make(map[string][][]string)
for entry, val := range envvarA {
retMap[entry] = val
}
for entry, val := range envvarB {
if _, ok := retMap[entry]; ok {
return nil, fmt.Errorf("env maps should not have intersection")
}
retMap[entry] = val
}
return retMap, nil
}
// TODO(https://fxbug.dev/42182062): Consider to remove this extra layer once builders
// migrate to this new SDK package.
jsonOutputWrapper := func(envvar map[string][][]string) map[string]map[string][][]string {
retMap := make(map[string]map[string][][]string)
retMap["env"] = envvar
return retMap
}
setX86JSON, err := os.Create(setEnvPrefix + ".x86.json")
if err != nil {
return "", err
}
defer setX86JSON.Close()
x86envJSON, err := mapMerge(env, envX86)
if err != nil {
return "", err
}
if err := json.NewEncoder(setX86JSON).Encode(jsonOutputWrapper(x86envJSON)); err != nil {
return "", err
}
setX64JSON, err := os.Create(setEnvPrefix + ".x64.json")
if err != nil {
return "", err
}
defer setX64JSON.Close()
x64envJSON, err := mapMerge(env, envX64)
if err != nil {
return "", err
}
if err := json.NewEncoder(setX64JSON).Encode(jsonOutputWrapper(x64envJSON)); err != nil {
return "", err
}
setARM64JSON, err := os.Create(setEnvPrefix + ".arm64.json")
if err != nil {
return "", err
}
defer setARM64JSON.Close()
arm64envJSON, err := mapMerge(env, envX64)
if err != nil {
return "", err
}
if err := json.NewEncoder(setARM64JSON).Encode(jsonOutputWrapper(arm64envJSON)); err != nil {
return "", err
}
*files = append(*files, packedFile{filepath.Join(tmpDir, "SetEnv.cmd"), filepath.Join("Windows Kits", WinKitVersion, "bin", "SetEnv.cmd")})
*files = append(*files, packedFile{filepath.Join(tmpDir, "SetEnv.x86.json"), filepath.Join("Windows Kits", WinKitVersion, "bin", "SetEnv.x86.json")})
*files = append(*files, packedFile{filepath.Join(tmpDir, "SetEnv.x64.json"), filepath.Join("Windows Kits", WinKitVersion, "bin", "SetEnv.x64.json")})
*files = append(*files, packedFile{filepath.Join(tmpDir, "SetEnv.arm64.json"), filepath.Join("Windows Kits", WinKitVersion, "bin", "SetEnv.arm64.json")})
vsVersionFile := filepath.Join(tmpDir, "VS_VERSION")
fd, err := os.Create(vsVersionFile)
if err != nil {
return tmpDir, err
}
defer fd.Close()
fmt.Fprint(fd, vsVersion)
*files = append(*files, packedFile{filepath.Join(tmpDir, "VS_VERSION"), filepath.Join("Windows Kits", WinKitVersion, "bin", "VS_VERSION")})
return tmpDir, nil
}
func walkSDKFiles(files []packedFile, filter string, fn func(entry packedFile) error) ([]byte, error) {
var totalSize, count int64
lockFileCreator := newLockFileCreator()
missingFile := false
for _, entry := range files {
simplified := entry.origin
if len(simplified) > 40 {
simplified = simplified[len(simplified)-40:]
}
fmt.Printf("\r %d/%d ...%s", count, len(files), simplified)
info, err := os.Stat(entry.origin)
if err != nil {
if os.IsNotExist(err) {
missingFile = true
fmt.Fprintf(os.Stderr, "\r%s does not exist.\n\n", entry.origin)
} else {
return nil, err
}
}
totalSize += info.Size()
count++
if err := fn(entry); err != nil {
return nil, err
}
if filter == "" || !strings.HasPrefix(entry.origin, filter) {
// Skip envvar batch file and JSON files from lockfile
// hashes calculation
inputFile, err := os.Open(entry.origin)
if err != nil {
return nil, err
}
defer inputFile.Close()
lockFileCreator.addFile(entry, inputFile)
}
}
fmt.Println()
fmt.Printf("\n %1.3f GB of data in %d files", float64(totalSize)/1e9, len(files))
if missingFile {
return nil, fmt.Errorf("missing files in SDK package")
}
return lockFileCreator.generateLockFile()
}
func generageSDKDir(files []packedFile, envDir string) error {
// Write SDK package directory in place instead of moving
// it from temporary directory as os.Rename behaves differently
// for directories.
if _, err := os.Stat(outputPath); !os.IsNotExist(err) {
os.RemoveAll(outputPath)
}
if err := os.MkdirAll(outputPath, 0755); err != nil {
return err
}
vfsoverlay := &VFSOverlay{
Version: 0,
CaseSensitive: false,
RootRelative: "overlay-dir",
OverlayRelative: true,
Roots: make([]ContentEntry, 0),
}
lockFileData, err := walkSDKFiles(files, envDir, func(entry packedFile) error {
if needOverlay(entry.target) {
vfsoverlay.addFile(entry.target)
}
if err := os.MkdirAll(filepath.Dir(filepath.Join(outputPath, entry.target)), 0755); err != nil {
return err
}
targetFile, err := os.Create(filepath.Join(outputPath, entry.target))
if err != nil {
return err
}
inputFile, err := os.Open(entry.origin)
if err != nil {
return err
}
defer inputFile.Close()
if _, err := io.Copy(targetFile, inputFile); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
lockFile, err := os.Create(filepath.Join(outputPath, "content.lock"))
if err != nil {
return err
}
defer lockFile.Close()
lockFile.Write(lockFileData)
overlayFile, err := os.Create(filepath.Join(outputPath, "llvm-vfsoverlay.yaml"))
if err != nil {
return err
}
defer overlayFile.Close()
if err = vfsoverlay.generateVFSOverlay(overlayFile); err != nil {
return err
}
return nil
}
func needOverlay(path string) bool {
ext := strings.ToLower(filepath.Ext(path))
if ext == ".h" || ext == ".lib" {
return true
}
return false
}
func generateSDKArchive(files []packedFile, envDir string) (string, error) {
vfsoverlay := &VFSOverlay{
Version: 0,
CaseSensitive: false,
RootRelative: "overlay-dir",
OverlayRelative: true,
Roots: make([]ContentEntry, 0),
}
outputFile, err := os.CreateTemp("", "sdkpack.zip")
if err != nil {
return "", err
}
defer outputFile.Close()
zipWriter := zip.NewWriter(outputFile)
defer zipWriter.Close()
lockFileData, err := walkSDKFiles(files, envDir, func(entry packedFile) error {
if needOverlay(entry.target) {
vfsoverlay.addFile(entry.target)
}
compressedFile, err := zipWriter.Create(entry.target)
if err != nil {
return err
}
inputFile, err := os.Open(entry.origin)
if err != nil {
return err
}
defer inputFile.Close()
if _, err = io.Copy(compressedFile, inputFile); err != nil {
return err
}
return nil
})
if err != nil {
return "", err
}
lockFile, err := zipWriter.Create("content.lock")
if err != nil {
return "", err
}
lockFile.Write(lockFileData)
overlayFile, err := zipWriter.Create("llvm-vfsoverlay.yaml")
if err != nil {
return "", err
}
if err = vfsoverlay.generateVFSOverlay(overlayFile); err != nil {
return "", err
}
return "", nil
}
func generateSDK() error {
vsPath, err := getVSPath()
if err != nil {
return err
}
tmpVSToolsPath, err := expandWildcards(vsPath, filepath.Join("VC", "Tools", "MSVC", VcrtVersionWildcard))
if err != nil {
return err
}
vcToolsPath := tmpVSToolsPath[len(vsPath)+1:]
fmt.Printf("Building file list for VS %s and Windows SDK %s\n", vsVersion, sdkVersion)
files, err := buildFileList(vsPath, vcToolsPath, VcrtVersionWildcard)
if err != nil {
return err
}
if dryRun {
lockFileData, err := walkSDKFiles(files, "", func(entry packedFile) error {
return nil
})
if err != nil {
return err
}
fmt.Printf("%s\n", string(lockFileData))
return nil
}
tmpDir, err := addEnvSetup(&files, vcToolsPath)
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
if archivePath != "" {
tempOutput, err := generateSDKArchive(files, tmpDir)
if err != nil {
return err
}
return os.Rename(tempOutput, archivePath)
}
if outputPath != "" {
return generageSDKDir(files, tmpDir)
}
return nil
}
func main() {
if runtime.GOOS != "windows" {
fmt.Println("this program only works under Windows")
os.Exit(0)
}
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "usage: %s [options]\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if help {
flag.Usage()
os.Exit(0)
}
if outputPath == "" && archivePath == "" {
outputPath = "winsdk"
} else if outputPath != "" && archivePath != "" {
fmt.Fprintf(os.Stderr, "output and archive flags cannot be used together\n")
flag.Usage()
os.Exit(1)
}
if err := generateSDK(); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
}
}