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)
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
capabilities.Bandwidth = bandwidthManager.GetLastResult()
capabilitiesJson := capabilities.ToJSON()
@@ -186,7 +186,7 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
}
// 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.SetBandwidthManager(bandwidthManager)
@@ -240,7 +240,7 @@ func checkDiskSpaceWarnings(capabilities *envcheck.RunnerCapabilities) {
}
// 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)
defer ticker.Stop()
@@ -254,7 +254,7 @@ func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNa
return
case <-ticker.C:
// Detect updated capabilities (disk space changes over time)
capabilities := envcheck.DetectCapabilities(ctx, dockerHost)
capabilities := envcheck.DetectCapabilities(ctx, dockerHost, workingDir)
// Include latest bandwidth result
if bandwidthManager != nil {

View File

@@ -165,7 +165,7 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
defer cancel()
// 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
if p.bandwidthManager != nil {

View File

@@ -19,6 +19,7 @@ import (
// DiskInfo holds disk space information
type DiskInfo struct {
Path string `json:"path,omitempty"` // Path being checked (working directory)
Total uint64 `json:"total_bytes"`
Free uint64 `json:"free_bytes"`
Used uint64 `json:"used_bytes"`
@@ -69,7 +70,8 @@ type CapabilityFeatures struct {
}
// 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{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
@@ -115,8 +117,8 @@ func DetectCapabilities(ctx context.Context, dockerHost string) *RunnerCapabilit
// Detect package managers
detectPackageManagers(ctx, cap)
// Detect disk space
cap.Disk = detectDiskSpace()
// Detect disk space on the working directory's filesystem
cap.Disk = detectDiskSpace(workingDir)
// Generate suggested labels based on detected capabilities
cap.SuggestedLabels = generateSuggestedLabels(cap)
@@ -158,7 +160,8 @@ func detectXcode(ctx context.Context) *XcodeInfo {
continue // Skip header lines
}
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
if idx := strings.Index(line, "-sdk"); idx != -1 {
sdkPart := strings.TrimSpace(line[:idx])
@@ -267,11 +270,20 @@ func generateSuggestedLabels(cap *RunnerCapabilities) []string {
// Xcode/iOS labels (macOS only)
if cap.Xcode != nil {
addLabel("xcode")
// Check for iOS SDK
// Check for 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")
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
@@ -395,18 +407,19 @@ func detectDockerCompose(ctx context.Context) bool {
func detectTools(ctx context.Context, cap *RunnerCapabilities) {
toolDetectors := map[string]func(context.Context) []string{
"node": detectNodeVersions,
"go": detectGoVersions,
"python": detectPythonVersions,
"java": detectJavaVersions,
"dotnet": detectDotnetVersions,
"rust": detectRustVersions,
"ruby": detectRubyVersions,
"php": detectPHPVersions,
"swift": detectSwiftVersions,
"kotlin": detectKotlinVersions,
"flutter": detectFlutterVersions,
"dart": detectDartVersions,
"node": detectNodeVersions,
"go": detectGoVersions,
"python": detectPythonVersions,
"java": detectJavaVersions,
"dotnet": detectDotnetVersions,
"rust": detectRustVersions,
"ruby": detectRubyVersions,
"php": detectPHPVersions,
"swift": detectSwiftVersions,
"kotlin": detectKotlinVersions,
"flutter": detectFlutterVersions,
"dart": detectDartVersions,
"powershell": detectPowerShellVersions,
}
for tool, detector := range toolDetectors {
@@ -763,6 +776,50 @@ func detectDartVersions(ctx context.Context) []string {
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 {
if _, err := exec.LookPath(cmd); err != nil {
return ""

View File

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

View File

@@ -6,23 +6,49 @@
package envcheck
import (
"path/filepath"
"golang.org/x/sys/windows"
)
// detectDiskSpace detects disk space on the C: drive (Windows version)
func detectDiskSpace() *DiskInfo {
// detectDiskSpace detects disk space on the specified path's drive (Windows version)
// 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
path := windows.StringToUTF16Ptr("C:\\")
err := windows.GetDiskFreeSpaceEx(path, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
pathPtr := windows.StringToUTF16Ptr(drivePath)
err = windows.GetDiskFreeSpaceEx(pathPtr, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
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
usedPercent := float64(used) / float64(totalNumberOfBytes) * 100
return &DiskInfo{
Path: drivePath,
Total: totalNumberOfBytes,
Free: totalNumberOfFreeBytes,
Used: used,