added automatic mozilla-release download

This commit is contained in:
partisan 2025-04-13 19:21:04 +02:00
parent e5c95924d5
commit a13f43086f
6 changed files with 477 additions and 194 deletions

237
main.go
View file

@ -46,33 +46,75 @@ var (
var errors []error // This is also in build.go. Great! This code is a mess.
func init() {
flag.StringVar(&target, "t", "", "Target location format: component-arch-release-platform")
flag.BoolVar(&compress, "c", false, "Compress the build directory into a tar.gz file before uploading")
flag.StringVar(&version, "v", "", "Specify version for the package. For nightly, use current date if not specified.")
flag.StringVar(&component, "component", "browser", "Component name (default: browser)")
flag.StringVar(&arch, "arch", runtime.GOARCH, "Architecture (default: system architecture)")
flag.StringVar(&release, "release", "nightly", "Release type (default: nightly)")
flag.StringVar(&platform, "platform", runtime.GOOS, "Platform (default: system platform)")
flag.BoolVar(&all, "a", false, "Perform all steps (build, clean, update)")
flag.BoolVar(&buildFlag, "b", false, "Build Spitfire")
flag.BoolVar(&clean, "clean", false, "Revert uncommitted changes without removing build artifacts")
flag.BoolVar(&fullClean, "full-clean", false, "Perform a full clean by removing all build artifacts (clobber)")
flag.BoolVar(&update, "u", false, "Update Mozilla repository")
flag.BoolVar(&prePatch, "pre-patch", false, "Apply pre-build patches")
flag.BoolVar(&postPatch, "post-patch", false, "Apply post-build patches")
flag.BoolVar(&skipPatchUpdate, "skip-patch-update", false, "Skip updating the patches repository")
flag.BoolVar(&run, "r", false, "Run the project after build")
flag.BoolVar(&upload, "upload", false, "Upload the compressed build file to SourceForge")
flag.StringVar(&uploadPath, "upload-path", "", "Path to the file to upload if no build present")
flag.BoolVar(&skipDeps, "skip-deps", false, "Skip checking for required system dependencies")
flag.BoolVar(&ignoreErrors, "ignore-errors", false, "Processes all steps even if errors occur")
flag.Bool("h", false, "Display help message")
flag.StringVar(&target, "t", "", "🎯 Target location format: component-arch-release-platform")
flag.BoolVar(&compress, "c", false, "🗜️ Compress the build directory into a tar.gz file before uploading")
flag.StringVar(&version, "v", "", "🏷️ Specify version for the package. For nightly, use current date if not specified.")
flag.StringVar(&component, "component", "browser", "🌐 Component name (default: browser)")
flag.StringVar(&arch, "arch", runtime.GOARCH, "💻 Architecture (default: system architecture)")
flag.StringVar(&release, "release", "nightly", "🌙 Release type (default: nightly)")
flag.StringVar(&platform, "platform", runtime.GOOS, "🖥️ Platform (default: system platform)")
flag.BoolVar(&all, "a", false, "🔄 Perform all steps (build, clean, update)")
flag.BoolVar(&buildFlag, "b", false, "🔨 Build Spitfire")
flag.BoolVar(&clean, "clean", false, "🧹 Revert uncommitted changes without removing build artifacts")
flag.BoolVar(&fullClean, "full-clean", false, "🧼 Perform a full clean by removing all build artifacts (clobber)")
flag.BoolVar(&update, "u", false, "🔄 Update Mozilla repository")
flag.BoolVar(&prePatch, "pre-patch", false, "🔧 Apply pre-build patches")
flag.BoolVar(&postPatch, "post-patch", false, "🔧 Apply post-build patches")
flag.BoolVar(&skipPatchUpdate, "skip-patch-update", false, "⏭️ Skip updating the patches repository")
flag.BoolVar(&run, "r", false, "🚀 Run the project after build")
flag.BoolVar(&upload, "upload", false, "📤 Upload the compressed build file to SourceForge")
flag.StringVar(&uploadPath, "upload-path", "", "📂 Path to the file to upload if no build present")
flag.BoolVar(&skipDeps, "skip-deps", false, "⏭️ Skip checking for required system dependencies")
flag.BoolVar(&ignoreErrors, "ignore-errors", false, "⚠️ Processes all steps even if errors occur")
flag.Bool("h", false, " Display help message")
}
func printHelp() {
fmt.Println("Usage: go run . -p=<path-to-build> -t=<target> [-c|--compress] [-v|--version=<version>] [-component=<component>] [-arch=<architecture>] [-release=<release>] [-platform=<platform>]")
flag.PrintDefaults()
fmt.Println("Example: go run . --upload -c -a")
fmt.Println("📖 Spitfire Build System Help")
fmt.Println("Usage: go run . [options]")
fmt.Println("")
fmt.Println("🚀 Main Operations:")
fmt.Println("")
fmt.Println("🔧 Build Options:")
fmt.Println(" -a, -all Perform all steps (build, clean, update)")
fmt.Println(" -b, -build Build Spitfire")
fmt.Println(" -r, -run Run the project after build")
fmt.Println("")
fmt.Println("🧹 Clean Options:")
fmt.Println(" -clean Revert uncommitted changes")
fmt.Println(" -full-clean Remove all build artifacts (clobber)")
fmt.Println("")
fmt.Println("🔄 Update Options:")
fmt.Println(" -u, -update Update Mozilla repository")
fmt.Println(" -pre-patch Apply pre-build patches")
fmt.Println(" -post-patch Apply post-build patches")
fmt.Println(" -skip-patch-update Skip updating patches repository")
fmt.Println("")
fmt.Println("📦 Package Options:")
fmt.Println(" -c, -compress Compress build directory to tar.gz")
fmt.Println(" -upload Upload compressed file to SourceForge")
fmt.Println(" -upload-path Path to file for upload")
fmt.Println("")
fmt.Println("⚙️ Configuration Options:")
fmt.Println(" -t, -target Target format: component-arch-release-platform")
fmt.Println(" -v, -version Package version (default: date for nightly)")
fmt.Println(" -component Component name (default: browser)")
fmt.Println(" -arch Architecture (default: system arch)")
fmt.Println(" -release Release type (default: nightly)")
fmt.Println(" -platform Platform (default: system OS)")
fmt.Println("")
fmt.Println("🔍 Other Options:")
fmt.Println(" -skip-deps Skip system dependency checks")
fmt.Println(" -ignore-errors Continue despite errors")
fmt.Println(" -h, -help Show this help message")
fmt.Println("")
fmt.Println("💡 Examples:")
fmt.Println(" Full build and upload: go run . -a -c -upload")
fmt.Println(" Just build: go run . -b")
fmt.Println(" Clean build: go run . -clean")
fmt.Println(" Upload existing: go run . -upload -upload-path=./build.tar.gz")
fmt.Println("")
fmt.Println("🌐 Project: https://weforge.xyz/Spitfire")
os.Exit(0)
}
@ -92,162 +134,178 @@ func main() {
printHelp()
}
fmt.Println("🚀 Starting Spitfire build process...")
if !skipDeps {
fmt.Println("🔍 Checking system dependencies...")
if err := spitfire.CheckDependencies(); err != nil {
handleError(fmt.Errorf("system check failed: %w", err))
handleError(fmt.Errorf("system check failed: %w", err))
}
fmt.Println("✅ All dependencies verified")
}
if version == "" && release == "nightly" {
version = time.Now().Format("2006.01.02")
fmt.Printf("📅 Using nightly version: %s\n", version)
}
// Save the initial directory
var err error
initialDir, err = os.Getwd()
if err != nil {
handleError(fmt.Errorf("failed to get current directory: %w", err))
handleError(fmt.Errorf("failed to get current directory: %w", err))
}
var buildDir string
if all || buildFlag || prePatch || postPatch || clean || update || run {
fmt.Println("🏗️ Starting build process...")
buildDir, err = BuildProcess()
if err != nil {
handleError(fmt.Errorf("build process failed: %w", err))
handleError(fmt.Errorf("build process failed: %w", err))
} else if buildDir == "" && (buildFlag || all || run) {
handleError(fmt.Errorf("build directory not found after build process"))
handleError(fmt.Errorf("build directory not found after build process"))
}
}
if compress || upload {
fmt.Println("📦 Packaging and uploading build...")
if err := PackageAndUploadProcess(buildDir); err != nil {
handleError(fmt.Errorf("package/upload failed: %w", err))
handleError(fmt.Errorf("package/upload failed: %w", err))
}
}
spitfire.PrintErrors()
if len(errors) > 0 {
fmt.Fprintf(os.Stderr, "Exiting with %d error(s):\n", len(errors))
fmt.Fprintf(os.Stderr, "Exiting with %d error(s):\n", len(errors))
for _, e := range errors {
fmt.Fprintf(os.Stderr, "- %v\n", e)
fmt.Fprintf(os.Stderr, " - %v\n", e)
}
os.Exit(1)
} else {
fmt.Println("🎉 All operations completed successfully!")
os.Exit(0)
}
}
func BuildProcess() (string, error) {
fmt.Println("📂 Resolving source path...")
sourcePath, err := spitfire.ResolvePath("./mozilla-release")
if err != nil {
return "", fmt.Errorf("resolve source path: %w", err)
return "", fmt.Errorf("resolve source path: %w", err)
}
var buildDir string
if all {
fmt.Println("🔄 Running full build process...")
if err := spitfire.DownloadSource(sourcePath, sourceRepo); err != nil {
return "", fmt.Errorf("download source: %w", err)
return "", fmt.Errorf("download source: %w", err)
}
if err := spitfire.DiscardChanges(sourcePath); err != nil {
return "", fmt.Errorf("discard changes: %w", err)
return "", fmt.Errorf("discard changes: %w", err)
}
if err := spitfire.CleanBuild(sourcePath, fullClean); err != nil {
return "", fmt.Errorf("clean build: %w", err)
return "", fmt.Errorf("clean build: %w", err)
}
if err := spitfire.UpdateRepo(sourcePath); err != nil {
return "", fmt.Errorf("update repo: %w", err)
return "", fmt.Errorf("update repo: %w", err)
}
if err := spitfire.ApplyPatches(sourcePath, patchesRepo, "pre-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil {
return "", fmt.Errorf("pre-patch: %w", err)
return "", fmt.Errorf("pre-patch: %w", err)
}
if err := spitfire.Configure(sourcePath); err != nil {
return "", fmt.Errorf("configure: %w", err)
return "", fmt.Errorf("configure: %w", err)
}
if err := spitfire.Build(sourcePath); err != nil {
return "", fmt.Errorf("build: %w", err)
return "", fmt.Errorf("build: %w", err)
}
buildDir, err = spitfire.ResolveBuildDir(sourcePath)
if err != nil {
return "", fmt.Errorf("resolve build dir: %w", err)
return "", fmt.Errorf("resolve build dir: %w", err)
}
if err := spitfire.ApplyPatches(buildDir, patchesRepo, "post-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil {
return "", fmt.Errorf("post-patch: %w", err)
return "", fmt.Errorf("post-patch: %w", err)
}
if run {
if err := spitfire.RunProject(sourcePath); err != nil {
return "", fmt.Errorf("run project: %w", err)
return "", fmt.Errorf("run project: %w", err)
}
}
fmt.Println("Spitfire build completed successfully.")
fmt.Println("Spitfire build completed successfully")
} else if buildFlag {
fmt.Println("🔨 Running build process...")
if prePatch {
if err := spitfire.ApplyPatches(sourcePath, patchesRepo, "pre-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil {
return "", fmt.Errorf("pre-patch: %w", err)
return "", fmt.Errorf("pre-patch: %w", err)
}
}
if err := spitfire.Configure(sourcePath); err != nil {
return "", fmt.Errorf("configure: %w", err)
return "", fmt.Errorf("configure: %w", err)
}
if err := spitfire.Build(sourcePath); err != nil {
return "", fmt.Errorf("build: %w", err)
return "", fmt.Errorf("build: %w", err)
}
buildDir, err = spitfire.ResolveBuildDir(sourcePath)
if err != nil {
return "", fmt.Errorf("resolve build dir: %w", err)
return "", fmt.Errorf("resolve build dir: %w", err)
}
if postPatch {
if err := spitfire.ApplyPatches(buildDir, patchesRepo, "post-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil {
return "", fmt.Errorf("post-patch: %w", err)
return "", fmt.Errorf("post-patch: %w", err)
}
}
if run {
if err := spitfire.RunProject(sourcePath); err != nil {
return "", fmt.Errorf("run project: %w", err)
return "", fmt.Errorf("run project: %w", err)
}
}
fmt.Println("Spitfire build completed successfully.")
fmt.Println("Spitfire build completed successfully")
} else if clean {
fmt.Println("🧹 Cleaning build...")
if err := spitfire.CleanBuild(sourcePath, fullClean); err != nil {
return "", fmt.Errorf("clean build: %w", err)
return "", fmt.Errorf("clean build: %w", err)
}
fmt.Println("Cleaned Firefox build.")
fmt.Println("✅ Firefox build cleaned")
} else if update {
fmt.Println("🔄 Updating repository...")
if err := spitfire.DownloadSource(sourcePath, sourceRepo); err != nil {
return "", fmt.Errorf("download source: %w", err)
return "", fmt.Errorf("download source: %w", err)
}
if err := spitfire.UpdateRepo(sourcePath); err != nil {
return "", fmt.Errorf("update repo: %w", err)
return "", fmt.Errorf("update repo: %w", err)
}
fmt.Println("Mozilla repository updated.")
fmt.Println("Mozilla repository updated")
} else if prePatch {
fmt.Println("🔧 Applying pre-compile patches...")
if err := spitfire.DownloadSource(sourcePath, sourceRepo); err != nil {
return "", fmt.Errorf("download source: %w", err)
return "", fmt.Errorf("download source: %w", err)
}
if err := spitfire.ApplyPatches(sourcePath, patchesRepo, "pre-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil {
return "", fmt.Errorf("pre-patch: %w", err)
return "", fmt.Errorf("pre-patch: %w", err)
}
fmt.Println("Patches updated.")
fmt.Println("✅ Pre-compile patches applied")
} else if postPatch {
fmt.Println("🔧 Applying post-compile patches...")
buildDir, err = spitfire.ResolveBuildDir(sourcePath)
if err != nil {
return "", fmt.Errorf("resolve build dir: %w", err)
return "", fmt.Errorf("resolve build dir: %w", err)
}
if _, err := os.Stat(buildDir); err == nil {
if err := spitfire.ApplyPatches(buildDir, patchesRepo, "post-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil {
return "", fmt.Errorf("post-patch: %w", err)
return "", fmt.Errorf("post-patch: %w", err)
}
}
if run {
if err := spitfire.RunProject(sourcePath); err != nil {
return "", fmt.Errorf("run project: %w", err)
return "", fmt.Errorf("run project: %w", err)
}
}
} else if run {
fmt.Println("🚀 Running project...")
if err := spitfire.RunProject(sourcePath); err != nil {
return "", fmt.Errorf("run project: %w", err)
return "", fmt.Errorf("run project: %w", err)
}
}
@ -255,69 +313,79 @@ func BuildProcess() (string, error) {
}
func PackageAndUploadProcess(buildDir string) error {
fmt.Println("📦 Starting package and upload process...")
restoreWorkingDirectory()
pathToUse := buildDir
if upload && uploadPath != "" {
pathToUse = uploadPath
fmt.Printf(" ├─ Using custom upload path: %s\n", pathToUse)
}
if pathToUse == "" {
return fmt.Errorf("no valid build or upload path provided")
return fmt.Errorf("no valid build or upload path provided")
}
fmt.Println("📏 Calculating directory size...")
uncompressedSize, err := spitfire.GetDirectorySize(pathToUse)
if err != nil {
return fmt.Errorf("get directory size: %w", err)
return fmt.Errorf("get directory size: %w", err)
}
fmt.Printf("Uncompressed directory size: %s\n", spitfire.BytesToHumanReadable(uncompressedSize))
fmt.Printf(" ├─ Uncompressed size: %s\n", spitfire.BytesToHumanReadable(uncompressedSize))
outputCompressedFile := filepath.Join(".", fmt.Sprintf(
"%s-%s-%s-%s.tar.gz",
component, arch, release, platform,
))
fmt.Printf(" ├─ Output filename: %s\n", outputCompressedFile)
if compress {
fmt.Println("🗜️ Compressing directory...")
if err := spitfire.CompressDirectory(pathToUse, outputCompressedFile); err != nil {
return fmt.Errorf("compress directory: %w", err)
return fmt.Errorf("compress directory: %w", err)
}
fmt.Printf("Build directory compressed to: %s\n", outputCompressedFile)
fmt.Printf("Build directory compressed to: %s\n", outputCompressedFile)
}
compressedSize, err := spitfire.GetFileSize(outputCompressedFile)
if err != nil {
return fmt.Errorf("get compressed file size: %w", err)
return fmt.Errorf("get compressed file size: %w", err)
}
fmt.Printf("Compressed file size: %s\n", spitfire.BytesToHumanReadable(compressedSize))
fmt.Printf(" ├─ Compressed size: %s\n", spitfire.BytesToHumanReadable(compressedSize))
compressionRatio, efficiency := spitfire.CalculateCompressionEfficiency(uncompressedSize, compressedSize)
fmt.Printf("Compression ratio: %.2f:1\n", compressionRatio)
fmt.Printf("Compression efficiency: %.2f%%\n", efficiency)
fmt.Printf("Compressed dir: %s\n", pathToUse)
fmt.Printf(" ├─ Compression ratio: %.2f:1\n", compressionRatio)
fmt.Printf(" ├─ Compression efficiency: %.2f%%\n", efficiency)
fmt.Printf(" └─ Source directory: %s\n", pathToUse)
if !upload {
fmt.Println("⏩ Upload skipped (not requested)")
return nil
}
fmt.Println("🔑 Loading configuration...")
config, err := spitfire.LoadConfig()
if err != nil {
return fmt.Errorf("load config: %w", err)
return fmt.Errorf("load config: %w", err)
}
if _, err := os.Stat(outputCompressedFile); err != nil {
return fmt.Errorf("compressed file not found: %w", err)
return fmt.Errorf("compressed file not found: %w", err)
}
uploadDir := fmt.Sprintf("%s/%s/%s/%s", component, arch, release, version)
fmt.Printf("📤 Uploading to: %s\n", uploadDir)
if err := spitfire.Upload(config, outputCompressedFile, "/home/frs/project/spitfire-browser/"+uploadDir+"/"); err != nil {
return fmt.Errorf("upload compressed file: %w", err)
return fmt.Errorf("upload compressed file: %w", err)
}
fmt.Println("Compressed file uploaded successfully.")
fmt.Println("Compressed file uploaded successfully")
fmt.Println("📥 Downloading APPINDEX...")
if err := spitfire.DownloadAPPINDEX(config, "/home/frs/project/spitfire-browser/"); err != nil {
fmt.Println("Failed to download APPINDEX. A new one will be created and uploaded.")
fmt.Println("⚠️ Failed to download APPINDEX - creating new one")
}
fmt.Println("📝 Updating APPINDEX...")
if err := spitfire.PackageAPPINDEX(
packageName,
release,
@ -334,24 +402,27 @@ func PackageAndUploadProcess(buildDir string) error {
platform,
uploadDir,
); err != nil {
return fmt.Errorf("package APPINDEX: %w", err)
return fmt.Errorf("package APPINDEX: %w", err)
}
fmt.Println("APPINDEX updated successfully.")
fmt.Println("APPINDEX updated successfully")
fmt.Println("🧹 Cleaning APPINDEX...")
if err := spitfire.CleanAppIndex(); err != nil {
return fmt.Errorf("clean APPINDEX: %w", err)
return fmt.Errorf("clean APPINDEX: %w", err)
}
fmt.Println("📤 Uploading APPINDEX...")
if err := spitfire.UploadAPPINDEX(config, "/home/frs/project/spitfire-browser/"); err != nil {
return fmt.Errorf("upload APPINDEX: %w", err)
return fmt.Errorf("upload APPINDEX: %w", err)
}
fmt.Println("APPINDEX uploaded successfully.")
fmt.Println("APPINDEX uploaded successfully")
return nil
}
func restoreWorkingDirectory() {
fmt.Println("↩️ Restoring working directory...")
if err := os.Chdir(initialDir); err != nil {
log.Printf("Failed to restore working directory: %v", err)
log.Printf("⚠️ Failed to restore working directory: %v", err)
}
}

View file

@ -27,32 +27,36 @@ func PackageAPPINDEX(
platform, // e.g. "linux"
remoteDir string, // e.g. "nightly/linux/amd64"
) error {
fmt.Println("📦 Starting APPINDEX packaging...")
// Construct a filename that matches what you compress & upload
// Adjust this naming convention to match your compress step exactly!
// Example: "spitfire-browser-nightly-amd64-linux.tar.gz"
fileName := fmt.Sprintf("%s-%s-%s-%s.tar.gz", origin, arch, release, platform)
fmt.Printf(" ├─ Generated filename: %s\n", fileName)
// If you have a real file on disk, you might call `calcChecksum(fileName)`.
// For demo, we call `calcChecksum` with a mock package name.
// Calculate checksum
checksum := calcChecksum(fileName)
fmt.Printf(" ├─ Calculated checksum: %s\n", checksum[:8]+"...") // Show partial checksum
// Remove existing entry based on P, R, A, o, and p:
removeExistingEntry(name, release, arch, origin, platform)
// Use current Unix timestamp
timestamp := time.Now().Unix()
fmt.Printf(" ├─ Current timestamp: %d\n", timestamp)
// Open or create the APPINDEX file for appending
file, err := os.OpenFile("./APPINDEX", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Failed to open APPINDEX file: %v", err)
log.Fatalf("Failed to open APPINDEX file: %v", err)
}
defer file.Close()
// Build the SourceForge-based download URL:
// https://downloads.sourceforge.net/project/spitfire-browser/<remoteDir>/<fileName>
downloadURL := fmt.Sprintf("https://downloads.sourceforge.net/project/spitfire-browser/%s/%s", remoteDir, fileName)
fmt.Printf(" ├─ Download URL: %s\n", downloadURL)
// Format the entry.
entry := fmt.Sprintf(`
@ -85,11 +89,13 @@ func PackageAPPINDEX(
// Trim leading newline to keep it clean
entry = strings.TrimPrefix(entry, "\n")
// Write entry
fmt.Println("✏️ Writing new entry...")
if _, err := file.WriteString(entry + "\n"); err != nil {
log.Fatalf("Failed to write to APPINDEX file: %v", err)
log.Fatalf("Failed to write to APPINDEX file: %v", err)
}
fmt.Println("APPINDEX has been updated successfully.")
fmt.Println("🎉 APPINDEX updated successfully")
return nil
}
@ -192,10 +198,12 @@ func removeExistingEntry(name, release, arch, origin, platform string) {
// CleanAppIndex cleans up any orphaned "C:" entries and collapses excessive newlines
func CleanAppIndex() error {
fmt.Println("🧼 Cleaning APPINDEX file...")
// Read file contents
content, err := os.ReadFile("./APPINDEX")
if err != nil {
return fmt.Errorf("failed to read APPINDEX: %v", err)
return fmt.Errorf("❌ Failed to read APPINDEX: %v", err)
}
// Split the file content into lines
@ -242,11 +250,12 @@ func CleanAppIndex() error {
cleanedContent = strings.ReplaceAll(cleanedContent, "\n\n\n", "\n\n")
// Write the cleaned content back to the file
fmt.Println("✏️ Writing cleaned content...")
err = os.WriteFile("./APPINDEX", []byte(cleanedContent), 0644)
if err != nil {
return fmt.Errorf("failed to write cleaned APPINDEX: %v", err)
return fmt.Errorf("❌ Failed to write cleaned APPINDEX: %v", err)
}
fmt.Println("APPINDEX cleaned successfully.")
fmt.Println("APPINDEX cleaned successfully")
return nil
}

View file

@ -6,14 +6,21 @@ import (
"os/exec"
"path/filepath"
"runtime"
"sync"
"time"
)
// Array to store errors
var errors []string
var (
repoOperationsDone bool
repoMutex sync.Mutex
)
// 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)
if runtime.GOOS == "windows" {
var cmd *exec.Cmd
if scope == "user" {
@ -21,18 +28,18 @@ func SetGlobalEnv(variable, value string, scope string) error {
} 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)
return fmt.Errorf("unknown scope: %s", scope)
}
// 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))
return fmt.Errorf("failed to set environment variable %s=%s: %v\nOutput: %s", variable, value, err, string(out))
}
fmt.Printf("Successfully set %s=%s\n", variable, value)
fmt.Printf("Successfully set %s=%s\n", variable, value)
return nil
} else {
return fmt.Errorf("global environment variable setting is not supported on non-Windows systems")
return fmt.Errorf("global environment variable setting is not supported on non-Windows systems")
}
}
@ -45,49 +52,63 @@ func runCommand(command string, args ...string) error {
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()
}
// Function to resolve paths using absolute path
func ResolvePath(path string) (string, error) {
fmt.Printf("📍 Resolving path: %s\n", path)
// Convert Unix-style slashes to the platform's native slashes
path = filepath.FromSlash(path)
// Get the absolute path
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("failed to resolve path: %s, error: %v", path, err)
return "", fmt.Errorf("failed to resolve path: %s, error: %v", path, err)
}
fmt.Printf(" └─ Resolved to: %s\n", absPath)
return absPath, nil
}
// Function to download Mozilla source if not present
func DownloadSource(sourcePath, sourceRepo string) error {
fmt.Println("🔍 Checking Mozilla source repository...")
if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
fmt.Println("Mozilla source not found. Cloning from repository...")
fmt.Println("📥 Mozilla source not found - cloning repository...")
if err := runCommand("hg", "clone", sourceRepo, sourcePath); err != nil {
return fmt.Errorf("failed to clone Mozilla repository: %w", err)
return fmt.Errorf("failed to clone Mozilla repository: %w", err)
}
fmt.Println("✅ Repository cloned successfully")
} else {
fmt.Println("Mozilla source already exists.")
fmt.Println(" Mozilla source already exists")
}
return nil
}
// Function to discard uncommitted changes
func DiscardChanges(sourcePath string) error {
fmt.Println("Discarding uncommitted changes...")
fmt.Println("🧹 Discarding uncommitted changes...")
if err := runCommand("hg", "revert", "--all", "--no-backup", "-R", sourcePath); err != nil {
return fmt.Errorf("failed to revert changes: %w", err)
return fmt.Errorf("failed to revert changes: %w", err)
}
fmt.Println("✅ Changes discarded successfully")
return nil
}
// Function to clean build
func CleanBuild(sourcePath string, fullClean bool) error {
fmt.Println("Cleaning build...")
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)
return fmt.Errorf("failed to revert changes: %w", err)
}
if fullClean {
fmt.Println("🧹 Performing full clean (clobber)...")
machCmd := filepath.Join(sourcePath, "mach")
if runtime.GOOS == "windows" {
machCmd = filepath.Join(sourcePath, "mach.bat")
@ -95,117 +116,195 @@ func CleanBuild(sourcePath string, fullClean bool) error {
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)
return fmt.Errorf("failed to clean build: %w", err)
}
fmt.Println("Build artifacts cleaned successfully.")
fmt.Println("Build artifacts cleaned successfully")
}
return nil
}
// Function to update Mozilla repository
// UpdateRepo updates an existing repository if not already done
func UpdateRepo(sourcePath string) error {
fmt.Println("Updating Mozilla repository...")
if err := os.Chdir(sourcePath); err != nil {
return fmt.Errorf("failed to navigate to source directory: %w", err)
repoMutex.Lock()
defer repoMutex.Unlock()
if repoOperationsDone {
fmt.Println(" Repository operations already completed - skipping update")
return nil
}
if err := runCommand("hg", "pull", "-u"); err != nil {
return fmt.Errorf("failed to update repository: %w", err)
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
}
return nil
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)
}
// Function to update patches
func UpdatePatches(patchesDir, patchesRepo, sourcePath string) {
fmt.Println("Updating patches...")
fmt.Println("🔄 Updating patches...")
if _, err := os.Stat(patchesDir); err == nil {
fmt.Println("Patches directory already exists. Cleaning and pulling updates...")
fmt.Println(" Patches directory exists - updating...")
if err := os.Chdir(patchesDir); err != nil {
errors = append(errors, "Failed to navigate to patches directory.")
errors = append(errors, "Failed to navigate to patches directory")
return
}
fmt.Println("🧹 Cleaning patches directory...")
if err := runCommand("git", "clean", "-xdf"); err != nil {
errors = append(errors, "Failed to clean patches directory.")
errors = append(errors, "Failed to clean patches directory")
}
fmt.Println("💾 Stashing local changes...")
_ = runCommand("git", "stash", "push", "--include-untracked")
fmt.Println("📥 Fetching updates...")
if err := runCommand("git", "fetch"); err != nil {
errors = append(errors, "Failed to fetch updates from patches repository.")
errors = append(errors, "Failed to fetch updates from patches repository")
}
fmt.Println("🔀 Rebasing changes...")
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.")
errors = append(errors, "Failed to rebase updates from main branch")
}
} 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.")
errors = append(errors, "Failed to rebase updates from master branch")
}
} else {
errors = append(errors, "No valid branch (main or master) found in patches repository.")
errors = append(errors, "No valid branch (main or master) found in patches repository")
return
}
if runCommand("git", "stash", "list") == nil {
fmt.Println("↩️ Restoring stashed changes...")
_ = runCommand("git", "stash", "pop")
} else {
fmt.Println("No stash entries found, skipping pop.")
fmt.Println(" No stash entries found - skipping pop")
}
} else {
fmt.Println("Patches directory does not exist. Cloning repository...")
fmt.Println("📥 Cloning patches repository...")
if err := runCommand("git", "clone", patchesRepo, patchesDir); err != nil {
errors = append(errors, "Failed to clone patches repository.")
errors = append(errors, "Failed to clone patches repository")
}
}
// Handle platform-specific rsync command
fmt.Println("Copying files from patches directory to Firefox source directory...")
fmt.Println("📂 Copying files to source directory...")
if runtime.GOOS == "windows" || !isMsys2() {
// Use robocopy for Windows
fmt.Println("🖥️ Using robocopy for Windows...")
if err := runCommand("robocopy", patchesDir, sourcePath, "*", "/S", "/XF", ".git", "/XD", ".git"); err != nil {
errors = append(errors, "Failed to copy files (Windows robocopy).")
errors = append(errors, "Failed to copy files (Windows robocopy)")
}
} else {
// Use rsync for Unix-like systems
fmt.Println("🐧 Using rsync for Unix...")
if err := runCommand("rsync", "-av", "--exclude=.git", patchesDir+"/", sourcePath+"/"); err != nil {
errors = append(errors, "Failed to copy files (rsync).")
errors = append(errors, "Failed to copy files (rsync)")
}
}
}
// Function to configure Spitfire
func Configure(sourcePath string) error {
fmt.Println("Configuring Spitfire...")
fmt.Println("⚙️ Configuring Spitfire...")
if err := os.Chdir(sourcePath); err != nil {
return fmt.Errorf("failed to navigate to source directory: %w", err)
return fmt.Errorf("failed to navigate to source directory: %w", err)
}
machCmd := "./mach"
if runtime.GOOS == "windows" {
machCmd = ".\\mach"
}
fmt.Println("🔧 Running mach configure...")
return runCommand(machCmd, "configure")
}
// Function to build Spitfire
func Build(sourcePath string) error {
fmt.Println("Building Spitfire...")
fmt.Println("🔨 Building Spitfire...")
if err := os.Chdir(sourcePath); err != nil {
return fmt.Errorf("failed to navigate to source directory: %w", err)
return fmt.Errorf("failed to navigate to source directory: %w", err)
}
machCmd := "./mach"
if runtime.GOOS == "windows" {
machCmd = ".\\mach"
}
fmt.Println("🏗️ Running mach build...")
return runCommand(machCmd, "build")
}
// Function to run the project after build
func RunProject(sourcePath string) error {
fmt.Println("Running the project...")
fmt.Println("🚀 Running the project...")
// Resolve the build directory
fmt.Println("🔍 Resolving build directory...")
buildDir, err := ResolveBuildDir(sourcePath)
if err != nil {
return fmt.Errorf("error resolving build directory: %w", err)
return fmt.Errorf("error resolving build directory: %w", err)
}
fmt.Printf(" ├─ Build directory: %s\n", buildDir)
// List of possible binaries
binaries := []string{"firefox", "Firefox", "spitfire", "Spitfire"}
@ -213,62 +312,66 @@ func RunProject(sourcePath string) error {
var binaryPath string
// Check for the existence of binaries
fmt.Println("🔎 Searching for executable binary...")
for _, binary := range binaries {
binaryPath = filepath.Join(buildDir, binary)
if _, err := os.Stat(binaryPath); err == nil {
fmt.Printf("Found binary: %s\n", binaryPath)
fmt.Printf("Found binary: %s\n", binaryPath)
break
}
binaryPath = ""
}
if binaryPath == "" {
return fmt.Errorf("no suitable binary found to run the project")
return fmt.Errorf("no suitable binary found to run the project")
}
// 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)
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)
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)
fmt.Printf("Running binary: %s with new profile at %s\n", binaryPath, profilePath)
runErr := cmd.Run()
// Delete the profile directory after execution
fmt.Printf("Deleting profile directory: %s\n", profilePath)
// 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)
fmt.Printf("⚠️ Warning: failed to delete profile directory: %v\n", removeErr)
} else {
fmt.Println("Profile directory deleted successfully.")
fmt.Println("✅ Profile directory cleaned successfully")
}
if runErr != nil {
return fmt.Errorf("failed to run the project: %w", runErr)
return fmt.Errorf("failed to run the project: %w", runErr)
}
fmt.Println("Binary execution finished successfully.")
fmt.Println("🎉 Binary execution completed successfully")
return nil
}
// ResolveBuildDir detects the build directory dynamically
func ResolveBuildDir(sourcePath string) (string, error) {
fmt.Println("🔍 Detecting build directory...")
// 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)
return "", fmt.Errorf("error resolving build directory: %v", err)
}
if len(matches) == 0 {
return "", fmt.Errorf("build directory not found under %s", sourcePath)
return "", fmt.Errorf("build directory not found under %s", sourcePath)
}
full := filepath.Join(matches[0], "dist", "bin")
@ -279,9 +382,9 @@ func ResolveBuildDir(sourcePath string) (string, error) {
// Function to print collected errors
func PrintErrors() {
if len(errors) > 0 {
fmt.Println("The following errors occurred during execution:")
fmt.Println("The following errors occurred during execution:")
for _, err := range errors {
fmt.Printf("- %s\n", err)
fmt.Printf(" - %s\n", err)
}
}
}

View file

@ -7,16 +7,29 @@ import (
"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
@ -40,10 +53,44 @@ func CheckDependencies() error {
}
}
// 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) {
missingTools = append(missingTools, "mach (ensure you are in the mozilla-release directory and it is built)")
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
@ -59,16 +106,42 @@ func CheckDependencies() error {
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
}
fmt.Printf("Command '%s' found at path '%s'\n", command, path)
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)

View file

@ -10,62 +10,68 @@ import (
// ApplyPatches handles cloning, updating, and applying patches
func ApplyPatches(sourcePath string, patchesRepo string, patchesPath string, patchesCloneDir string, skipUpdateRepo bool) error {
// Define the full patches path
fullPatchesPath := filepath.Join(patchesCloneDir, patchesPath) // Cleaned path without double slashes
fullPatchesPath := filepath.Join(patchesCloneDir, patchesPath)
fmt.Printf("Source Path: %s\n", sourcePath)
fmt.Printf("Patches Clone Directory: %s\n", patchesCloneDir)
fmt.Printf("Patches Repository URL: %s\n", patchesRepo)
fmt.Printf("Patches Path: %s\n", fullPatchesPath)
// Print configuration with consistent style
fmt.Printf("🔧 Config:\n")
fmt.Printf(" ├─ Source Path: %s\n", sourcePath)
fmt.Printf(" ├─ Patches Clone Dir: %s\n", patchesCloneDir)
fmt.Printf(" ├─ Patches Repo: %s\n", patchesRepo)
fmt.Printf(" └─ Patches Path: %s\n", fullPatchesPath)
// Check if the patches directory already exists
if _, err := os.Stat(patchesCloneDir); os.IsNotExist(err) {
// Clone the patches repository
fmt.Println("Patches directory does not exist. Proceeding to clone.")
fmt.Println("📥 Cloning patches repository...")
cmdClone := exec.Command("git", "clone", patchesRepo, patchesCloneDir)
cmdClone.Stdout = os.Stdout
cmdClone.Stderr = os.Stderr
if err := cmdClone.Run(); err != nil {
return fmt.Errorf("failed to clone patches repository: %v", err)
return fmt.Errorf("❌ Failed to clone patches repository: %v", err)
}
fmt.Println("Patches repository cloned successfully.")
fmt.Println("Patches repository cloned successfully")
} else {
if !skipUpdateRepo {
// If the directory exists, fetch and pull the latest changes
fmt.Println("Patches directory already exists. Fetching latest changes.")
fmt.Println("🔄 Updating patches repository...")
// Fetch updates
cmdFetch := exec.Command("git", "fetch", "--all")
cmdFetch.Dir = patchesCloneDir
cmdFetch.Stdout = os.Stdout
cmdFetch.Stderr = os.Stderr
if err := cmdFetch.Run(); err != nil {
return fmt.Errorf("failed to fetch updates for patches repository: %v", err)
return fmt.Errorf("❌ Failed to fetch updates: %v", err)
}
// Reset to latest
cmdReset := exec.Command("git", "reset", "--hard", "origin/main")
cmdReset.Dir = patchesCloneDir
cmdReset.Stdout = os.Stdout
cmdReset.Stderr = os.Stderr
if err := cmdReset.Run(); err != nil {
return fmt.Errorf("failed to reset patches repository to latest state: %v", err)
return fmt.Errorf("❌ Failed to reset repository: %v", err)
}
fmt.Println("Patches repository updated successfully.")
fmt.Println("✅ Patches repository updated")
} else {
fmt.Println("⏩ Skipping patches update (requested by user)")
}
}
// Verify the patches directory exists
if _, err := os.Stat(fullPatchesPath); os.IsNotExist(err) {
return fmt.Errorf("patches path not found: %s", fullPatchesPath)
return fmt.Errorf("❌ Patches path not found: %s", fullPatchesPath)
}
// Apply the patches using `run.sh`
applyCmd := exec.Command("go", "run", ".", "--path", sourcePath, "--patches", fullPatchesPath)
applyCmd.Dir = patchesCloneDir // Run from the patches directory
applyCmd.Dir = patchesCloneDir
applyCmd.Stdout = os.Stdout
applyCmd.Stderr = os.Stderr
fmt.Println("Applying patches...")
if err := applyCmd.Run(); err != nil {
return fmt.Errorf("failed to apply patches: %v", err)
return fmt.Errorf("❌ Failed to apply patches: %v", err)
}
fmt.Println("Patches applied successfully.")
fmt.Println("🎉 Patches applied successfully")
return nil
}

View file

@ -26,52 +26,57 @@ type Config struct {
// Load the SourceForge configuration from a file
func LoadConfig() (*Config, error) {
fmt.Println("🔑 Loading SourceForge configuration...")
file, err := os.Open("sourceforge_config.json")
if err != nil {
return nil, fmt.Errorf("failed to open config file: %v", err)
return nil, fmt.Errorf("failed to open config file: %v", err)
}
defer file.Close()
config := &Config{}
if err := json.NewDecoder(file).Decode(config); err != nil {
return nil, fmt.Errorf("failed to decode config file: %v", err)
return nil, fmt.Errorf("failed to decode config file: %v", err)
}
// Expand tilde manually
fmt.Println("🔍 Expanding SSH key path...")
expandedPath, err := homedir.Expand(config.SFKeyPath)
if err != nil {
return nil, fmt.Errorf("failed expanding key path: %v", err)
return nil, fmt.Errorf("failed expanding key path: %v", err)
}
config.SFKeyPath = filepath.Clean(expandedPath)
// Validate that the key file exists
fmt.Println("🔐 Validating SSH key...")
if _, err := os.Stat(config.SFKeyPath); os.IsNotExist(err) {
return nil, fmt.Errorf("SSH key file not found at path: %s", config.SFKeyPath)
return nil, fmt.Errorf("SSH key file not found at path: %s", config.SFKeyPath)
} else if err != nil {
return nil, fmt.Errorf("error accessing SSH key file: %v", err)
return nil, fmt.Errorf("error accessing SSH key file: %v", err)
}
fmt.Println("✅ Configuration loaded successfully")
return config, nil
}
// CompressDirectory compresses the build directory to a tar.gz file using PAX format for large file support
func CompressDirectory(srcDir, dstFile string) error {
// Create the destination file
fmt.Printf("🗜️ Compressing directory: %s → %s\n", srcDir, dstFile)
fmt.Println("📄 Creating destination file...")
f, err := os.Create(dstFile)
if err != nil {
return fmt.Errorf("could not create file %s: %v", dstFile, err)
return fmt.Errorf("could not create file %s: %v", dstFile, err)
}
defer f.Close()
// Create a new gzip writer
fmt.Println("📦 Initializing gzip writer...")
gw := gzip.NewWriter(f)
defer gw.Close()
// Create a new tar writer with PAX format for large file support
fmt.Println("📦 Initializing tar writer (PAX format)...")
tw := tar.NewWriter(gw)
defer tw.Close()
// Walk through the source directory and add files to the tar archive
fmt.Println("🔍 Walking directory structure...")
err = filepath.Walk(srcDir, func(file string, fi os.FileInfo, err error) error {
if err != nil {
return err
@ -130,46 +135,58 @@ func CompressDirectory(srcDir, dstFile string) error {
})
if err != nil {
return fmt.Errorf("error walking the source directory %s: %v", srcDir, err)
return fmt.Errorf("error walking the source directory %s: %v", srcDir, err)
}
fmt.Println("✅ Compression completed successfully")
return nil
}
// Upload the file to SourceForge, ensuring the local directory structure is created and uploaded
func Upload(config *Config, buildPath, remoteDir string) error {
fmt.Println("📤 Starting upload process...")
// Generate a random hash for the temp directory name
fmt.Println("🔒 Generating random hash for temp directory...")
randomHash, err := generateRandomHash(8) // 8 bytes = 16 hex characters
if err != nil {
return fmt.Errorf("failed to generate random hash: %v", err)
return fmt.Errorf("failed to generate random hash: %v", err)
}
// Create a temporary directory with the random hash appended
fmt.Printf("📂 Creating temporary directory with hash: %s...\n", randomHash)
tmpDir, err := os.MkdirTemp("", "spitfire-upload-"+randomHash)
if err != nil {
return fmt.Errorf("failed to create temporary directory: %v", err)
return fmt.Errorf("failed to create temporary directory: %v", err)
}
// Create the required local directory structure inside the temporary directory
fmt.Printf("📁 Creating local directory structure: %s...\n", remoteDir)
localDir := filepath.Join(tmpDir, remoteDir)
err = os.MkdirAll(localDir, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create local directory structure: %v", err)
return fmt.Errorf("failed to create local directory structure: %v", err)
}
// Move the build file to the local directory structure
fmt.Printf("📦 Copying build file to temp location: %s...\n", filepath.Base(buildPath))
destinationFile := filepath.Join(localDir, filepath.Base(buildPath))
err = copyFile(buildPath, destinationFile)
if err != nil {
return fmt.Errorf("failed to copy file to local directory structure: %v", err)
return fmt.Errorf("failed to copy file to local directory structure: %v", err)
}
// Upload the entire local directory structure to the remote directory
fmt.Printf("Uploading file %s to %s on SourceForge...\n", buildPath, remoteDir)
fmt.Printf("🚀 Uploading %s to SourceForge (%s)...\n", filepath.Base(buildPath), remoteDir)
scpCmd := exec.Command("scp", "-i", config.SFKeyPath, "-r", tmpDir+"/.", fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, "/"))
scpCmd.Stdout = os.Stdout
scpCmd.Stderr = os.Stderr
return scpCmd.Run()
if err := scpCmd.Run(); err != nil {
return fmt.Errorf("❌ upload failed: %v", err)
}
fmt.Println("✅ Upload completed successfully")
return nil
}
// Helper function to generate a random hash
@ -206,7 +223,7 @@ func copyFile(src, dst string) error {
// Download the APPINDEX file from SourceForge
func DownloadAPPINDEX(config *Config, remoteDir string) error {
fmt.Println("Downloading APPINDEX from SourceForge...")
fmt.Println("📥 Downloading APPINDEX from SourceForge...")
// Construct the correct path without double slashes
remoteAPPINDEXPath := filepath.Join(remoteDir, "APPINDEX")
@ -220,23 +237,27 @@ func DownloadAPPINDEX(config *Config, remoteDir string) error {
if err != nil {
// Check if the error is due to the file not existing
if strings.Contains(err.Error(), "No such file or directory") {
fmt.Println("APPINDEX file not found on the server. A new one will be created.")
fmt.Println(" APPINDEX file not found - will create new one")
return nil // Continue without failing if the APPINDEX is missing
}
return fmt.Errorf("failed to download APPINDEX: %v", err) // Fail for other types of errors
return fmt.Errorf("failed to download APPINDEX: %v", err) // Fail for other types of errors
}
fmt.Println("APPINDEX downloaded successfully.")
fmt.Println("APPINDEX downloaded successfully")
return nil
}
// Upload the updated APPINDEX file to SourceForge
func UploadAPPINDEX(config *Config, remoteDir string) error {
fmt.Println("Uploading updated APPINDEX to SourceForge...")
fmt.Println("📤 Uploading updated APPINDEX to SourceForge...")
cmd := exec.Command("scp", "-i", config.SFKeyPath, "./APPINDEX", fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, remoteDir))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
if err := cmd.Run(); err != nil {
return fmt.Errorf("❌ failed to upload APPINDEX: %v", err)
}
fmt.Println("✅ APPINDEX uploaded successfully")
return nil
}
// GetDirectorySize calculates the total size of all files in a directory