package spitfire import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "sync" ) var ( reportedCommands = make(map[string]bool) reportMutex sync.Mutex ) // CheckDependencies ensures that all required tools and dependencies for the build are installed. func CheckDependencies() error { // Just to make sure, reset reported commands at start reportMutex.Lock() reportedCommands = make(map[string]bool) reportMutex.Unlock() // Common dependencies dependencies := map[string]string{ "git": "https://git-scm.com/downloads", // Git "python": "https://www.python.org/downloads/", // Python "pip3": "https://pip.pypa.io/en/stable/installing/", // Pip3 "hg": "https://www.mercurial-scm.org/", // Mercurial (hg) "rustc": "https://www.rust-lang.org/tools/install", // Rust compiler "cargo": "https://www.rust-lang.org/tools/install", // Cargo package manager } // Add platform-specific dependencies if runtime.GOOS == "windows" { mozBuildPath := os.Getenv("MOZILLABUILD") if mozBuildPath == "" { mozBuildPath = "C:\\mozilla-build" // Default path for MozillaBuild on Windows } if !dirExists(mozBuildPath) { dependencies["mozbuild"] = "https://ftp.mozilla.org/pub/mozilla/libraries/win32/MozillaBuildSetup-Latest.exe" } } else { dependencies["rsync"] = "https://rsync.samba.org/download.html" // rsync for non-Windows platforms } // Check for missing tools missingTools := []string{} for tool, downloadLink := range dependencies { if !isCommandAvailable(tool) { missingTools = append(missingTools, fmt.Sprintf("%s (Download: %s)", tool, downloadLink)) } } // Check Rust version if installed if isCommandAvailable("rustc") { _, err := getCommandOutput("rustc", "--version") if err != nil { missingTools = append(missingTools, "rustc (version check failed)") } } // Check for `mach` script in the source directory machPath := filepath.Join("mozilla-release", "mach") repoExists, err := CheckRepoExists("mozilla-release") if err != nil { // Repository exists but is corrupted fmt.Println("⚠️ Existing repository appears corrupted") } if !fileExists(machPath) { if len(missingTools) == 0 { // Only mach is missing and other dependencies are satisfied - try to clone fmt.Println("⚠️ mach script missing - attempting to clone repository...") if err := CloneRepo("mozilla-release", "https://hg.mozilla.org/releases/mozilla-release/"); err != nil { missingTools = append(missingTools, "mach (failed to automatically clone repository)") } else { // Verify mach exists after clone if !fileExists(machPath) { missingTools = append(missingTools, "mach (repository cloned but mach still missing)") } else { fmt.Println("✅ Successfully cloned repository and found mach script") } } } else { // Other tools are also missing - just report mach as missing if !repoExists { missingTools = append(missingTools, "mach (repository not found)") } else { missingTools = append(missingTools, "mach (found repository but mach is missing)") } } } // Report missing tools if any if len(missingTools) > 0 { fmt.Println("The following tools are missing and are required for the build:") for _, tool := range missingTools { fmt.Println(" - " + tool) } return fmt.Errorf("missing required tools") } fmt.Println("All required system dependencies are installed.") return nil } // CheckRepoExists verifies if the Mozilla repository exists and is valid func CheckRepoExists(sourcePath string) (bool, error) { hgDir := filepath.Join(sourcePath, ".hg") if _, err := os.Stat(hgDir); os.IsNotExist(err) { return false, nil } return true, nil } // isCommandAvailable checks if a command/tool is available on the system. func isCommandAvailable(command string) bool { path, err := exec.LookPath(command) if err != nil { return false } reportMutex.Lock() defer reportMutex.Unlock() if !reportedCommands[command] { fmt.Printf("Command '%s' found at path '%s'\n", command, path) reportedCommands[command] = true } return true } // Helper function to get command output func getCommandOutput(name string, arg ...string) (string, error) { cmd := exec.Command(name, arg...) output, err := cmd.CombinedOutput() if err != nil { return "", err } return string(output), nil } // fileExists checks if a file exists at the given path. func fileExists(path string) bool { _, err := os.Stat(path) return err == nil } // dirExists checks if a directory exists at the given path. func dirExists(path string) bool { info, err := os.Stat(path) if os.IsNotExist(err) { return false } return info.IsDir() } // isMsys2 detects if the environment is MSYS2 by checking the MSYSTEM environment variable. func isMsys2() bool { return strings.Contains(strings.ToLower(os.Getenv("MSYSTEM")), "msys") }