Skip to content

Commit 30878ea

Browse files
committed
Add parallel build support with -parallel and -jobs options
This commit introduces parallel package building to ratt. - New flag `-parallel` enables parallel builds. - New flag `-jobs` controls the number of concurrent workers (default: number of CPU cores, must be > 0). - Added `buildPackagesSequential` and `buildPackagesParallel` helpers. - Refactored main build loop to support sequential or parallel execution. This allows faster builds by utilizing multiple CPU cores. Signed-off-by: Arthur Diniz <[email protected]>
1 parent e11cf12 commit 30878ea

File tree

3 files changed

+101
-20
lines changed

3 files changed

+101
-20
lines changed

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
module github.com/Debian/ratt
22

3-
go 1.18
3+
go 1.24.0
4+
5+
toolchain go1.24.4
46

57
require pault.ag/go/debian v0.12.0
68

79
require (
810
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
11+
golang.org/x/sync v0.17.0 // indirect
912
pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a // indirect
1013
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
55
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
66
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
77
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
8+
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
9+
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
810
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
911
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1012
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

ratt.go

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ import (
2121
"os/exec"
2222
"path/filepath"
2323
"regexp"
24+
"runtime"
2425
"sort"
2526
"strings"
27+
"sync"
2628

29+
"golang.org/x/sync/errgroup"
2730
"pault.ag/go/debian/control"
2831
"pault.ag/go/debian/version"
2932
)
@@ -100,6 +103,14 @@ var (
100103
false,
101104
"Output results in JSON format (currently only works in combination with -dry_run)")
102105

106+
parallel = flag.Bool("parallel",
107+
false,
108+
"Build packages in parallel")
109+
110+
jobs = flag.Int("jobs",
111+
runtime.NumCPU(),
112+
"Number of parallel build jobs (default: number of CPU cores)")
113+
103114
listsPrefixRe = regexp.MustCompile(`/([^/]*_dists_.*)_InRelease$`)
104115
)
105116

@@ -429,13 +440,91 @@ func getAptIndexPaths(dist string) ([]string, []string) {
429440
return fallbackIndexPaths()
430441
}
431442

443+
type buildJob struct {
444+
src string
445+
version version.Version
446+
}
447+
448+
func buildPackagesSequential(builder *sbuild, rebuild map[string][]version.Version) (map[string]*buildResult, []dryRunBuild) {
449+
buildresults := make(map[string]*buildResult)
450+
var dryRunBuilds []dryRunBuild
451+
cnt := 1
452+
453+
for src, versions := range rebuild {
454+
sort.Sort(sort.Reverse(version.Slice(versions)))
455+
newest := versions[0]
456+
log.Printf("Building package %d of %d: %s\n", cnt, len(rebuild), src)
457+
cnt++
458+
result := builder.build(src, &newest)
459+
if result.err != nil {
460+
log.Printf("building %s failed: %v\n", src, result.err)
461+
}
462+
buildresults[src] = result
463+
464+
if *dryRun {
465+
cmd := builder.buildCommandLine(src, &newest)
466+
dryRunBuilds = append(dryRunBuilds, dryRunBuild{
467+
Package: src,
468+
Version: newest.String(),
469+
SbuildCommand: strings.Join(cmd, " "),
470+
})
471+
}
472+
}
473+
return buildresults, dryRunBuilds
474+
}
475+
476+
func buildPackagesParallel(builder *sbuild, rebuild map[string][]version.Version, numJobs int) (map[string]*buildResult, []dryRunBuild) {
477+
var eg errgroup.Group
478+
eg.SetLimit(numJobs)
479+
480+
buildresults := make(map[string]*buildResult)
481+
var dryRunBuilds []dryRunBuild
482+
var resultsMu sync.Mutex
483+
cnt := 1
484+
485+
for src, versions := range rebuild {
486+
src, versions := src, versions // capture loop variables
487+
eg.Go(func() error {
488+
sort.Sort(sort.Reverse(version.Slice(versions)))
489+
newest := versions[0]
490+
log.Printf("Building package %d of %d: %s\n", cnt, len(rebuild), src)
491+
cnt++
492+
result := builder.build(src, &newest)
493+
if result.err != nil {
494+
log.Printf("building %s failed: %v\n", src, result.err)
495+
}
496+
497+
resultsMu.Lock()
498+
buildresults[src] = result
499+
if *dryRun {
500+
cmd := builder.buildCommandLine(src, &newest)
501+
dryRunBuilds = append(dryRunBuilds, dryRunBuild{
502+
Package: src,
503+
Version: newest.String(),
504+
SbuildCommand: strings.Join(cmd, " "),
505+
})
506+
}
507+
resultsMu.Unlock()
508+
return nil
509+
})
510+
}
511+
512+
eg.Wait()
513+
log.Printf("Completed building %d packages with %d workers\n", len(buildresults), numJobs)
514+
return buildresults, dryRunBuilds
515+
}
516+
432517
func main() {
433518
flag.Parse()
434519

435520
if *jsonOutput && !*dryRun {
436521
log.Fatal("-json can only be used together with -dry_run")
437522
}
438523

524+
if *jobs <= 0 {
525+
log.Fatal("-jobs must be a positive number")
526+
}
527+
439528
if flag.NArg() == 0 {
440529
log.Fatalf("Usage: %s [options] <path-to-changes-file>...\n", os.Args[0])
441530
}
@@ -569,28 +658,15 @@ func main() {
569658
dryRun: *dryRun,
570659
extraDebs: debs,
571660
}
572-
cnt := 1
661+
573662
buildresults := make(map[string](*buildResult))
574663
var dryRunBuilds []dryRunBuild
575-
for src, versions := range rebuild {
576-
sort.Sort(sort.Reverse(version.Slice(versions)))
577-
newest := versions[0]
578-
log.Printf("Building package %d of %d: %s \n", cnt, len(rebuild), src)
579-
cnt++
580-
result := builder.build(src, &newest)
581-
if result.err != nil {
582-
log.Printf("building %s failed: %v\n", src, result.err)
583-
}
584-
buildresults[src] = result
585664

586-
if *dryRun {
587-
cmd := builder.buildCommandLine(src, &newest)
588-
dryRunBuilds = append(dryRunBuilds, dryRunBuild{
589-
Package: src,
590-
Version: newest.String(),
591-
SbuildCommand: strings.Join(cmd, " "),
592-
})
593-
}
665+
if *parallel {
666+
log.Printf("Building packages in parallel using %d workers\n", *jobs)
667+
buildresults, dryRunBuilds = buildPackagesParallel(builder, rebuild, *jobs)
668+
} else {
669+
buildresults, dryRunBuilds = buildPackagesSequential(builder, rebuild)
594670
}
595671

596672
var toInclude []string

0 commit comments

Comments
 (0)