First proof-of-concept version

This commit is contained in:
Sebastiaan de Schaetzen 2025-04-25 10:55:12 +02:00
parent 7442746e59
commit 7c9597c938
6 changed files with 249 additions and 34 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/.idea
/venv /venv
/.env /.env
/work /work

View File

@ -1,24 +1,16 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings"
) )
func cloneRepository(repo Repository, workDir string) error {
cmd := exec.Command("git", "clone", repo.CloneURL, filepath.Join(workDir, repo.Name))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to clone %s: %w", repo.Name, err)
}
return nil
}
func main() { func main() {
token := os.Getenv("TOKEN") token := os.Getenv("TOKEN")
if token == "" { if token == "" {
@ -47,30 +39,193 @@ func main() {
log.Fatalf("Failed to get repositories: %v", err) log.Fatalf("Failed to get repositories: %v", err)
} }
success := true failedRepos := make([]string, 0)
for _, repo := range repos { for _, repo := range repos {
if repo.Template || strings.Contains(repo.Name, "skip-autobuild") {
// Skip template repositories
continue
}
wasRepoProcessed := processRepo(&client, workDir, repo)
if !wasRepoProcessed {
failedRepos = append(failedRepos, repo.Name)
}
}
if len(failedRepos) > 0 {
log.Println("The following repos failed to process:")
for _, repo := range failedRepos {
log.Printf(" - %s", repo)
}
os.Exit(1)
}
}
func processRepo(client *GiteaClient, workDir string, repo Repository) (success bool) {
log.Printf("Checking %s", repo.FullName) log.Printf("Checking %s", repo.FullName)
hasFile, err := client.hasPKGBUILD(repo) hasFile, err := client.hasPKGBUILD(repo)
if err != nil { if err != nil {
log.Printf("Error checking for PKGBUILD: %v", err) log.Printf("Error checking for PKGBUILD: %v", err)
success = false return false
continue
} }
if !hasFile { if !hasFile {
log.Println("PKGBUILD not found, skipping") log.Println("PKGBUILD not found, skipping")
continue return true
} }
if err := cloneRepository(repo, workDir); err != nil { repoPath, err := cloneRepository(client.Token, repo, workDir)
success = false if err != nil {
log.Printf("Error cloning repository: %v", err) log.Printf("Error cloning repository: %v", err)
return false
} }
// Run pre-run script (if it exists)
requirePush := false
defer func() {
if requirePush {
err = pushRepository(repoPath)
if err != nil {
log.Printf("Error pushing repository: %v", err)
success = false
}
}
}()
preRunScript := filepath.Join(repoPath, "pre-run.sh")
if _, err := os.Stat(preRunScript); !os.IsNotExist(err) {
cmd := exec.Command("/bin/sh", "./pre-run.sh")
cmd.Dir = workDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Printf("Error running pre-run script: %v", err)
return false
}
requirePush = true
} }
if !success { // Check if the repository is up-to-date
os.Exit(1) upToDate, err := checkUpToDate(repoPath)
if err != nil {
log.Printf("Error checking if up to date: %v", err)
return false
} }
if upToDate {
log.Printf("%s is up-to-date, skipping", repo.FullName)
return true
}
log.Printf("%s requires bumping, running script", repo.FullName)
updated, err := bumpRepository(repoPath)
if err != nil {
log.Printf("Error bumping repository: %v", err)
return false
}
if updated {
requirePush = true
}
return true
}
func cloneRepository(token string, repo Repository, workDir string) (string, error) {
targetPath := filepath.Join(workDir, repo.Name)
cmd := exec.Command("git", "clone", fmt.Sprintf("https://seeseemelk:%s@gitea.seeseepuff.be/archlinux/%s", token, repo.Name), targetPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("failed to clone %s: %w", repo.Name, err)
}
return targetPath, nil
}
func checkUpToDate(repoPath string) (bool, error) {
log.Println("Checking if up-to-date")
cmd := exec.Command("/bin/sh", "./check-up-to-date.sh", repoPath)
output, err := cmd.CombinedOutput()
log.Println("Command output: ", string(output))
if err != nil {
return false, fmt.Errorf("failed to inspect %s: %w", repoPath, err)
}
if strings.Index(string(output), "UP-TO-DATE") >= 0 {
return true, nil
} else if strings.Index(string(output), "OUT-OF-DATE") >= 0 {
return false, nil
}
return false, errors.New("error parsing command output")
}
/*
Finds the line that says "pkgrel=NUMBER" in the repository's PKGBUILD file,
and increments the number by 1.
*/
func bumpRepository(repoPath string) (bool, error) {
pkgbuildPath := filepath.Join(repoPath, "PKGBUILD")
// Read the file
content, err := os.ReadFile(pkgbuildPath)
if err != nil {
log.Fatalf("failed to read %s: %v", pkgbuildPath, err)
}
lines := strings.Split(string(content), "\n")
updated := false
// Iterate through lines to find and update pkgrel
for i, line := range lines {
if strings.HasPrefix(line, "pkgrel=") {
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
// Parse and increment the pkgrel value
pkgrel, err := strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
log.Fatalf("Failed to parse pkgrel value: %v", err)
}
lines[i] = fmt.Sprintf("pkgrel=%d", pkgrel+1)
updated = true
break
}
}
}
if !updated {
return false, fmt.Errorf("pkgrel line not found in PKGBUILD file")
}
// Write the updated content back to the PKGBUILD file
err = os.WriteFile(pkgbuildPath, []byte(strings.Join(lines, "\n")), 0644)
if err != nil {
return false, fmt.Errorf("error writing updated PKGBUILD file: %w", err)
}
log.Println("PKGBUILD updated, creating commit")
// Create commit
cmd := exec.Command("git", "commit", "PKGBUILD", "-m", "Bump pkgrel")
cmd.Dir = repoPath
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return false, fmt.Errorf("failed to commit changes: %w", err)
}
return true, nil
}
func pushRepository(repoPath string) error {
// Push commit
log.Println("Pushing commit")
cmd := exec.Command("git", "push")
cmd.Dir = repoPath
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("failed to push changes: %w", err)
}
log.Println("Successfully bumped pkgrel in PKGBUILD")
return nil
} }

