2
0

feat(capabilities): add visionOS SDK, PowerShell versions, working directory disk space
Some checks failed
CI / build-and-test (push) Has been cancelled
Release / build (amd64, darwin) (push) Has been cancelled
Release / build (amd64, linux) (push) Has been cancelled
Release / build (amd64, windows) (push) Has been cancelled
Release / build (arm64, darwin) (push) Has been cancelled
Release / build (arm64, linux) (push) Has been cancelled
Release / release (push) Has been cancelled

- Add visionOS/xrOS SDK detection for Vision Pro development
- Add PowerShell version detection (pwsh and powershell) with actual versions
- Detect disk space on working directory filesystem (not just root)
  - Useful for runners using external/USB drives for builds
- Add watchOS and tvOS suggested labels
- Refactor disk detection to accept path parameter

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
GitCaddy
2026-01-11 20:32:23 +00:00
parent 66d0b1e608
commit b303a83a77
5 changed files with 127 additions and 33 deletions

View File

@@ -163,7 +163,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
bandwidthManager.Start(ctx) bandwidthManager.Start(ctx)
log.Infof("bandwidth manager started, testing against: %s", reg.Address) log.Infof("bandwidth manager started, testing against: %s", reg.Address)
capabilities := envcheck.DetectCapabilities(ctx, dockerHost) capabilities := envcheck.DetectCapabilities(ctx, dockerHost, cfg.Container.WorkdirParent)
// Include initial bandwidth result if available // Include initial bandwidth result if available
capabilities.Bandwidth = bandwidthManager.GetLastResult() capabilities.Bandwidth = bandwidthManager.GetLastResult()
capabilitiesJson := capabilities.ToJSON() capabilitiesJson := capabilities.ToJSON()
@@ -186,7 +186,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
} }
// Start periodic capabilities update goroutine // Start periodic capabilities update goroutine
go periodicCapabilitiesUpdate(ctx, runner, ls.Names(), dockerHost) go periodicCapabilitiesUpdate(ctx, runner, ls.Names(), dockerHost, cfg.Container.WorkdirParent)
poller := poll.New(cfg, cli, runner) poller := poll.New(cfg, cli, runner)
poller.SetBandwidthManager(bandwidthManager) poller.SetBandwidthManager(bandwidthManager)
@@ -240,7 +240,7 @@ func checkDiskSpaceWarnings(capabilities *envcheck.RunnerCapabilities) {
} }
// periodicCapabilitiesUpdate periodically updates capabilities including disk space and bandwidth // periodicCapabilitiesUpdate periodically updates capabilities including disk space and bandwidth
func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNames []string, dockerHost string) { func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNames []string, dockerHost string, workingDir string) {
ticker := time.NewTicker(CapabilitiesUpdateInterval) ticker := time.NewTicker(CapabilitiesUpdateInterval)
defer ticker.Stop() defer ticker.Stop()
@@ -254,7 +254,7 @@ func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNa
return return
case <-ticker.C: case <-ticker.C:
// Detect updated capabilities (disk space changes over time) // Detect updated capabilities (disk space changes over time)
capabilities := envcheck.DetectCapabilities(ctx, dockerHost) capabilities := envcheck.DetectCapabilities(ctx, dockerHost, workingDir)
// Include latest bandwidth result // Include latest bandwidth result
if bandwidthManager != nil { if bandwidthManager != nil {

View File

@@ -165,7 +165,7 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
defer cancel() defer cancel()
// Detect capabilities including current disk space // Detect capabilities including current disk space
caps := envcheck.DetectCapabilities(ctx, p.cfg.Container.DockerHost) caps := envcheck.DetectCapabilities(ctx, p.cfg.Container.DockerHost, p.cfg.Container.WorkdirParent)
// Include latest bandwidth result if available // Include latest bandwidth result if available
if p.bandwidthManager != nil { if p.bandwidthManager != nil {

View File

@@ -19,6 +19,7 @@ import (
// DiskInfo holds disk space information // DiskInfo holds disk space information
type DiskInfo struct { type DiskInfo struct {
Path string `json:"path,omitempty"` // Path being checked (working directory)
Total uint64 `json:"total_bytes"` Total uint64 `json:"total_bytes"`
Free uint64 `json:"free_bytes"` Free uint64 `json:"free_bytes"`
Used uint64 `json:"used_bytes"` Used uint64 `json:"used_bytes"`
@@ -69,7 +70,8 @@ type CapabilityFeatures struct {
} }
// DetectCapabilities detects the runner's capabilities // DetectCapabilities detects the runner's capabilities
func DetectCapabilities(ctx context.Context, dockerHost string) *RunnerCapabilities { // workingDir is the directory where builds will run (for disk space detection)
func DetectCapabilities(ctx context.Context, dockerHost string, workingDir string) *RunnerCapabilities {
cap := &RunnerCapabilities{ cap := &RunnerCapabilities{
OS: runtime.GOOS, OS: runtime.GOOS,
Arch: runtime.GOARCH, Arch: runtime.GOARCH,
@@ -115,8 +117,8 @@ func DetectCapabilities(ctx context.Context, dockerHost string) *RunnerCapabilit
// Detect package managers // Detect package managers
detectPackageManagers(ctx, cap) detectPackageManagers(ctx, cap)
// Detect disk space // Detect disk space on the working directory's filesystem
cap.Disk = detectDiskSpace() cap.Disk = detectDiskSpace(workingDir)
// Generate suggested labels based on detected capabilities // Generate suggested labels based on detected capabilities
cap.SuggestedLabels = generateSuggestedLabels(cap) cap.SuggestedLabels = generateSuggestedLabels(cap)
@@ -158,7 +160,8 @@ func detectXcode(ctx context.Context) *XcodeInfo {
continue // Skip header lines continue // Skip header lines
} }
if strings.Contains(line, "iOS") || strings.Contains(line, "macOS") || if strings.Contains(line, "iOS") || strings.Contains(line, "macOS") ||
strings.Contains(line, "watchOS") || strings.Contains(line, "tvOS") { strings.Contains(line, "watchOS") || strings.Contains(line, "tvOS") ||
strings.Contains(line, "visionOS") || strings.Contains(line, "xrOS") {
// Extract SDK name // Extract SDK name
if idx := strings.Index(line, "-sdk"); idx != -1 { if idx := strings.Index(line, "-sdk"); idx != -1 {
sdkPart := strings.TrimSpace(line[:idx]) sdkPart := strings.TrimSpace(line[:idx])
@@ -267,11 +270,20 @@ func generateSuggestedLabels(cap *RunnerCapabilities) []string {
// Xcode/iOS labels (macOS only) // Xcode/iOS labels (macOS only)
if cap.Xcode != nil { if cap.Xcode != nil {
addLabel("xcode") addLabel("xcode")
// Check for iOS SDK // Check for SDKs
for _, sdk := range cap.Xcode.SDKs { for _, sdk := range cap.Xcode.SDKs {
if strings.Contains(strings.ToLower(sdk), "ios") { sdkLower := strings.ToLower(sdk)
if strings.Contains(sdkLower, "ios") {
addLabel("ios") addLabel("ios")
break }
if strings.Contains(sdkLower, "visionos") || strings.Contains(sdkLower, "xros") {
addLabel("visionos")
}
if strings.Contains(sdkLower, "watchos") {
addLabel("watchos")
}
if strings.Contains(sdkLower, "tvos") {
addLabel("tvos")
} }
} }
// If simulators available, add simulator label // If simulators available, add simulator label
@@ -395,18 +407,19 @@ func detectDockerCompose(ctx context.Context) bool {
func detectTools(ctx context.Context, cap *RunnerCapabilities) { func detectTools(ctx context.Context, cap *RunnerCapabilities) {
toolDetectors := map[string]func(context.Context) []string{ toolDetectors := map[string]func(context.Context) []string{
"node": detectNodeVersions, "node": detectNodeVersions,
"go": detectGoVersions, "go": detectGoVersions,
"python": detectPythonVersions, "python": detectPythonVersions,
"java": detectJavaVersions, "java": detectJavaVersions,
"dotnet": detectDotnetVersions, "dotnet": detectDotnetVersions,
"rust": detectRustVersions, "rust": detectRustVersions,
"ruby": detectRubyVersions, "ruby": detectRubyVersions,
"php": detectPHPVersions, "php": detectPHPVersions,
"swift": detectSwiftVersions, "swift": detectSwiftVersions,
"kotlin": detectKotlinVersions, "kotlin": detectKotlinVersions,
"flutter": detectFlutterVersions, "flutter": detectFlutterVersions,
"dart": detectDartVersions, "dart": detectDartVersions,
"powershell": detectPowerShellVersions,
} }
for tool, detector := range toolDetectors { for tool, detector := range toolDetectors {
@@ -763,6 +776,50 @@ func detectDartVersions(ctx context.Context) []string {
return detectToolVersion(ctx, "dart", "--version", "Dart SDK version: ") return detectToolVersion(ctx, "dart", "--version", "Dart SDK version: ")
} }
func detectPowerShellVersions(ctx context.Context) []string {
versions := []string{}
// Check for pwsh (PowerShell Core / PowerShell 7+)
if v := detectPwshVersion(ctx, "pwsh"); v != "" {
versions = append(versions, "pwsh:"+v)
}
// Check for powershell (Windows PowerShell 5.x)
if runtime.GOOS == "windows" {
if v := detectPwshVersion(ctx, "powershell"); v != "" {
versions = append(versions, "powershell:"+v)
}
}
return versions
}
func detectPwshVersion(ctx context.Context, cmd string) string {
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// Use -Command to get version
var c *exec.Cmd
if cmd == "pwsh" {
c = exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
} else {
c = exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
}
output, err := c.Output()
if err != nil {
return ""
}
version := strings.TrimSpace(string(output))
// Return major.minor
parts := strings.Split(version, ".")
if len(parts) >= 2 {
return parts[0] + "." + parts[1]
}
return version
}
func detectSimpleToolVersion(ctx context.Context, cmd string) string { func detectSimpleToolVersion(ctx context.Context, cmd string) string {
if _, err := exec.LookPath(cmd); err != nil { if _, err := exec.LookPath(cmd); err != nil {
return "" return ""

View File

@@ -9,13 +9,23 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// detectDiskSpace detects disk space on the root filesystem (Unix version) // detectDiskSpace detects disk space on the specified path's filesystem (Unix version)
func detectDiskSpace() *DiskInfo { // If path is empty, defaults to "/"
func detectDiskSpace(path string) *DiskInfo {
if path == "" {
path = "/"
}
var stat unix.Statfs_t var stat unix.Statfs_t
err := unix.Statfs("/", &stat) err := unix.Statfs(path, &stat)
if err != nil { if err != nil {
return nil // Fallback to root if the path doesn't exist
err = unix.Statfs("/", &stat)
if err != nil {
return nil
}
path = "/"
} }
total := stat.Blocks * uint64(stat.Bsize) total := stat.Blocks * uint64(stat.Bsize)
@@ -24,6 +34,7 @@ func detectDiskSpace() *DiskInfo {
usedPercent := float64(used) / float64(total) * 100 usedPercent := float64(used) / float64(total) * 100
return &DiskInfo{ return &DiskInfo{
Path: path,
Total: total, Total: total,
Free: free, Free: free,
Used: used, Used: used,

View File

@@ -6,23 +6,49 @@
package envcheck package envcheck
import ( import (
"path/filepath"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
// detectDiskSpace detects disk space on the C: drive (Windows version) // detectDiskSpace detects disk space on the specified path's drive (Windows version)
func detectDiskSpace() *DiskInfo { // If path is empty, defaults to "C:\"
func detectDiskSpace(path string) *DiskInfo {
if path == "" {
path = "C:\\"
}
// Resolve to absolute path
absPath, err := filepath.Abs(path)
if err != nil {
absPath = "C:\\"
}
// Extract drive letter (e.g., "D:\" from "D:\builds\runner")
drivePath := filepath.VolumeName(absPath) + "\\"
if drivePath == "\\" {
drivePath = "C:\\"
}
var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64 var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64
path := windows.StringToUTF16Ptr("C:\\") pathPtr := windows.StringToUTF16Ptr(drivePath)
err := windows.GetDiskFreeSpaceEx(path, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes) err = windows.GetDiskFreeSpaceEx(pathPtr, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
if err != nil { if err != nil {
return nil // Fallback to C: drive
pathPtr = windows.StringToUTF16Ptr("C:\\")
err = windows.GetDiskFreeSpaceEx(pathPtr, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
if err != nil {
return nil
}
drivePath = "C:\\"
} }
used := totalNumberOfBytes - totalNumberOfFreeBytes used := totalNumberOfBytes - totalNumberOfFreeBytes
usedPercent := float64(used) / float64(totalNumberOfBytes) * 100 usedPercent := float64(used) / float64(totalNumberOfBytes) * 100
return &DiskInfo{ return &DiskInfo{
Path: drivePath,
Total: totalNumberOfBytes, Total: totalNumberOfBytes,
Free: totalNumberOfFreeBytes, Free: totalNumberOfFreeBytes,
Used: used, Used: used,