Files
bootstrap_windows_env/internal/bootstrap/winget.go
T
2026-05-27 22:12:14 -05:00

147 lines
3.7 KiB
Go

package bootstrap
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
func WingetInstallArgs(pkg Package) []string {
args := []string{
"install", "--id", pkg.WingetID, "--exact", "--source", pkg.WingetSource,
"--accept-source-agreements", "--accept-package-agreements", "--disable-interactivity",
}
if pkg.WingetOverride != "" {
args = append(args, "--override", pkg.WingetOverride)
}
return args
}
func WingetListArgs(pkg Package) []string {
return []string{
"list", "--id", pkg.WingetID, "--exact", "--source", pkg.WingetSource,
"--accept-source-agreements", "--disable-interactivity",
}
}
func WingetOutputShowsInstalled(pkg Package, output string) bool {
lower := strings.ToLower(output)
if strings.Contains(lower, "no installed package found") || strings.Contains(lower, "no package found") {
return false
}
return strings.Contains(lower, strings.ToLower(pkg.WingetID))
}
type PackageState struct {
Package Package
Installed bool
CheckErr error
}
func CheckPackages(ctx context.Context, runner Runner, packages []Package, workers int) []PackageState {
if workers < 1 {
workers = 1
}
states := make([]PackageState, len(packages))
type job struct {
index int
pkg Package
}
jobs := make(chan job)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for current := range jobs {
result := runner.Run(ctx, "winget", WingetListArgs(current.pkg)...)
state := PackageState{Package: current.pkg}
if result.Err != nil && !strings.Contains(strings.ToLower(result.CombinedOutput()), "no installed package found") {
state.CheckErr = fmt.Errorf("%s: %w", result.CombinedOutput(), result.Err)
} else {
state.Installed = WingetOutputShowsInstalled(current.pkg, result.CombinedOutput())
}
states[current.index] = state
}
}()
}
for i, pkg := range packages {
jobs <- job{index: i, pkg: pkg}
}
close(jobs)
wg.Wait()
return states
}
func PendingPackages(states []PackageState) []Package {
var pending []Package
for _, state := range states {
if !state.Installed {
pending = append(pending, state.Package)
}
}
return pending
}
func UnknownPackageStates(packages []Package) []PackageState {
states := make([]PackageState, 0, len(packages))
for _, pkg := range packages {
states = append(states, PackageState{Package: pkg})
}
return states
}
func InstallPackages(ctx context.Context, runner Runner, packages []Package) Issues {
var issues Issues
for _, pkg := range packages {
prepared, err := PreparePackageForInstall(pkg)
if err != nil {
issues = append(issues, Issue{Step: "prepare " + pkg.Name, Err: err})
continue
}
result := runner.Run(ctx, "winget", WingetInstallArgs(prepared)...)
if result.Err != nil {
issues = append(issues, Issue{
Step: "install " + pkg.Name,
Err: fmt.Errorf("%w: %s", result.Err, result.CombinedOutput()),
})
}
}
return issues
}
func PreparePackageForInstall(pkg Package) (Package, error) {
if pkg.WingetConfigAsset == "" {
return pkg, nil
}
path, err := writeInstallAsset(pkg.WingetConfigAsset)
if err != nil {
return pkg, err
}
pkg.WingetOverride = strings.ReplaceAll(pkg.WingetOverride, "{config}", `"`+path+`"`)
return pkg, nil
}
func writeInstallAsset(asset string) (string, error) {
data, err := managedAssets.ReadFile(asset)
if err != nil {
return "", err
}
root, err := os.UserCacheDir()
if err != nil || root == "" {
root = os.TempDir()
}
dir := filepath.Join(root, "bootstrap_windows_env")
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", err
}
target := filepath.Join(dir, filepath.Base(asset))
if err := os.WriteFile(target, data, 0o644); err != nil {
return "", err
}
return target, nil
}