52
check-up-to-date.sh Normal file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# Exit on any error
set -e
cd $1
# Source the PKGBUILD in a subshell to avoid polluting the global environment
(
source ./PKGBUILD
# Convert pkgname to array in case it's a single string
pkgnames=("${pkgname[@]}")
for pkg in "${pkgnames[@]}"; do
echo "Checking package: $pkg"
if pacman -Si "$pkg" &>/dev/null; then
echo "Package '$pkg' exists in a repository."
# Get the package build date
pkg_build_date=$(date -d "$(pacman -Si "$pkg" | grep 'Build Date' | cut -d: -f2-)" +%s)
all_deps=("${depends[@]}" "${makedepends[@]}" "${optdepends[@]}")
# Check each dependency
for dep in "${all_deps[@]}"; do
dep_name=$(echo "$dep" | sed 's/[<>=].*//') # Remove version constraints
echo "Querying dependency: $dep_name"
if pacman -Si "$dep_name" &>/dev/null; then
dep_build_date=$(date -d "$(pacman -Si "$dep_name" | grep 'Build Date' | cut -d: -f2-)" +%s)
if (( dep_build_date >= pkg_build_date )); then
echo "Dependency '$dep_name' has newer or equal build date than '$pkg'."
echo "OUT-OF-DATE"
exit 0
fi
else
echo "Dependency '$dep_name' not found in repositories. Skipping."
fi
done
echo "All dependencies are older than package '$pkg'."
echo "UP-TO-DATE"
exit 0
else
echo "Package '$pkg' does NOT exist in any repository."
echo "OUT-OF-DATE"
exit 0
fi
done
)

View File

@ -18,6 +18,8 @@ type Repository struct {
CloneURL string `json:"clone_url"` CloneURL string `json:"clone_url"`
Owner Owner `json:"owner"` Owner Owner `json:"owner"`
FullName string `json:"full_name"` FullName string `json:"full_name"`
Template bool `json:"template"`
Topics []string `json:"topics"`
} }
type Owner struct { type Owner struct {

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module autobuilder
go 1.24.2

2
run.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env sh
docker run -it --platform amd64 --env-file .env --rm -v ./:/app -w /app gitea.seeseepuff.be/archlinux/archlinux