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
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:
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user