changed to golang
This commit is contained in:
parent
f904f731f3
commit
89687eecd6
12 changed files with 867 additions and 484 deletions
spitfire
181
spitfire/appindex.go
Normal file
181
spitfire/appindex.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
package spitfire
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Package the APPINDEX update process
|
||||
func PackageAPPINDEX(name, release, version, arch, size, installedSize, description, url, license, origin, maintainer, dependencies, platform string) error {
|
||||
// Mock package file name
|
||||
pkgFile := fmt.Sprintf("%s-%s", name, version)
|
||||
|
||||
// Calculate checksums
|
||||
checksum := calcChecksum(pkgFile)
|
||||
contentChecksum := calcChecksum(pkgFile)
|
||||
|
||||
// Timestamp
|
||||
timestamp := time.Now().Unix()
|
||||
|
||||
// Remove existing entry based on P, R, A, and o fields
|
||||
removeExistingEntry(name, release, arch, origin)
|
||||
|
||||
// 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)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write the new entry
|
||||
entry := fmt.Sprintf(`
|
||||
C:%s
|
||||
P:%s
|
||||
R:%s
|
||||
V:%s
|
||||
A:%s
|
||||
S:%s
|
||||
I:%s
|
||||
T:%s
|
||||
U:%s
|
||||
L:%s
|
||||
o:%s
|
||||
m:%s
|
||||
t:%d
|
||||
c:%s
|
||||
D:%s
|
||||
p:%s
|
||||
q:
|
||||
Z:%s
|
||||
|
||||
`, checksum, name, release, version, arch, size, installedSize, description, url, license, origin, maintainer, timestamp, contentChecksum, dependencies, platform, checksum)
|
||||
|
||||
if _, err := file.WriteString(entry); err != nil {
|
||||
log.Fatalf("Failed to write to APPINDEX file: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("APPINDEX has been updated successfully.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcChecksum calculates a checksum (SHA-1) for a given input string
|
||||
func calcChecksum(input string) string {
|
||||
h := sha1.New()
|
||||
_, _ = io.WriteString(h, input)
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// removeExistingEntry removes an existing entry from APPINDEX based on P, R, A, and o fields
|
||||
func removeExistingEntry(name, release, arch, origin string) {
|
||||
// Read file contents
|
||||
content, err := os.ReadFile("./APPINDEX")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return // If file does not exist, no need to remove anything
|
||||
}
|
||||
log.Fatalf("Failed to read APPINDEX: %v", err)
|
||||
}
|
||||
|
||||
// Remove lines matching the package with the same P, R, A, and o fields
|
||||
lines := strings.Split(string(content), "\n")
|
||||
var newLines []string
|
||||
remove := false
|
||||
for _, line := range lines {
|
||||
// Detect start of an entry by matching on P, R, A, and o
|
||||
if strings.HasPrefix(line, "P:"+name) {
|
||||
remove = true
|
||||
}
|
||||
if remove && strings.HasPrefix(line, "R:"+release) {
|
||||
remove = true
|
||||
}
|
||||
if remove && strings.HasPrefix(line, "A:"+arch) {
|
||||
remove = true
|
||||
}
|
||||
if remove && strings.HasPrefix(line, "o:"+origin) {
|
||||
remove = true
|
||||
}
|
||||
|
||||
// Stop removal at the end of an entry
|
||||
if remove && line == "" {
|
||||
remove = false
|
||||
continue // Skip the line
|
||||
}
|
||||
|
||||
// Append lines that are not part of the matching entry
|
||||
if !remove {
|
||||
newLines = append(newLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the updated contents back to the file
|
||||
err = os.WriteFile("./APPINDEX", []byte(strings.Join(newLines, "\n")), 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to update APPINDEX: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CleanAppIndex cleans up any orphaned "C:" entries and collapses excessive newlines
|
||||
func CleanAppIndex() error {
|
||||
// Read file contents
|
||||
content, err := os.ReadFile("./APPINDEX")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read APPINDEX: %v", err)
|
||||
}
|
||||
|
||||
// Split the file content into lines
|
||||
lines := strings.Split(string(content), "\n")
|
||||
var newLines []string
|
||||
var currentEntry []string
|
||||
inEntry := false
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// Start of an entry when we encounter a checksum
|
||||
if strings.HasPrefix(line, "C:") {
|
||||
// If we already have a valid entry, add it to newLines
|
||||
if inEntry && len(currentEntry) > 1 {
|
||||
newLines = append(newLines, currentEntry...)
|
||||
}
|
||||
currentEntry = []string{line}
|
||||
inEntry = true
|
||||
} else if inEntry && line == "" {
|
||||
// End of an entry
|
||||
if len(currentEntry) > 1 {
|
||||
newLines = append(newLines, currentEntry...)
|
||||
newLines = append(newLines, "") // Add a blank line to separate entries
|
||||
}
|
||||
currentEntry = nil
|
||||
inEntry = false
|
||||
} else if inEntry {
|
||||
// Continue adding lines to the current entry
|
||||
currentEntry = append(currentEntry, line)
|
||||
} else if line != "" {
|
||||
// Add non-entry lines (for extra safety)
|
||||
newLines = append(newLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
// In case the last entry was valid
|
||||
if inEntry && len(currentEntry) > 1 {
|
||||
newLines = append(newLines, currentEntry...)
|
||||
}
|
||||
|
||||
// Collapse consecutive blank lines
|
||||
cleanedContent := strings.Join(newLines, "\n")
|
||||
cleanedContent = strings.ReplaceAll(cleanedContent, "\n\n\n", "\n\n")
|
||||
|
||||
// Write the cleaned content back to the file
|
||||
err = os.WriteFile("./APPINDEX", []byte(cleanedContent), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write cleaned APPINDEX: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("APPINDEX cleaned successfully.")
|
||||
return nil
|
||||
}
|
166
spitfire/build.go
Normal file
166
spitfire/build.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package spitfire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Array to store errors
|
||||
var errors []string
|
||||
|
||||
// Run an external command like scp or rsync
|
||||
func runCommand(command string, args ...string) error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Function to resolve paths using absolute path
|
||||
func ResolvePath(path string) (string, error) {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve path: %s", path)
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
// Function to download Mozilla source if not present
|
||||
func DownloadSource(sourcePath string, sourceRepo string) {
|
||||
if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
|
||||
fmt.Println("Mozilla source not found. Cloning from repository...")
|
||||
if err := runCommand("hg", "clone", sourceRepo, sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to clone Mozilla repository.")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Mozilla source already exists.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to discard uncommitted changes
|
||||
func DiscardChanges(sourcePath string) {
|
||||
fmt.Println("Discarding uncommitted changes...")
|
||||
if err := runCommand("hg", "revert", "--all", "--no-backup", "-R", sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to revert changes in Mozilla repository.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to clean build
|
||||
func CleanBuild(sourcePath string) {
|
||||
fmt.Println("Cleaning build...")
|
||||
if err := os.Chdir(sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to navigate to source directory.")
|
||||
return
|
||||
}
|
||||
if err := runCommand("hg", "revert", "--all", "--no-backup"); err != nil {
|
||||
errors = append(errors, "Failed to revert changes in Mozilla repository.")
|
||||
}
|
||||
if err := runCommand("./mach", "clobber"); err != nil {
|
||||
errors = append(errors, "Failed to clean build.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update Mozilla repository
|
||||
func UpdateRepo(sourcePath string) {
|
||||
fmt.Println("Updating Mozilla repository...")
|
||||
if err := os.Chdir(sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to navigate to source directory.")
|
||||
return
|
||||
}
|
||||
if err := runCommand("hg", "pull", "-u"); err != nil {
|
||||
errors = append(errors, "Failed to update Mozilla repository.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update patches
|
||||
func UpdatePatches(patchesDir, patchesRepo, sourcePath string) {
|
||||
fmt.Println("Updating patches...")
|
||||
if _, err := os.Stat(patchesDir); err == nil {
|
||||
fmt.Println("Patches directory already exists. Cleaning and pulling updates...")
|
||||
if err := os.Chdir(patchesDir); err != nil {
|
||||
errors = append(errors, "Failed to navigate to patches directory.")
|
||||
return
|
||||
}
|
||||
if err := runCommand("git", "clean", "-xdf"); err != nil {
|
||||
errors = append(errors, "Failed to clean patches directory.")
|
||||
}
|
||||
_ = runCommand("git", "stash", "push", "--include-untracked")
|
||||
if err := runCommand("git", "fetch"); err != nil {
|
||||
errors = append(errors, "Failed to fetch updates from patches repository.")
|
||||
}
|
||||
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.")
|
||||
}
|
||||
} 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.")
|
||||
}
|
||||
} else {
|
||||
errors = append(errors, "No valid branch (main or master) found in patches repository.")
|
||||
return
|
||||
}
|
||||
if runCommand("git", "stash", "list") == nil {
|
||||
_ = runCommand("git", "stash", "pop")
|
||||
} else {
|
||||
fmt.Println("No stash entries found, skipping pop.")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Patches directory does not exist. Cloning repository...")
|
||||
if err := runCommand("git", "clone", patchesRepo, patchesDir); err != nil {
|
||||
errors = append(errors, "Failed to clone patches repository.")
|
||||
}
|
||||
}
|
||||
fmt.Println("Copying files from patches directory to Firefox source directory...")
|
||||
if err := runCommand("rsync", "-av", "--exclude=.git", patchesDir+"/", sourcePath+"/"); err != nil {
|
||||
errors = append(errors, "Failed to copy files.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to configure Spitfire
|
||||
func Configure(sourcePath string) {
|
||||
fmt.Println("Configuring Spitfire...")
|
||||
if err := os.Chdir(sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to navigate to source directory.")
|
||||
return
|
||||
}
|
||||
if err := runCommand("./mach", "configure"); err != nil {
|
||||
errors = append(errors, "Configuration failed.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to build Spitfire
|
||||
func Build(sourcePath string) {
|
||||
fmt.Println("Building Spitfire...")
|
||||
if err := os.Chdir(sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to navigate to source directory.")
|
||||
return
|
||||
}
|
||||
if err := runCommand("./mach", "build"); err != nil {
|
||||
errors = append(errors, "Build failed.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to run the project after build
|
||||
func RunProject(sourcePath string) {
|
||||
fmt.Println("Running the project...")
|
||||
if err := os.Chdir(sourcePath); err != nil {
|
||||
errors = append(errors, "Failed to navigate to source directory.")
|
||||
return
|
||||
}
|
||||
if err := runCommand("./mach", "run"); err != nil {
|
||||
errors = append(errors, "Failed to run the project.")
|
||||
}
|
||||
}
|
||||
|
||||
// Function to print collected errors
|
||||
func PrintErrors() {
|
||||
if len(errors) > 0 {
|
||||
fmt.Println("The following errors occurred during execution:")
|
||||
for _, err := range errors {
|
||||
fmt.Printf("- %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
246
spitfire/upload.go
Normal file
246
spitfire/upload.go
Normal file
|
@ -0,0 +1,246 @@
|
|||
package spitfire
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"crypto/rand"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// Config struct to hold SourceForge configurations
|
||||
type Config struct {
|
||||
SFKeyPath string
|
||||
SFUser string
|
||||
SFHost string
|
||||
SFProject string
|
||||
}
|
||||
|
||||
// Load the SourceForge configuration from a file
|
||||
func LoadConfig() (*Config, error) {
|
||||
file, err := os.Open("sourceforge_config.json") // Assuming a JSON config file
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := &Config{}
|
||||
if err := json.NewDecoder(file).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
f, err := os.Create(dstFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create file %s: %v", dstFile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Create a new gzip writer
|
||||
gw := gzip.NewWriter(f)
|
||||
defer gw.Close()
|
||||
|
||||
// Create a new tar writer with PAX format for large file support
|
||||
tw := tar.NewWriter(gw)
|
||||
defer tw.Close()
|
||||
|
||||
// Walk through the source directory and add files to the tar archive
|
||||
err = filepath.Walk(srcDir, func(file string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create tar header using PAX format
|
||||
header, err := tar.FileInfoHeader(fi, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the correct header name, preserving the relative directory structure
|
||||
relPath, err := filepath.Rel(srcDir, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = relPath
|
||||
|
||||
// Explicitly set the type flag for directories
|
||||
if fi.IsDir() {
|
||||
header.Typeflag = tar.TypeDir
|
||||
} else if fi.Mode()&os.ModeSymlink != 0 {
|
||||
// Handle symlinks
|
||||
linkTarget, err := os.Readlink(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Linkname = linkTarget
|
||||
}
|
||||
|
||||
// Write the header to the tarball
|
||||
if err := tw.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If it's a directory or symlink, skip writing its contents
|
||||
if fi.IsDir() || fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open the file for reading
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Copy the file content to the tar writer
|
||||
if _, err := io.Copy(tw, f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking the source directory %s: %v", srcDir, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload the file to SourceForge, ensuring the local directory structure is created and uploaded
|
||||
func Upload(config *Config, buildPath, remoteDir string) error {
|
||||
// Generate a random hash for the temp directory name
|
||||
randomHash, err := generateRandomHash(8) // 8 bytes = 16 hex characters
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate random hash: %v", err)
|
||||
}
|
||||
|
||||
// Create a temporary directory with the random hash appended
|
||||
tmpDir, err := os.MkdirTemp("", "spitfire-upload-"+randomHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temporary directory: %v", err)
|
||||
}
|
||||
|
||||
// Create the required local directory structure inside the temporary directory
|
||||
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)
|
||||
}
|
||||
|
||||
// Move the build file to the local directory structure
|
||||
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)
|
||||
}
|
||||
|
||||
// Upload the entire local directory structure to the remote directory
|
||||
fmt.Printf("Uploading file %s to %s on SourceForge...\n", 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()
|
||||
}
|
||||
|
||||
// Helper function to generate a random hash
|
||||
func generateRandomHash(length int) (string, error) {
|
||||
bytes := make([]byte, length)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// Helper function to copy a file from src to dst
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return destFile.Sync() // Ensure all writes to the file are flushed
|
||||
}
|
||||
|
||||
// Download the APPINDEX file from SourceForge
|
||||
func DownloadAPPINDEX(config *Config, remoteDir string) error {
|
||||
fmt.Println("Downloading APPINDEX from SourceForge...")
|
||||
|
||||
// Construct the correct path without double slashes
|
||||
remoteAPPINDEXPath := filepath.Join(remoteDir, "APPINDEX")
|
||||
|
||||
// Run the SCP command to download the APPINDEX file
|
||||
cmd := exec.Command("scp", "-i", config.SFKeyPath, fmt.Sprintf("%s@%s:%s", config.SFUser, config.SFHost, remoteAPPINDEXPath), "./APPINDEX")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err := cmd.Run()
|
||||
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.")
|
||||
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
|
||||
}
|
||||
|
||||
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...")
|
||||
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()
|
||||
}
|
||||
|
||||
// GetDirectorySize calculates the total size of all files in a directory
|
||||
func GetDirectorySize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
size += info.Size()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// GetFileSize returns the size of a file in bytes
|
||||
func GetFileSize(filePath string) (int64, error) {
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fileInfo.Size(), nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue