Builder/spitfire/build.go

391 lines
12 KiB
Go
Raw Normal View History

2024-09-09 01:25:07 +02:00
package spitfire
import (
"fmt"
"os"
"os/exec"
"path/filepath"
2024-09-10 23:21:46 +02:00
"runtime"
"sync"
"time"
2024-09-09 01:25:07 +02:00
)
// Array to store errors
var errors []string
var (
repoOperationsDone bool
repoMutex sync.Mutex
)
2024-09-10 23:30:34 +02:00
// SetGlobalEnv sets the MOZILLABUILD environment variable globally for the user (or system)
func SetGlobalEnv(variable, value string, scope string) error {
fmt.Printf("⚙️ Setting environment variable: %s=%s (scope: %s)\n", variable, value, scope)
2024-09-10 23:30:34 +02:00
if runtime.GOOS == "windows" {
var cmd *exec.Cmd
if scope == "user" {
cmd = exec.Command("setx", variable, value) // Set for current user
} else if scope == "system" {
cmd = exec.Command("setx", variable, value, "/M") // Set for system (requires admin privileges)
} else {
return fmt.Errorf("❌ unknown scope: %s", scope)
2024-09-10 23:30:34 +02:00
}
2024-09-10 23:21:46 +02:00
2024-09-10 23:30:34 +02:00
// Run the command
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("❌ failed to set environment variable %s=%s: %v\nOutput: %s", variable, value, err, string(out))
2024-09-10 23:21:46 +02:00
}
fmt.Printf("✅ Successfully set %s=%s\n", variable, value)
2024-09-10 23:30:34 +02:00
return nil
} else {
return fmt.Errorf("❌ global environment variable setting is not supported on non-Windows systems")
2024-09-10 23:21:46 +02:00
}
}
2024-09-09 01:25:07 +02:00
// Run an external command like scp or rsync
func runCommand(command string, args ...string) error {
2024-09-13 12:47:30 +02:00
fmt.Printf("Running command: %s %v\n", command, args)
2024-09-09 01:25:07 +02:00
cmd := exec.Command(command, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func runCommandInDir(dir string, name string, arg ...string) error {
cmd := exec.Command(name, arg...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
2024-09-09 01:25:07 +02:00
// Function to resolve paths using absolute path
func ResolvePath(path string) (string, error) {
fmt.Printf("📍 Resolving path: %s\n", path)
2024-09-10 23:09:24 +02:00
// Convert Unix-style slashes to the platform's native slashes
path = filepath.FromSlash(path)
// Get the absolute path
2024-09-09 01:25:07 +02:00
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("❌ failed to resolve path: %s, error: %v", path, err)
2024-09-09 01:25:07 +02:00
}
fmt.Printf(" └─ Resolved to: %s\n", absPath)
2024-09-09 01:25:07 +02:00
return absPath, nil
}
// Function to download Mozilla source if not present
func DownloadSource(sourcePath, sourceRepo string) error {
fmt.Println("🔍 Checking Mozilla source repository...")
2024-09-09 01:25:07 +02:00
if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
fmt.Println("📥 Mozilla source not found - cloning repository...")
2024-09-09 01:25:07 +02:00
if err := runCommand("hg", "clone", sourceRepo, sourcePath); err != nil {
return fmt.Errorf("❌ failed to clone Mozilla repository: %w", err)
2024-09-09 01:25:07 +02:00
}
fmt.Println("✅ Repository cloned successfully")
2024-09-09 01:25:07 +02:00
} else {
fmt.Println(" Mozilla source already exists")
2024-09-09 01:25:07 +02:00
}
return nil
2024-09-09 01:25:07 +02:00
}
// Function to discard uncommitted changes
func DiscardChanges(sourcePath string) error {
fmt.Println("🧹 Discarding uncommitted changes...")
2024-09-09 01:25:07 +02:00
if err := runCommand("hg", "revert", "--all", "--no-backup", "-R", sourcePath); err != nil {
return fmt.Errorf("❌ failed to revert changes: %w", err)
2024-09-09 01:25:07 +02:00
}
fmt.Println("✅ Changes discarded successfully")
return nil
2024-09-09 01:25:07 +02:00
}
// Function to clean build
func CleanBuild(sourcePath string, fullClean bool) error {
fmt.Println("🧼 Cleaning build...")
if err := runCommand("hg", "revert", "--all", "--no-backup", "-R", sourcePath); err != nil {
return fmt.Errorf("❌ failed to revert changes: %w", err)
2024-09-09 01:25:07 +02:00
}
2024-12-16 21:24:35 +01:00
2025-01-11 23:11:11 +01:00
if fullClean {
fmt.Println("🧹 Performing full clean (clobber)...")
machCmd := filepath.Join(sourcePath, "mach")
2024-12-16 21:24:35 +01:00
if runtime.GOOS == "windows" {
machCmd = filepath.Join(sourcePath, "mach.bat")
2024-12-16 21:24:35 +01:00
}
cmd := exec.Command(machCmd, "clobber")
cmd.Dir = sourcePath // Run in the source directory
if err := cmd.Run(); err != nil {
return fmt.Errorf("❌ failed to clean build: %w", err)
2024-12-16 21:24:35 +01:00
}
fmt.Println("✅ Build artifacts cleaned successfully")
2024-09-09 01:25:07 +02:00
}
return nil
2024-09-09 01:25:07 +02:00
}
// UpdateRepo updates an existing repository if not already done
func UpdateRepo(sourcePath string) error {
repoMutex.Lock()
defer repoMutex.Unlock()
if repoOperationsDone {
fmt.Println(" Repository operations already completed - skipping update")
return nil
2024-09-09 01:25:07 +02:00
}
const maxRetries = 3
var success bool
for attempt := 1; attempt <= maxRetries; attempt++ {
fmt.Printf("🔄 Update attempt %d/%d\n", attempt, maxRetries)
if err := runCommandInDir(sourcePath, "hg", "pull", "-u"); err != nil {
fmt.Println("❌ Update failed - removing corrupted repository")
os.RemoveAll(sourcePath)
if attempt < maxRetries {
time.Sleep(10 * time.Second)
}
continue
}
success = true
break
2024-09-09 01:25:07 +02:00
}
if success {
repoOperationsDone = true
return nil
}
return fmt.Errorf("failed to update repository after %d attempts", maxRetries)
}
// CloneRepo clones a fresh repository if not already done
func CloneRepo(sourcePath, url string) error {
repoMutex.Lock()
defer repoMutex.Unlock()
if repoOperationsDone {
fmt.Println(" Repository operations already completed - skipping clone")
return nil
}
const maxRetries = 3
var success bool
parentDir := filepath.Dir(sourcePath)
if err := os.MkdirAll(parentDir, 0755); err != nil {
return fmt.Errorf("failed to create parent directory: %w", err)
}
for attempt := 1; attempt <= maxRetries; attempt++ {
fmt.Printf("📥 Clone attempt %d/%d\n", attempt, maxRetries)
if err := runCommand("hg", "clone", "--stream", url, sourcePath); err != nil {
fmt.Println("❌ Clone failed - cleaning up")
os.RemoveAll(sourcePath)
if attempt < maxRetries {
time.Sleep(10 * time.Second)
}
continue
}
success = true
break
}
if success {
repoOperationsDone = true
return nil
}
return fmt.Errorf("failed to clone repository after %d attempts", maxRetries)
2024-09-09 01:25:07 +02:00
}
// Function to update patches
func UpdatePatches(patchesDir, patchesRepo, sourcePath string) {
fmt.Println("🔄 Updating patches...")
2024-09-09 01:25:07 +02:00
if _, err := os.Stat(patchesDir); err == nil {
fmt.Println(" Patches directory exists - updating...")
2024-09-09 01:25:07 +02:00
if err := os.Chdir(patchesDir); err != nil {
errors = append(errors, "❌ Failed to navigate to patches directory")
2024-09-09 01:25:07 +02:00
return
}
fmt.Println("🧹 Cleaning patches directory...")
2024-09-09 01:25:07 +02:00
if err := runCommand("git", "clean", "-xdf"); err != nil {
errors = append(errors, "❌ Failed to clean patches directory")
2024-09-09 01:25:07 +02:00
}
fmt.Println("💾 Stashing local changes...")
2024-09-09 01:25:07 +02:00
_ = runCommand("git", "stash", "push", "--include-untracked")
fmt.Println("📥 Fetching updates...")
2024-09-09 01:25:07 +02:00
if err := runCommand("git", "fetch"); err != nil {
errors = append(errors, "❌ Failed to fetch updates from patches repository")
2024-09-09 01:25:07 +02:00
}
fmt.Println("🔀 Rebasing changes...")
2024-09-09 01:25:07 +02:00
if runCommand("git", "show-ref", "--verify", "--quiet", "refs/heads/main") == nil {
if err := runCommand("git", "rebase", "origin/main"); err != nil {
errors = append(errors, "❌ Failed to rebase updates from main branch")
2024-09-09 01:25:07 +02:00
}
} else if runCommand("git", "show-ref", "--verify", "--quiet", "refs/heads/master") == nil {
if err := runCommand("git", "rebase", "origin/master"); err != nil {
errors = append(errors, "❌ Failed to rebase updates from master branch")
2024-09-09 01:25:07 +02:00
}
} else {
errors = append(errors, "❌ No valid branch (main or master) found in patches repository")
2024-09-09 01:25:07 +02:00
return
}
2024-09-09 01:25:07 +02:00
if runCommand("git", "stash", "list") == nil {
fmt.Println("↩️ Restoring stashed changes...")
2024-09-09 01:25:07 +02:00
_ = runCommand("git", "stash", "pop")
} else {
fmt.Println(" No stash entries found - skipping pop")
2024-09-09 01:25:07 +02:00
}
} else {
fmt.Println("📥 Cloning patches repository...")
2024-09-09 01:25:07 +02:00
if err := runCommand("git", "clone", patchesRepo, patchesDir); err != nil {
errors = append(errors, "❌ Failed to clone patches repository")
2024-09-09 01:25:07 +02:00
}
}
2024-09-10 23:21:46 +02:00
fmt.Println("📂 Copying files to source directory...")
2025-01-11 23:11:11 +01:00
if runtime.GOOS == "windows" || !isMsys2() {
fmt.Println("🖥️ Using robocopy for Windows...")
2024-09-13 12:47:30 +02:00
if err := runCommand("robocopy", patchesDir, sourcePath, "*", "/S", "/XF", ".git", "/XD", ".git"); err != nil {
errors = append(errors, "❌ Failed to copy files (Windows robocopy)")
2024-09-10 23:21:46 +02:00
}
} else {
fmt.Println("🐧 Using rsync for Unix...")
2024-09-10 23:21:46 +02:00
if err := runCommand("rsync", "-av", "--exclude=.git", patchesDir+"/", sourcePath+"/"); err != nil {
errors = append(errors, "❌ Failed to copy files (rsync)")
2024-09-10 23:21:46 +02:00
}
2024-09-13 12:47:30 +02:00
}
2024-09-09 01:25:07 +02:00
}
// Function to configure Spitfire
func Configure(sourcePath string) error {
fmt.Println("⚙️ Configuring Spitfire...")
2024-09-09 01:25:07 +02:00
if err := os.Chdir(sourcePath); err != nil {
return fmt.Errorf("❌ failed to navigate to source directory: %w", err)
2024-09-09 01:25:07 +02:00
}
2024-09-13 12:47:30 +02:00
machCmd := "./mach"
2024-09-13 12:47:30 +02:00
if runtime.GOOS == "windows" {
machCmd = ".\\mach"
2024-09-09 01:25:07 +02:00
}
fmt.Println("🔧 Running mach configure...")
return runCommand(machCmd, "configure")
2024-09-09 01:25:07 +02:00
}
// Function to build Spitfire
func Build(sourcePath string) error {
fmt.Println("🔨 Building Spitfire...")
2024-09-09 01:25:07 +02:00
if err := os.Chdir(sourcePath); err != nil {
return fmt.Errorf("❌ failed to navigate to source directory: %w", err)
2024-09-09 01:25:07 +02:00
}
2024-09-13 12:47:30 +02:00
machCmd := "./mach"
2024-09-13 12:47:30 +02:00
if runtime.GOOS == "windows" {
machCmd = ".\\mach"
2024-09-09 01:25:07 +02:00
}
fmt.Println("🏗️ Running mach build...")
return runCommand(machCmd, "build")
2024-09-09 01:25:07 +02:00
}
// Function to run the project after build
func RunProject(sourcePath string) error {
fmt.Println("🚀 Running the project...")
2024-09-13 12:47:30 +02:00
2024-12-11 17:12:00 +01:00
// Resolve the build directory
fmt.Println("🔍 Resolving build directory...")
2024-12-11 17:12:00 +01:00
buildDir, err := ResolveBuildDir(sourcePath)
if err != nil {
return fmt.Errorf("❌ error resolving build directory: %w", err)
2024-12-11 17:12:00 +01:00
}
fmt.Printf(" ├─ Build directory: %s\n", buildDir)
2024-12-11 17:12:00 +01:00
// List of possible binaries
binaries := []string{"firefox", "Firefox", "spitfire", "Spitfire"}
var binaryPath string
// Check for the existence of binaries
fmt.Println("🔎 Searching for executable binary...")
2024-12-11 17:12:00 +01:00
for _, binary := range binaries {
binaryPath = filepath.Join(buildDir, binary)
if _, err := os.Stat(binaryPath); err == nil {
fmt.Printf("✅ Found binary: %s\n", binaryPath)
2024-12-11 17:12:00 +01:00
break
}
binaryPath = ""
2024-09-09 01:25:07 +02:00
}
2024-09-13 12:47:30 +02:00
2024-12-11 17:12:00 +01:00
if binaryPath == "" {
return fmt.Errorf("❌ no suitable binary found to run the project")
2024-09-09 01:25:07 +02:00
}
2024-12-11 17:12:00 +01:00
// Create a unique profile directory for each run
profilePath := filepath.Join(buildDir, fmt.Sprintf("profile_%d", time.Now().UnixNano()))
fmt.Printf("📁 Creating temporary profile: %s\n", profilePath)
if err := os.Mkdir(profilePath, 0755); err != nil {
return fmt.Errorf("❌ failed to create profile directory: %w", err)
}
// Run the binary with the new profile
fmt.Printf("⚡ Launching: %s -no-remote -profile %s\n", binaryPath, profilePath)
cmd := exec.Command(binaryPath, "-no-remote", "-profile", profilePath)
2024-12-11 17:12:00 +01:00
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
fmt.Printf("✅ Running binary: %s with new profile at %s\n", binaryPath, profilePath)
runErr := cmd.Run()
// Clean up profile directory
fmt.Println("🧹 Cleaning up temporary profile...")
if removeErr := os.RemoveAll(profilePath); removeErr != nil {
fmt.Printf("⚠️ Warning: failed to delete profile directory: %v\n", removeErr)
} else {
fmt.Println("✅ Profile directory cleaned successfully")
2024-12-11 17:12:00 +01:00
}
if runErr != nil {
return fmt.Errorf("❌ failed to run the project: %w", runErr)
}
fmt.Println("🎉 Binary execution completed successfully")
return nil
2024-12-11 17:12:00 +01:00
}
// ResolveBuildDir detects the build directory dynamically
func ResolveBuildDir(sourcePath string) (string, error) {
fmt.Println("🔍 Detecting build directory...")
2024-12-11 17:12:00 +01:00
// The expected build directory pattern
globPattern := filepath.Join(sourcePath, "obj-*")
// Find matching directories
matches, err := filepath.Glob(globPattern)
if err != nil {
return "", fmt.Errorf("❌ error resolving build directory: %v", err)
2024-12-11 17:12:00 +01:00
}
if len(matches) == 0 {
return "", fmt.Errorf("❌ build directory not found under %s", sourcePath)
2024-12-11 17:12:00 +01:00
}
full := filepath.Join(matches[0], "dist", "bin")
// Return the first match (assumes one build directory exists)
return full, nil
2024-09-09 01:25:07 +02:00
}
// Function to print collected errors
func PrintErrors() {
if len(errors) > 0 {
fmt.Println("❌ The following errors occurred during execution:")
2024-09-09 01:25:07 +02:00
for _, err := range errors {
fmt.Printf(" - %s\n", err)
2024-09-09 01:25:07 +02:00
}
}
}