ALHP.GO/main.go

599 lines
16 KiB
Go
Raw Normal View History

2021-06-10 21:32:11 +02:00
package main
import (
"context"
2021-10-25 06:20:03 +02:00
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"flag"
2021-06-10 21:32:11 +02:00
"fmt"
2022-02-11 21:33:44 +01:00
"git.harting.dev/ALHP/ALHP.GO/ent"
"git.harting.dev/ALHP/ALHP.GO/ent/dbpackage"
"git.harting.dev/ALHP/ALHP.GO/ent/migrate"
2021-06-10 21:32:11 +02:00
"github.com/Jguer/go-alpm/v2"
2021-10-25 06:20:03 +02:00
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/sethvargo/go-retry"
2021-06-10 21:32:11 +02:00
log "github.com/sirupsen/logrus"
"github.com/wercker/journalhook"
2022-02-19 18:03:55 +01:00
"golang.org/x/sync/semaphore"
2021-06-10 21:32:11 +02:00
"gopkg.in/yaml.v2"
"html/template"
"math"
2021-06-10 21:32:11 +02:00
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"sync"
2021-06-10 21:32:11 +02:00
"syscall"
"time"
)
var (
conf *Conf
2021-09-12 17:35:45 +02:00
repos []string
alpmHandle *alpm.Handle
buildManager *BuildManager
2021-09-12 17:35:45 +02:00
db *ent.Client
journalLog = flag.Bool("journal", false, "Log to systemd journal instead of stdout")
checkInterval = flag.Int("interval", 5, "How often svn2git should be checked in minutes (default: 5)")
2021-06-10 21:32:11 +02:00
)
2022-02-19 18:03:55 +01:00
func (b *BuildManager) htmlWorker(ctx context.Context) {
type Pkg struct {
2022-02-13 22:33:57 +01:00
Pkgbase string
Status string
Class string
Skip string
Version string
Svn2GitVersion string
BuildDate string
BuildDuration time.Duration
Checked string
Log string
LTO bool
LTOUnknown bool
LTODisabled bool
LTOAutoDisabled bool
DebugSym bool
DebugSymNotAvailable bool
DebugSymUnknown bool
}
type Repo struct {
Name string
Packages []Pkg
}
type March struct {
Name string
Repos []Repo
}
type tpl struct {
2021-11-25 02:10:39 +01:00
March []March
Generated string
Latest int
Failed int
Skipped int
Queued int
LTOEnabled int
LTOUnknown int
LTODisabled int
}
for {
gen := &tpl{}
for _, march := range conf.March {
addMarch := March{
Name: march,
}
for _, repo := range conf.Repos {
addRepo := Repo{
Name: repo,
}
2022-11-20 19:19:16 +01:00
pkgs := db.DbPackage.Query().Order(ent.Asc(dbpackage.FieldPkgbase)).
Where(dbpackage.MarchEQ(march), dbpackage.RepositoryEQ(dbpackage.Repository(repo))).AllX(ctx)
for _, pkg := range pkgs {
addPkg := Pkg{
Pkgbase: pkg.Pkgbase,
Status: strings.ToUpper(pkg.Status.String()),
2022-11-20 19:19:16 +01:00
Class: statusID2string(pkg.Status),
Skip: pkg.SkipReason,
Version: pkg.RepoVersion,
Svn2GitVersion: pkg.Version,
}
if pkg.STime != nil && pkg.UTime != nil {
addPkg.BuildDuration = time.Duration(*pkg.STime+*pkg.UTime) * time.Second
}
if !pkg.BuildTimeStart.IsZero() {
2021-11-02 10:59:58 +01:00
addPkg.BuildDate = pkg.BuildTimeStart.UTC().Format(time.RFC1123)
}
if !pkg.Updated.IsZero() {
2021-11-02 10:59:58 +01:00
addPkg.Checked = pkg.Updated.UTC().Format(time.RFC1123)
}
if pkg.Status == dbpackage.StatusFailed {
2021-11-29 13:29:43 +01:00
addPkg.Log = fmt.Sprintf("%s/%s/%s.log", logDir, pkg.March, pkg.Pkgbase)
}
2021-11-16 23:30:31 +01:00
switch pkg.Lto {
case dbpackage.LtoUnknown:
if pkg.Status != dbpackage.StatusSkipped && pkg.Status != dbpackage.StatusFailed {
addPkg.LTOUnknown = true
}
2021-11-16 23:30:31 +01:00
case dbpackage.LtoEnabled:
2021-11-16 23:42:54 +01:00
addPkg.LTO = true
2021-11-16 23:30:31 +01:00
case dbpackage.LtoDisabled:
2021-11-16 23:42:54 +01:00
addPkg.LTODisabled = true
case dbpackage.LtoAutoDisabled:
addPkg.LTOAutoDisabled = true
2021-11-16 23:30:31 +01:00
}
2022-02-13 22:33:57 +01:00
switch pkg.DebugSymbols {
case dbpackage.DebugSymbolsUnknown:
if pkg.Status != dbpackage.StatusSkipped && pkg.Status != dbpackage.StatusFailed {
addPkg.DebugSymUnknown = true
}
case dbpackage.DebugSymbolsAvailable:
addPkg.DebugSym = true
case dbpackage.DebugSymbolsNotAvailable:
addPkg.DebugSymNotAvailable = true
}
addRepo.Packages = append(addRepo.Packages, addPkg)
}
addMarch.Repos = append(addMarch.Repos, addRepo)
}
gen.March = append(gen.March, addMarch)
}
2021-11-02 10:59:58 +01:00
gen.Generated = time.Now().UTC().Format(time.RFC1123)
2021-11-23 22:04:50 +01:00
var v []struct {
Status dbpackage.Status `json:"status"`
Count int `json:"count"`
}
2022-02-19 18:03:55 +01:00
db.DbPackage.Query().GroupBy(dbpackage.FieldStatus).Aggregate(ent.Count()).ScanX(ctx, &v)
2021-11-23 22:04:50 +01:00
for _, c := range v {
switch c.Status {
case dbpackage.StatusFailed:
gen.Failed = c.Count
case dbpackage.StatusSkipped:
gen.Skipped = c.Count
case dbpackage.StatusLatest:
gen.Latest = c.Count
2021-11-24 13:24:41 +01:00
case dbpackage.StatusQueued:
gen.Queued = c.Count
2021-11-23 22:04:50 +01:00
}
}
2021-11-25 02:10:39 +01:00
var v2 []struct {
Status dbpackage.Lto `json:"lto"`
Count int `json:"count"`
}
2022-11-20 19:19:16 +01:00
db.DbPackage.Query().Where(dbpackage.StatusNEQ(dbpackage.StatusSkipped)).
GroupBy(dbpackage.FieldLto).Aggregate(ent.Count()).ScanX(ctx, &v2)
2021-11-25 02:10:39 +01:00
for _, c := range v2 {
switch c.Status {
case dbpackage.LtoUnknown:
gen.LTOUnknown = c.Count
case dbpackage.LtoDisabled, dbpackage.LtoAutoDisabled:
gen.LTODisabled += c.Count
case dbpackage.LtoEnabled:
gen.LTOEnabled = c.Count
}
}
statusTpl, err := template.ParseFiles("tpl/packages.html")
2022-02-16 08:11:34 +01:00
if err != nil {
log.Warningf("[HTML] Error parsing template file: %v", err)
continue
}
2022-11-20 19:19:16 +01:00
f, err := os.OpenFile(filepath.Join(conf.Basedir.Repo, "packages.html"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
2022-02-16 08:11:34 +01:00
if err != nil {
log.Warningf("[HTML] Erro ropening output file: %v", err)
continue
}
err = statusTpl.Execute(f, gen)
if err != nil {
log.Warningf("[HTML] Error filling template: %v", err)
}
_ = f.Close()
2021-12-20 00:38:47 +01:00
time.Sleep(time.Minute * 5)
}
}
func (b *BuildManager) repoWorker(repo string) {
2021-06-10 21:32:11 +02:00
for {
select {
2021-12-20 16:04:13 +01:00
case pkgL := <-b.repoAdd[repo]:
b.repoWG.Add(1)
toAdd := make([]string, 0)
2021-12-20 16:04:13 +01:00
for _, pkg := range pkgL {
toAdd = append(toAdd, pkg.PkgFiles...)
}
args := []string{"-s", "-v", "-p", "-n", filepath.Join(conf.Basedir.Repo, repo, "os", conf.Arch, repo) + ".db.tar.xz"}
args = append(args, toAdd...)
cmd := exec.Command("repo-add", args...)
res, err := cmd.CombinedOutput()
log.Debug(string(res))
if err != nil && cmd.ProcessState.ExitCode() != 1 {
log.Panicf("%s while repo-add: %v", string(res), err)
}
2021-06-10 21:32:11 +02:00
for _, pkg := range pkgL {
2022-11-20 19:19:16 +01:00
pkg.toDBPackage(true)
if _, err := os.Stat(filepath.Join(conf.Basedir.Debug, pkg.March,
pkg.DBPackage.Packages[0]+"-debug-"+pkg.Version+"-"+conf.Arch+".pkg.tar.zst")); err == nil {
pkg.DBPackage = pkg.DBPackage.Update().
2022-02-13 22:33:57 +01:00
SetStatus(dbpackage.StatusLatest).
ClearSkipReason().
SetDebugSymbols(dbpackage.DebugSymbolsAvailable).
SetRepoVersion(pkg.Version).
SetHash(pkg.Hash).
SaveX(context.Background())
} else {
2022-11-20 19:19:16 +01:00
pkg.DBPackage = pkg.DBPackage.Update().
2022-02-13 22:33:57 +01:00
SetStatus(dbpackage.StatusLatest).
ClearSkipReason().
SetDebugSymbols(dbpackage.DebugSymbolsNotAvailable).
SetRepoVersion(pkg.Version).
SetHash(pkg.Hash).
SaveX(context.Background())
}
2021-06-10 21:32:11 +02:00
}
2022-11-20 19:19:16 +01:00
cmd = exec.Command("paccache", "-rc", filepath.Join(conf.Basedir.Repo, repo, "os", conf.Arch), "-k", "1") //nolint:gosec
res, err = cmd.CombinedOutput()
log.Debug(string(res))
2022-02-16 08:11:34 +01:00
if err != nil {
log.Warningf("Error running paccache: %v", err)
}
err = updateLastUpdated()
if err != nil {
log.Warningf("Error updating lastupdate: %v", err)
}
b.repoWG.Done()
2021-12-20 16:04:13 +01:00
case pkgL := <-b.repoPurge[repo]:
for _, pkg := range pkgL {
if _, err := os.Stat(filepath.Join(conf.Basedir.Repo, pkg.FullRepo, "os", conf.Arch, pkg.FullRepo) + ".db.tar.xz"); err != nil {
continue
}
2021-12-20 16:04:13 +01:00
if len(pkg.PkgFiles) == 0 {
if err := pkg.findPkgFiles(); err != nil {
log.Warningf("[%s/%s] Unable to find files: %v", pkg.FullRepo, pkg.Pkgbase, err)
continue
} else if len(pkg.PkgFiles) == 0 {
continue
}
}
2021-06-10 21:32:11 +02:00
2021-12-20 16:04:13 +01:00
var realPkgs []string
for _, filePath := range pkg.PkgFiles {
if _, err := os.Stat(filePath); err == nil {
realPkgs = append(realPkgs, Package(filePath).Name())
}
}
if len(realPkgs) == 0 {
continue
2021-12-20 16:04:13 +01:00
}
2021-06-10 21:32:11 +02:00
2021-12-20 16:04:13 +01:00
b.repoWG.Add(1)
args := []string{"-s", "-v", filepath.Join(conf.Basedir.Repo, pkg.FullRepo, "os", conf.Arch, pkg.FullRepo) + ".db.tar.xz"}
args = append(args, realPkgs...)
cmd := exec.Command("repo-remove", args...)
res, err := cmd.CombinedOutput()
log.Debug(string(res))
if err != nil && cmd.ProcessState.ExitCode() == 1 {
log.Warningf("Error while deleting package %s: %s", pkg.Pkgbase, string(res))
}
2021-06-10 21:32:11 +02:00
2022-11-20 19:19:16 +01:00
if pkg.DBPackage != nil {
_ = pkg.DBPackage.Update().ClearRepoVersion().ClearHash().Exec(context.Background())
2021-12-20 16:04:13 +01:00
}
2021-12-20 16:04:13 +01:00
for _, file := range pkg.PkgFiles {
_ = os.Remove(file)
_ = os.Remove(file + ".sig")
}
2022-02-16 08:11:34 +01:00
err = updateLastUpdated()
if err != nil {
log.Warningf("Error updating lastupdate: %v", err)
}
2021-12-20 16:04:13 +01:00
b.repoWG.Done()
2021-06-10 21:32:11 +02:00
}
}
}
}
2022-02-19 18:03:55 +01:00
func (b *BuildManager) syncWorker(ctx context.Context) error {
2022-11-20 19:19:16 +01:00
err := os.MkdirAll(filepath.Join(conf.Basedir.Work, upstreamDir), 0o755)
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error creating upstream dir: %v", err)
}
2021-06-10 21:32:11 +02:00
for {
for gitDir, gitURL := range conf.Svn2git {
gitPath := filepath.Join(conf.Basedir.Work, upstreamDir, gitDir)
2021-06-10 21:32:11 +02:00
if _, err := os.Stat(gitPath); os.IsNotExist(err) {
cmd := exec.Command("git", "clone", "--depth=1", gitURL, gitPath)
2021-06-10 21:32:11 +02:00
res, err := cmd.CombinedOutput()
log.Debug(string(res))
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error running git clone: %v", err)
}
2021-06-10 21:32:11 +02:00
} else if err == nil {
cmd := exec.Command("git", "reset", "--hard")
cmd.Dir = gitPath
2021-06-10 21:32:11 +02:00
res, err := cmd.CombinedOutput()
log.Debug(string(res))
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error running git reset: %v", err)
}
2021-06-10 21:32:11 +02:00
cmd = exec.Command("git", "pull")
cmd.Dir = gitPath
2021-06-10 21:32:11 +02:00
res, err = cmd.CombinedOutput()
log.Debug(string(res))
2021-11-20 16:15:00 +01:00
if err != nil {
log.Warningf("Failed to update git repo %s: %v", gitDir, err)
}
2021-06-10 21:32:11 +02:00
}
}
// housekeeping
wg := new(sync.WaitGroup)
for _, repo := range repos {
wg.Add(1)
splitRepo := strings.Split(repo, "-")
repo := repo
go func() {
err := housekeeping(splitRepo[0], strings.Join(splitRepo[1:], "-"), wg)
if err != nil {
log.Warningf("[%s] housekeeping failed: %v", repo, err)
}
}()
}
wg.Wait()
err := logHK()
if err != nil {
log.Warningf("log-housekeeping failed: %v", err)
}
// fetch updates between sync runs
2021-08-31 21:13:22 +02:00
b.alpmMutex.Lock()
2022-02-16 08:11:34 +01:00
err = alpmHandle.Release()
if err != nil {
log.Fatalf("Error releasing ALPM handle: %v", err)
}
if err := retry.Fibonacci(ctx, 1*time.Second, func(ctx context.Context) error {
if err := setupChroot(); err != nil {
log.Warningf("Unable to upgrade chroot, trying again later.")
return retry.RetryableError(err)
}
return nil
}); err != nil {
log.Fatal(err)
}
2022-11-20 19:19:16 +01:00
alpmHandle, err = initALPM(filepath.Join(conf.Basedir.Work, chrootDir, pristineChroot),
filepath.Join(conf.Basedir.Work, chrootDir, pristineChroot, "/var/lib/pacman"))
2022-02-16 08:11:34 +01:00
if err != nil {
log.Warningf("Error while ALPM-init: %v", err)
}
2021-08-31 21:13:22 +02:00
b.alpmMutex.Unlock()
// do refreshSRCINFOs twice here
// since MirrorLatest depends on the DB being correct, there can be packages queued which should not be queued,
// so we check them twice to eliminate those.
2022-08-13 10:56:58 +02:00
log.Debugf("generating build-queue for PKGBUILDs found in %s", filepath.Join(conf.Basedir.Work, upstreamDir, "/**/PKGBUILD"))
err = b.refreshSRCINFOs(ctx, filepath.Join(conf.Basedir.Work, upstreamDir, "/**/PKGBUILD"))
if err != nil {
log.Fatalf("error refreshing PKGBUILDs: %v", err)
}
log.Debugf("regenerating build-queue for PKGBUILDs found in %s", filepath.Join(conf.Basedir.Work, upstreamDir, "/**/PKGBUILD"))
err = b.refreshSRCINFOs(ctx, filepath.Join(conf.Basedir.Work, upstreamDir, "/**/PKGBUILD"))
if err != nil {
log.Fatalf("error refreshing PKGBUILDs: %v", err)
}
queue, err := b.queue()
2022-02-16 08:11:34 +01:00
if err != nil {
2022-02-19 18:03:55 +01:00
log.Warningf("Error building buildQueue: %v", err)
} else {
log.Debugf("buildQueue with %d items", len(queue))
2022-02-19 18:03:55 +01:00
var fastQueue []*ProtoPackage
var slowQueue []*ProtoPackage
maxDiff := 0.0
cutOff := 0.0
for i := 0; i < len(queue); i++ {
if i+1 < len(queue) {
if math.Abs(queue[i].Priority()-queue[i+1].Priority()) > maxDiff {
maxDiff = math.Abs(queue[i].Priority() - queue[i+1].Priority())
cutOff = queue[i].Priority()
}
}
}
2022-02-19 18:03:55 +01:00
for _, pkg := range queue {
if pkg.Priority() > cutOff && cutOff >= conf.Build.SlowQueueThreshold {
2022-02-19 18:03:55 +01:00
slowQueue = append(slowQueue, pkg)
} else {
fastQueue = append(fastQueue, pkg)
}
2021-06-10 21:32:11 +02:00
}
if len(fastQueue) > 0 && len(slowQueue) > 0 {
2022-02-19 18:03:55 +01:00
log.Infof("Skipping slowQueue=%d in favor of fastQueue=%d", len(slowQueue), len(fastQueue))
slowQueue = []*ProtoPackage{}
2021-06-10 21:32:11 +02:00
}
2022-02-19 18:03:55 +01:00
err = b.buildQueue(fastQueue, ctx)
if err != nil {
return err
}
2022-02-19 18:03:55 +01:00
err = b.buildQueue(slowQueue, ctx)
if err != nil {
return err
}
2022-02-19 18:03:55 +01:00
if err := b.sem.Acquire(ctx, int64(conf.Build.Worker)); err != nil {
return err
2021-06-10 21:32:11 +02:00
}
b.sem.Release(int64(conf.Build.Worker))
2021-06-10 21:32:11 +02:00
}
if ctx.Err() == nil {
for _, repo := range repos {
err = movePackagesLive(repo)
if err != nil {
log.Errorf("[%s] Error moving packages live: %v", repo, err)
}
}
2022-02-19 18:03:55 +01:00
} else {
return ctx.Err()
}
log.Debugf("build-cycle finished")
time.Sleep(time.Duration(*checkInterval) * time.Minute)
2021-06-10 21:32:11 +02:00
}
}
func main() {
killSignals := make(chan os.Signal, 1)
signal.Notify(killSignals, syscall.SIGINT, syscall.SIGTERM)
2021-11-22 16:50:12 +01:00
reloadSignals := make(chan os.Signal, 1)
signal.Notify(reloadSignals, syscall.SIGUSR1)
flag.Parse()
2021-06-10 21:32:11 +02:00
confStr, err := os.ReadFile("config.yaml")
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error reading config file: %v", err)
}
2021-06-10 21:32:11 +02:00
err = yaml.Unmarshal(confStr, &conf)
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error parsing config file: %v", err)
}
2021-06-10 21:32:11 +02:00
lvl, err := log.ParseLevel(conf.Logging.Level)
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error parsing log level from config: %v", err)
}
2021-06-10 21:32:11 +02:00
log.SetLevel(lvl)
if *journalLog {
journalhook.Enable()
}
2021-06-10 21:32:11 +02:00
2021-06-30 17:41:50 +02:00
err = syscall.Setpriority(syscall.PRIO_PROCESS, 0, 5)
if err != nil {
log.Warningf("Failed to drop priority: %v", err)
}
2022-11-20 19:19:16 +01:00
err = os.MkdirAll(conf.Basedir.Repo, 0o755)
2022-02-16 08:11:34 +01:00
if err != nil {
log.Fatalf("Error creating repo dir: %v", err)
}
2022-11-20 19:19:16 +01:00
if conf.DB.Driver == "pgx" {
pdb, err := sql.Open("pgx", conf.DB.ConnectTo)
2021-10-25 06:20:03 +02:00
if err != nil {
2022-11-20 19:19:16 +01:00
log.Fatalf("Failed to open database %s: %v", conf.DB.ConnectTo, err)
2021-10-25 06:20:03 +02:00
}
drv := sql.OpenDB(dialect.Postgres, pdb.DB())
db = ent.NewClient(ent.Driver(drv))
} else {
2022-11-20 19:19:16 +01:00
db, err = ent.Open(conf.DB.Driver, conf.DB.ConnectTo)
2021-10-25 06:20:03 +02:00
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Failed to open database %s: %v", conf.DB.ConnectTo, err)
2021-10-25 06:20:03 +02:00
}
defer func(Client *ent.Client) {
_ = Client.Close()
}(db)
}
2021-07-26 16:38:12 +02:00
if err := db.Schema.Create(context.Background(), migrate.WithDropIndex(true), migrate.WithDropColumn(true)); err != nil {
log.Panicf("Automigrate failed: %v", err)
}
buildManager = &BuildManager{
2022-02-16 08:11:34 +01:00
repoPurge: make(map[string]chan []*ProtoPackage),
repoAdd: make(map[string]chan []*ProtoPackage),
2022-02-19 18:03:55 +01:00
sem: semaphore.NewWeighted(int64(conf.Build.Worker)),
2021-06-10 21:32:11 +02:00
}
err = setupChroot()
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Unable to setup chroot: %v", err)
}
2022-02-16 08:11:34 +01:00
err = syncMarchs()
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Error syncing marchs: %v", err)
2022-02-16 08:11:34 +01:00
}
2021-06-10 21:32:11 +02:00
2022-11-20 19:19:16 +01:00
alpmHandle, err = initALPM(filepath.Join(conf.Basedir.Work, chrootDir, pristineChroot),
filepath.Join(conf.Basedir.Work, chrootDir, pristineChroot, "/var/lib/pacman"))
2022-02-16 08:11:34 +01:00
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Error while ALPM-init: %v", err)
2022-02-16 08:11:34 +01:00
}
2022-02-19 18:03:55 +01:00
ctx, cancel := context.WithCancel(context.Background())
go func() {
_ = buildManager.syncWorker(ctx)
}()
go buildManager.htmlWorker(ctx)
2021-06-10 21:32:11 +02:00
2021-11-22 16:50:12 +01:00
killLoop:
for {
select {
case <-killSignals:
break killLoop
case <-reloadSignals:
confStr, err := os.ReadFile("config.yaml")
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Unable to open config: %v", err)
2021-11-22 16:50:12 +01:00
}
err = yaml.Unmarshal(confStr, &conf)
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Unable to parse config: %v", err)
2021-11-22 16:50:12 +01:00
}
lvl, err := log.ParseLevel(conf.Logging.Level)
if err != nil {
2022-11-20 19:19:16 +01:00
log.Panicf("Failure setting logging level: %v", err)
2021-11-22 16:50:12 +01:00
}
log.SetLevel(lvl)
2021-11-23 01:13:50 +01:00
log.Infof("Config reloaded")
2021-11-22 16:50:12 +01:00
}
}
2021-06-10 21:32:11 +02:00
2022-02-19 18:03:55 +01:00
cancel()
buildManager.repoWG.Wait()
2022-02-16 08:11:34 +01:00
_ = alpmHandle.Release()
2021-06-10 21:32:11 +02:00
}