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. var errors []error // This is also in build.go. Great! This code is a mess.
func init() { func init() {
flag.StringVar(&target, "t", "", "Target location format: component-arch-release-platform") 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.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(&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(&component, "component", "browser", "🌐 Component name (default: browser)")
flag.StringVar(&arch, "arch", runtime.GOARCH, "Architecture (default: system architecture)") flag.StringVar(&arch, "arch", runtime.GOARCH, "💻 Architecture (default: system architecture)")
flag.StringVar(&release, "release", "nightly", "Release type (default: nightly)") flag.StringVar(&release, "release", "nightly", "🌙 Release type (default: nightly)")
flag.StringVar(&platform, "platform", runtime.GOOS, "Platform (default: system platform)") flag.StringVar(&platform, "platform", runtime.GOOS, "🖥️ Platform (default: system platform)")
flag.BoolVar(&all, "a", false, "Perform all steps (build, clean, update)") flag.BoolVar(&all, "a", false, "🔄 Perform all steps (build, clean, update)")
flag.BoolVar(&buildFlag, "b", false, "Build Spitfire") flag.BoolVar(&buildFlag, "b", false, "🔨 Build Spitfire")
flag.BoolVar(&clean, "clean", false, "Revert uncommitted changes without removing build artifacts") 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(&fullClean, "full-clean", false, "🧼 Perform a full clean by removing all build artifacts (clobber)")
flag.BoolVar(&update, "u", false, "Update Mozilla repository") flag.BoolVar(&update, "u", false, "🔄 Update Mozilla repository")
flag.BoolVar(&prePatch, "pre-patch", false, "Apply pre-build patches") flag.BoolVar(&prePatch, "pre-patch", false, "🔧 Apply pre-build patches")
flag.BoolVar(&postPatch, "post-patch", false, "Apply post-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(&skipPatchUpdate, "skip-patch-update", false, "⏭️ Skip updating the patches repository")
flag.BoolVar(&run, "r", false, "Run the project after build") flag.BoolVar(&run, "r", false, "🚀 Run the project after build")
flag.BoolVar(&upload, "upload", false, "Upload the compressed build file to SourceForge") 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.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(&skipDeps, "skip-deps", false, "⏭️ Skip checking for required system dependencies")
flag.BoolVar(&ignoreErrors, "ignore-errors", false, "Processes all steps even if errors occur") flag.BoolVar(&ignoreErrors, "ignore-errors", false, "⚠️ Processes all steps even if errors occur")
flag.Bool("h", false, "Display help message") flag.Bool("h", false, " Display help message")
} }
func printHelp() { 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>]") fmt.Println("📖 Spitfire Build System Help")
flag.PrintDefaults() fmt.Println("Usage: go run . [options]")
fmt.Println("Example: go run . --upload -c -a") 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) os.Exit(0)
} }
@ -92,162 +134,178 @@ func main() {
printHelp() printHelp()
} }
fmt.Println("🚀 Starting Spitfire build process...")
if !skipDeps { if !skipDeps {
fmt.Println("🔍 Checking system dependencies...")
if err := spitfire.CheckDependencies(); err != nil { 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" { if version == "" && release == "nightly" {
version = time.Now().Format("2006.01.02") version = time.Now().Format("2006.01.02")
fmt.Printf("📅 Using nightly version: %s\n", version)
} }
// Save the initial directory // Save the initial directory
var err error var err error
initialDir, err = os.Getwd() initialDir, err = os.Getwd()
if err != nil { 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 var buildDir string
if all || buildFlag || prePatch || postPatch || clean || update || run { if all || buildFlag || prePatch || postPatch || clean || update || run {
fmt.Println("🏗️ Starting build process...")
buildDir, err = BuildProcess() buildDir, err = BuildProcess()
if err != nil { 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) { } 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 { if compress || upload {
fmt.Println("📦 Packaging and uploading build...")
if err := PackageAndUploadProcess(buildDir); err != nil { if err := PackageAndUploadProcess(buildDir); err != nil {
handleError(fmt.Errorf("package/upload failed: %w", err)) handleError(fmt.Errorf("package/upload failed: %w", err))
} }
} }
spitfire.PrintErrors() spitfire.PrintErrors()
if len(errors) > 0 { 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 { for _, e := range errors {
fmt.Fprintf(os.Stderr, "- %v\n", e) fmt.Fprintf(os.Stderr, " - %v\n", e)
} }
os.Exit(1) os.Exit(1)
} else { } else {
fmt.Println("🎉 All operations completed successfully!")
os.Exit(0) os.Exit(0)
} }
} }
func BuildProcess() (string, error) { func BuildProcess() (string, error) {
fmt.Println("📂 Resolving source path...")
sourcePath, err := spitfire.ResolvePath("./mozilla-release") sourcePath, err := spitfire.ResolvePath("./mozilla-release")
if err != nil { if err != nil {
return "", fmt.Errorf("resolve source path: %w", err) return "", fmt.Errorf("resolve source path: %w", err)
} }
var buildDir string var buildDir string
if all { if all {
fmt.Println("🔄 Running full build process...")
if err := spitfire.DownloadSource(sourcePath, sourceRepo); err != nil { 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 { 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 { 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 { 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 { 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 { 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 { if err := spitfire.Build(sourcePath); err != nil {
return "", fmt.Errorf("build: %w", err) return "", fmt.Errorf("build: %w", err)
} }
buildDir, err = spitfire.ResolveBuildDir(sourcePath) buildDir, err = spitfire.ResolveBuildDir(sourcePath)
if err != nil { 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 { 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 run {
if err := spitfire.RunProject(sourcePath); err != nil { 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 { } else if buildFlag {
fmt.Println("🔨 Running build process...")
if prePatch { if prePatch {
if err := spitfire.ApplyPatches(sourcePath, patchesRepo, "pre-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); err != nil { 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 { 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 { if err := spitfire.Build(sourcePath); err != nil {
return "", fmt.Errorf("build: %w", err) return "", fmt.Errorf("build: %w", err)
} }
buildDir, err = spitfire.ResolveBuildDir(sourcePath) buildDir, err = spitfire.ResolveBuildDir(sourcePath)
if err != nil { if err != nil {
return "", fmt.Errorf("resolve build dir: %w", err) return "", fmt.Errorf("resolve build dir: %w", err)
} }
if postPatch { if postPatch {
if err := spitfire.ApplyPatches(buildDir, patchesRepo, "post-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); 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 run {
if err := spitfire.RunProject(sourcePath); err != nil { 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 { } else if clean {
fmt.Println("🧹 Cleaning build...")
if err := spitfire.CleanBuild(sourcePath, fullClean); err != nil { 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 { } else if update {
fmt.Println("🔄 Updating repository...")
if err := spitfire.DownloadSource(sourcePath, sourceRepo); err != nil { 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 { 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 { } else if prePatch {
fmt.Println("🔧 Applying pre-compile patches...")
if err := spitfire.DownloadSource(sourcePath, sourceRepo); err != nil { 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 { 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 { } else if postPatch {
fmt.Println("🔧 Applying post-compile patches...")
buildDir, err = spitfire.ResolveBuildDir(sourcePath) buildDir, err = spitfire.ResolveBuildDir(sourcePath)
if err != nil { 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 := os.Stat(buildDir); err == nil {
if err := spitfire.ApplyPatches(buildDir, patchesRepo, "post-compile-patches", filepath.Join(sourcePath, "patcher"), skipPatchUpdate); 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 run {
if err := spitfire.RunProject(sourcePath); err != nil { if err := spitfire.RunProject(sourcePath); err != nil {
return "", fmt.Errorf("run project: %w", err) return "", fmt.Errorf("run project: %w", err)
} }
} }
} else if run { } else if run {
fmt.Println("🚀 Running project...")
if err := spitfire.RunProject(sourcePath); err != nil { 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 { func PackageAndUploadProcess(buildDir string) error {
fmt.Println("📦 Starting package and upload process...")
restoreWorkingDirectory() restoreWorkingDirectory()
pathToUse := buildDir pathToUse := buildDir
if upload && uploadPath != "" { if upload && uploadPath != "" {
pathToUse = uploadPath pathToUse = uploadPath
fmt.Printf(" ├─ Using custom upload path: %s\n", pathToUse)
} }
if 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) uncompressedSize, err := spitfire.GetDirectorySize(pathToUse)
if err != nil { 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( outputCompressedFile := filepath.Join(".", fmt.Sprintf(
"%s-%s-%s-%s.tar.gz", "%s-%s-%s-%s.tar.gz",
component, arch, release, platform, component, arch, release, platform,
)) ))
fmt.Printf(" ├─ Output filename: %s\n", outputCompressedFile)
if compress { if compress {
fmt.Println("🗜️ Compressing directory...")
if err := spitfire.CompressDirectory(pathToUse, outputCompressedFile); err != nil { 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) compressedSize, err := spitfire.GetFileSize(outputCompressedFile)
if err != nil { 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) compressionRatio, efficiency := spitfire.CalculateCompressionEfficiency(uncompressedSize, compressedSize)
fmt.Printf("Compression ratio: %.2f:1\n", compressionRatio) fmt.Printf(" ├─ Compression ratio: %.2f:1\n", compressionRatio)
fmt.Printf("Compression efficiency: %.2f%%\n", efficiency) fmt.Printf(" ├─ Compression efficiency: %.2f%%\n", efficiency)
fmt.Printf("Compressed dir: %s\n", pathToUse) fmt.Printf(" └─ Source directory: %s\n", pathToUse)
if !upload { if !upload {
fmt.Println("⏩ Upload skipped (not requested)")
return nil return nil
} }
fmt.Println("🔑 Loading configuration...")
config, err := spitfire.LoadConfig() config, err := spitfire.LoadConfig()
if err != nil { if err != nil {
return fmt.Errorf("load config: %w", err) return fmt.Errorf("load config: %w", err)
} }
if _, err := os.Stat(outputCompressedFile); err != nil { 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) 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 { 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 { 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( if err := spitfire.PackageAPPINDEX(
packageName, packageName,
release, release,
@ -334,24 +402,27 @@ func PackageAndUploadProcess(buildDir string) error {
platform, platform,
uploadDir, uploadDir,
); err != nil { ); 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 { 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 { 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 return nil
} }
func restoreWorkingDirectory() { func restoreWorkingDirectory() {
fmt.Println("↩️ Restoring working directory...")
if err := os.Chdir(initialDir); err != nil { 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" platform, // e.g. "linux"
remoteDir string, // e.g. "nightly/linux/amd64" remoteDir string, // e.g. "nightly/linux/amd64"
) error { ) error {
fmt.Println("📦 Starting APPINDEX packaging...")
// Construct a filename that matches what you compress & upload // Construct a filename that matches what you compress & upload
// Adjust this naming convention to match your compress step exactly! // Adjust this naming convention to match your compress step exactly!
// Example: "spitfire-browser-nightly-amd64-linux.tar.gz" // Example: "spitfire-browser-nightly-amd64-linux.tar.gz"
fileName := fmt.Sprintf("%s-%s-%s-%s.tar.gz", origin, arch, release, platform) 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)`. // Calculate checksum
// For demo, we call `calcChecksum` with a mock package name.
checksum := calcChecksum(fileName) 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: // Remove existing entry based on P, R, A, o, and p:
removeExistingEntry(name, release, arch, origin, platform) removeExistingEntry(name, release, arch, origin, platform)
// Use current Unix timestamp // Use current Unix timestamp
timestamp := time.Now().Unix() timestamp := time.Now().Unix()
fmt.Printf(" ├─ Current timestamp: %d\n", timestamp)
// Open or create the APPINDEX file for appending // Open or create the APPINDEX file for appending
file, err := os.OpenFile("./APPINDEX", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) file, err := os.OpenFile("./APPINDEX", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
log.Fatalf("Failed to open APPINDEX file: %v", err) log.Fatalf("Failed to open APPINDEX file: %v", err)
} }
defer file.Close() defer file.Close()
// Build the SourceForge-based download URL: // Build the SourceForge-based download URL:
// https://downloads.sourceforge.net/project/spitfire-browser/<remoteDir>/<fileName> // https://downloads.sourceforge.net/project/spitfire-browser/<remoteDir>/<fileName>
downloadURL := fmt.Sprintf("https://downloads.sourceforge.net/project/spitfire-browser/%s/%s", 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. // Format the entry.
entry := fmt.Sprintf(` entry := fmt.Sprintf(`
@ -85,11 +89,13 @@ func PackageAPPINDEX(
// Trim leading newline to keep it clean // Trim leading newline to keep it clean
entry = strings.TrimPrefix(entry, "\n") entry = strings.TrimPrefix(entry, "\n")
// Write entry
fmt.Println("✏️ Writing new entry...")
if _, err := file.WriteString(entry + "\n"); err != nil { 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 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 // CleanAppIndex cleans up any orphaned "C:" entries and collapses excessive newlines
func CleanAppIndex() error { func CleanAppIndex() error {
fmt.Println("🧼 Cleaning APPINDEX file...")
// Read file contents // Read file contents
content, err := os.ReadFile("./APPINDEX") content, err := os.ReadFile("./APPINDEX")
if err != nil { 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 // Split the file content into lines
@ -242,11 +250,12 @@ func CleanAppIndex() error {
cleanedContent = strings.ReplaceAll(cleanedContent, "\n\n\n", "\n\n") cleanedContent = strings.ReplaceAll(cleanedContent, "\n\n\n", "\n\n")
// Write the cleaned content back to the file // Write the cleaned content back to the file
fmt.Println("✏️ Writing cleaned content...")
err = os.WriteFile("./APPINDEX", []byte(cleanedContent), 0644) err = os.WriteFile("./APPINDEX", []byte(cleanedContent), 0644)
if err != nil { 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 return nil
} }

View file

@ -6,14 +6,21 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"time" "time"
) )
// Array to store errors // Array to store errors
var errors []string var errors []string
var (
repoOperationsDone bool
repoMutex sync.Mutex
)
// SetGlobalEnv sets the MOZILLABUILD environment variable globally for the user (or system) // SetGlobalEnv sets the MOZILLABUILD environment variable globally for the user (or system)
func SetGlobalEnv(variable, value string, scope string) error { 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" { if runtime.GOOS == "windows" {
var cmd *exec.Cmd var cmd *exec.Cmd
if scope == "user" { if scope == "user" {
@ -21,18 +28,18 @@ func SetGlobalEnv(variable, value string, scope string) error {
} else if scope == "system" { } else if scope == "system" {
cmd = exec.Command("setx", variable, value, "/M") // Set for system (requires admin privileges) cmd = exec.Command("setx", variable, value, "/M") // Set for system (requires admin privileges)
} else { } else {
return fmt.Errorf("unknown scope: %s", scope) return fmt.Errorf("unknown scope: %s", scope)
} }
// Run the command // Run the command
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { 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 return nil
} else { } 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() 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 // Function to resolve paths using absolute path
func ResolvePath(path string) (string, error) { func ResolvePath(path string) (string, error) {
fmt.Printf("📍 Resolving path: %s\n", path)
// Convert Unix-style slashes to the platform's native slashes // Convert Unix-style slashes to the platform's native slashes
path = filepath.FromSlash(path) path = filepath.FromSlash(path)
// Get the absolute path // Get the absolute path
absPath, err := filepath.Abs(path) absPath, err := filepath.Abs(path)
if err != nil { 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 return absPath, nil
} }
// Function to download Mozilla source if not present // Function to download Mozilla source if not present
func DownloadSource(sourcePath, sourceRepo string) error { func DownloadSource(sourcePath, sourceRepo string) error {
fmt.Println("🔍 Checking Mozilla source repository...")
if _, err := os.Stat(sourcePath); os.IsNotExist(err) { 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 { 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 { } else {
fmt.Println("Mozilla source already exists.") fmt.Println(" Mozilla source already exists")
} }
return nil return nil
} }
// Function to discard uncommitted changes // Function to discard uncommitted changes
func DiscardChanges(sourcePath string) error { 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 { 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 return nil
} }
// Function to clean build // Function to clean build
func CleanBuild(sourcePath string, fullClean bool) error { 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 { 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 { if fullClean {
fmt.Println("🧹 Performing full clean (clobber)...")
machCmd := filepath.Join(sourcePath, "mach") machCmd := filepath.Join(sourcePath, "mach")
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
machCmd = filepath.Join(sourcePath, "mach.bat") machCmd = filepath.Join(sourcePath, "mach.bat")
@ -95,117 +116,195 @@ func CleanBuild(sourcePath string, fullClean bool) error {
cmd := exec.Command(machCmd, "clobber") cmd := exec.Command(machCmd, "clobber")
cmd.Dir = sourcePath // Run in the source directory cmd.Dir = sourcePath // Run in the source directory
if err := cmd.Run(); err != nil { 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 return nil
} }
// Function to update Mozilla repository // UpdateRepo updates an existing repository if not already done
func UpdateRepo(sourcePath string) error { func UpdateRepo(sourcePath string) error {
fmt.Println("Updating Mozilla repository...") repoMutex.Lock()
if err := os.Chdir(sourcePath); err != nil { defer repoMutex.Unlock()
return fmt.Errorf("failed to navigate to source directory: %w", err)
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 // Function to update patches
func UpdatePatches(patchesDir, patchesRepo, sourcePath string) { func UpdatePatches(patchesDir, patchesRepo, sourcePath string) {
fmt.Println("Updating patches...") fmt.Println("🔄 Updating patches...")
if _, err := os.Stat(patchesDir); err == nil { 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 { 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 return
} }
fmt.Println("🧹 Cleaning patches directory...")
if err := runCommand("git", "clean", "-xdf"); err != nil { 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") _ = runCommand("git", "stash", "push", "--include-untracked")
fmt.Println("📥 Fetching updates...")
if err := runCommand("git", "fetch"); err != nil { 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 runCommand("git", "show-ref", "--verify", "--quiet", "refs/heads/main") == nil {
if err := runCommand("git", "rebase", "origin/main"); err != 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 { } else if runCommand("git", "show-ref", "--verify", "--quiet", "refs/heads/master") == nil {
if err := runCommand("git", "rebase", "origin/master"); err != 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 { } 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 return
} }
if runCommand("git", "stash", "list") == nil { if runCommand("git", "stash", "list") == nil {
fmt.Println("↩️ Restoring stashed changes...")
_ = runCommand("git", "stash", "pop") _ = runCommand("git", "stash", "pop")
} else { } else {
fmt.Println("No stash entries found, skipping pop.") fmt.Println(" No stash entries found - skipping pop")
} }
} else { } else {
fmt.Println("Patches directory does not exist. Cloning repository...") fmt.Println("📥 Cloning patches repository...")
if err := runCommand("git", "clone", patchesRepo, patchesDir); err != nil { 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 to source directory...")
fmt.Println("Copying files from patches directory to Firefox source directory...")
if runtime.GOOS == "windows" || !isMsys2() { 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 { 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 { } else {
// Use rsync for Unix-like systems fmt.Println("🐧 Using rsync for Unix...")
if err := runCommand("rsync", "-av", "--exclude=.git", patchesDir+"/", sourcePath+"/"); err != nil { 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 // Function to configure Spitfire
func Configure(sourcePath string) error { func Configure(sourcePath string) error {
fmt.Println("Configuring Spitfire...") fmt.Println("⚙️ Configuring Spitfire...")
if err := os.Chdir(sourcePath); err != nil { 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" machCmd := "./mach"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
machCmd = ".\\mach" machCmd = ".\\mach"
} }
fmt.Println("🔧 Running mach configure...")
return runCommand(machCmd, "configure") return runCommand(machCmd, "configure")
} }
// Function to build Spitfire // Function to build Spitfire
func Build(sourcePath string) error { func Build(sourcePath string) error {
fmt.Println("Building Spitfire...") fmt.Println("🔨 Building Spitfire...")
if err := os.Chdir(sourcePath); err != nil { 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" machCmd := "./mach"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
machCmd = ".\\mach" machCmd = ".\\mach"
} }
fmt.Println("🏗️ Running mach build...")
return runCommand(machCmd, "build") return runCommand(machCmd, "build")
} }
// Function to run the project after build // Function to run the project after build
func RunProject(sourcePath string) error { func RunProject(sourcePath string) error {
fmt.Println("Running the project...") fmt.Println("🚀 Running the project...")
// Resolve the build directory // Resolve the build directory
fmt.Println("🔍 Resolving build directory...")
buildDir, err := ResolveBuildDir(sourcePath) buildDir, err := ResolveBuildDir(sourcePath)
if err != nil { 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 // List of possible binaries
binaries := []string{"firefox", "Firefox", "spitfire", "Spitfire"} binaries := []string{"firefox", "Firefox", "spitfire", "Spitfire"}
@ -213,62 +312,66 @@ func RunProject(sourcePath string) error {
var binaryPath string var binaryPath string
// Check for the existence of binaries // Check for the existence of binaries
fmt.Println("🔎 Searching for executable binary...")
for _, binary := range binaries { for _, binary := range binaries {
binaryPath = filepath.Join(buildDir, binary) binaryPath = filepath.Join(buildDir, binary)
if _, err := os.Stat(binaryPath); err == nil { if _, err := os.Stat(binaryPath); err == nil {
fmt.Printf("Found binary: %s\n", binaryPath) fmt.Printf("Found binary: %s\n", binaryPath)
break break
} }
binaryPath = "" binaryPath = ""
} }
if 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 // Create a unique profile directory for each run
profilePath := filepath.Join(buildDir, fmt.Sprintf("profile_%d", time.Now().UnixNano())) 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 { 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 // 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 := exec.Command(binaryPath, "-no-remote", "-profile", profilePath)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin 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() runErr := cmd.Run()
// Delete the profile directory after execution // Clean up profile directory
fmt.Printf("Deleting profile directory: %s\n", profilePath) fmt.Println("🧹 Cleaning up temporary profile...")
if removeErr := os.RemoveAll(profilePath); removeErr != nil { 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 { } else {
fmt.Println("Profile directory deleted successfully.") fmt.Println("✅ Profile directory cleaned successfully")
} }
if runErr != nil { 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 return nil
} }
// ResolveBuildDir detects the build directory dynamically // ResolveBuildDir detects the build directory dynamically
func ResolveBuildDir(sourcePath string) (string, error) { func ResolveBuildDir(sourcePath string) (string, error) {
fmt.Println("🔍 Detecting build directory...")
// The expected build directory pattern // The expected build directory pattern
globPattern := filepath.Join(sourcePath, "obj-*") globPattern := filepath.Join(sourcePath, "obj-*")
// Find matching directories // Find matching directories
matches, err := filepath.Glob(globPattern) matches, err := filepath.Glob(globPattern)
if err != nil { 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 { 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") full := filepath.Join(matches[0], "dist", "bin")
@ -279,9 +382,9 @@ func ResolveBuildDir(sourcePath string) (string, error) {
// Function to print collected errors // Function to print collected errors
func PrintErrors() { func PrintErrors() {
if len(errors) > 0 { if len(errors) > 0 {
fmt.Println("The following errors occurred during execution:") fmt.Println("The following errors occurred during execution:")
for _, err := range errors { for _, err := range errors {
fmt.Printf("- %s\n", err) fmt.Printf(" - %s\n", err)
} }
} }
} }

View file

@ -7,16 +7,29 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
)
var (
reportedCommands = make(map[string]bool)
reportMutex sync.Mutex
) )
// CheckDependencies ensures that all required tools and dependencies for the build are installed. // CheckDependencies ensures that all required tools and dependencies for the build are installed.
func CheckDependencies() error { func CheckDependencies() error {
// Just to make sure, reset reported commands at start
reportMutex.Lock()
reportedCommands = make(map[string]bool)
reportMutex.Unlock()
// Common dependencies // Common dependencies
dependencies := map[string]string{ dependencies := map[string]string{
"git": "https://git-scm.com/downloads", // Git "git": "https://git-scm.com/downloads", // Git
"python": "https://www.python.org/downloads/", // Python "python": "https://www.python.org/downloads/", // Python
"pip3": "https://pip.pypa.io/en/stable/installing/", // Pip3 "pip3": "https://pip.pypa.io/en/stable/installing/", // Pip3
"hg": "https://www.mercurial-scm.org/", // Mercurial (hg) "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 // 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 // Check for `mach` script in the source directory
machPath := filepath.Join("mozilla-release", "mach") 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 !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 // Report missing tools if any
@ -59,16 +106,42 @@ func CheckDependencies() error {
return nil 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. // isCommandAvailable checks if a command/tool is available on the system.
func isCommandAvailable(command string) bool { func isCommandAvailable(command string) bool {
path, err := exec.LookPath(command) path, err := exec.LookPath(command)
if err != nil { if err != nil {
return false 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 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. // fileExists checks if a file exists at the given path.
func fileExists(path string) bool { func fileExists(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)

View file

@ -10,62 +10,68 @@ import (
// ApplyPatches handles cloning, updating, and applying patches // ApplyPatches handles cloning, updating, and applying patches
func ApplyPatches(sourcePath string, patchesRepo string, patchesPath string, patchesCloneDir string, skipUpdateRepo bool) error { func ApplyPatches(sourcePath string, patchesRepo string, patchesPath string, patchesCloneDir string, skipUpdateRepo bool) error {
// Define the full patches path // 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) // Print configuration with consistent style
fmt.Printf("Patches Clone Directory: %s\n", patchesCloneDir) fmt.Printf("🔧 Config:\n")
fmt.Printf("Patches Repository URL: %s\n", patchesRepo) fmt.Printf(" ├─ Source Path: %s\n", sourcePath)
fmt.Printf("Patches Path: %s\n", fullPatchesPath) 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 // Check if the patches directory already exists
if _, err := os.Stat(patchesCloneDir); os.IsNotExist(err) { if _, err := os.Stat(patchesCloneDir); os.IsNotExist(err) {
// Clone the patches repository // 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 := exec.Command("git", "clone", patchesRepo, patchesCloneDir)
cmdClone.Stdout = os.Stdout cmdClone.Stdout = os.Stdout
cmdClone.Stderr = os.Stderr cmdClone.Stderr = os.Stderr
if err := cmdClone.Run(); err != nil { 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 { } else {
if !skipUpdateRepo { if !skipUpdateRepo {
// If the directory exists, fetch and pull the latest changes fmt.Println("🔄 Updating patches repository...")
fmt.Println("Patches directory already exists. Fetching latest changes.")
// Fetch updates
cmdFetch := exec.Command("git", "fetch", "--all") cmdFetch := exec.Command("git", "fetch", "--all")
cmdFetch.Dir = patchesCloneDir cmdFetch.Dir = patchesCloneDir
cmdFetch.Stdout = os.Stdout cmdFetch.Stdout = os.Stdout
cmdFetch.Stderr = os.Stderr cmdFetch.Stderr = os.Stderr
if err := cmdFetch.Run(); err != nil { 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 := exec.Command("git", "reset", "--hard", "origin/main")
cmdReset.Dir = patchesCloneDir cmdReset.Dir = patchesCloneDir
cmdReset.Stdout = os.Stdout cmdReset.Stdout = os.Stdout
cmdReset.Stderr = os.Stderr cmdReset.Stderr = os.Stderr
if err := cmdReset.Run(); err != nil { 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 // Verify the patches directory exists
if _, err := os.Stat(fullPatchesPath); os.IsNotExist(err) { 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` // Apply the patches using `run.sh`
applyCmd := exec.Command("go", "run", ".", "--path", sourcePath, "--patches", fullPatchesPath) 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.Stdout = os.Stdout
applyCmd.Stderr = os.Stderr applyCmd.Stderr = os.Stderr
fmt.Println("Applying patches...")
if err := applyCmd.Run(); err != nil { 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 return nil
} }

View file

@ -26,52 +26,57 @@ type Config struct {
// Load the SourceForge configuration from a file // Load the SourceForge configuration from a file
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
fmt.Println("🔑 Loading SourceForge configuration...")
file, err := os.Open("sourceforge_config.json") file, err := os.Open("sourceforge_config.json")
if err != nil { 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() defer file.Close()
config := &Config{} config := &Config{}
if err := json.NewDecoder(file).Decode(config); err != nil { 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) expandedPath, err := homedir.Expand(config.SFKeyPath)
if err != nil { 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) 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) { 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 { } 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 return config, nil
} }
// CompressDirectory compresses the build directory to a tar.gz file using PAX format for large file support // CompressDirectory compresses the build directory to a tar.gz file using PAX format for large file support
func CompressDirectory(srcDir, dstFile string) error { 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) f, err := os.Create(dstFile)
if err != nil { 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() defer f.Close()
// Create a new gzip writer fmt.Println("📦 Initializing gzip writer...")
gw := gzip.NewWriter(f) gw := gzip.NewWriter(f)
defer gw.Close() 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) tw := tar.NewWriter(gw)
defer tw.Close() 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 { err = filepath.Walk(srcDir, func(file string, fi os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@ -130,46 +135,58 @@ func CompressDirectory(srcDir, dstFile string) error {
}) })
if err != nil { 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 return nil
} }
// Upload the file to SourceForge, ensuring the local directory structure is created and uploaded // Upload the file to SourceForge, ensuring the local directory structure is created and uploaded
func Upload(config *Config, buildPath, remoteDir string) error { func Upload(config *Config, buildPath, remoteDir string) error {
fmt.Println("📤 Starting upload process...")
// Generate a random hash for the temp directory name // 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 randomHash, err := generateRandomHash(8) // 8 bytes = 16 hex characters
if err != nil { 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 // 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) tmpDir, err := os.MkdirTemp("", "spitfire-upload-"+randomHash)
if err != nil { 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 // Create the required local directory structure inside the temporary directory
fmt.Printf("📁 Creating local directory structure: %s...\n", remoteDir)
localDir := filepath.Join(tmpDir, remoteDir) localDir := filepath.Join(tmpDir, remoteDir)
err = os.MkdirAll(localDir, os.ModePerm) err = os.MkdirAll(localDir, os.ModePerm)
if err != nil { 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 // 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)) destinationFile := filepath.Join(localDir, filepath.Base(buildPath))
err = copyFile(buildPath, destinationFile) err = copyFile(buildPath, destinationFile)
if err != nil { 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 // 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 := exec.Command("scp", "-i", config.SFKeyPath, "-r", tmpDir+"/.", fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, "/"))
scpCmd.Stdout = os.Stdout scpCmd.Stdout = os.Stdout
scpCmd.Stderr = os.Stderr 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 // Helper function to generate a random hash
@ -206,7 +223,7 @@ func copyFile(src, dst string) error {
// Download the APPINDEX file from SourceForge // Download the APPINDEX file from SourceForge
func DownloadAPPINDEX(config *Config, remoteDir string) error { 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 // Construct the correct path without double slashes
remoteAPPINDEXPath := filepath.Join(remoteDir, "APPINDEX") remoteAPPINDEXPath := filepath.Join(remoteDir, "APPINDEX")
@ -220,23 +237,27 @@ func DownloadAPPINDEX(config *Config, remoteDir string) error {
if err != nil { if err != nil {
// Check if the error is due to the file not existing // Check if the error is due to the file not existing
if strings.Contains(err.Error(), "No such file or directory") { 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 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 return nil
} }
// Upload the updated APPINDEX file to SourceForge // Upload the updated APPINDEX file to SourceForge
func UploadAPPINDEX(config *Config, remoteDir string) error { 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 := exec.Command("scp", "-i", config.SFKeyPath, "./APPINDEX", fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, remoteDir))
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr 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 // GetDirectorySize calculates the total size of all files in a directory