Skip to content

Commit 4d57a16

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 4d57a16

File tree

1 file changed

+108
-19
lines changed

1 file changed

+108
-19
lines changed

ratt.go

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

2729
"pault.ag/go/debian/control"
2830
"pault.ag/go/debian/version"
@@ -100,6 +102,14 @@ var (
100102
false,
101103
"Output results in JSON format (currently only works in combination with -dry_run)")
102104

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

@@ -429,13 +439,105 @@ func getAptIndexPaths(dist string) ([]string, []string) {
429439
return fallbackIndexPaths()
430440
}
431441

442+
type buildJob struct {
443+
src string
444+
version version.Version
445+
}
446+
447+
func buildPackagesSequential(builder *sbuild, rebuild map[string][]version.Version) (map[string]*buildResult, []dryRunBuild) {
448+
buildresults := make(map[string]*buildResult)
449+
var dryRunBuilds []dryRunBuild
450+
cnt := 1
451+
452+
for src, versions := range rebuild {
453+
sort.Sort(sort.Reverse(version.Slice(versions)))
454+
newest := versions[0]
455+
log.Printf("Building package %d of %d: %s\n", cnt, len(rebuild), src)
456+
cnt++
457+
result := builder.build(src, &newest)
458+
if result.err != nil {
459+
log.Printf("building %s failed: %v\n", src, result.err)
460+
}
461+
buildresults[src] = result
462+
463+
if *dryRun {
464+
cmd := builder.buildCommandLine(src, &newest)
465+
dryRunBuilds = append(dryRunBuilds, dryRunBuild{
466+
Package: src,
467+
Version: newest.String(),
468+
SbuildCommand: strings.Join(cmd, " "),
469+
})
470+
}
471+
}
472+
return buildresults, dryRunBuilds
473+
}
474+
475+
func buildPackagesParallel(builder *sbuild, rebuild map[string][]version.Version, numJobs int) (map[string]*buildResult, []dryRunBuild) {
476+
jobs := make(chan buildJob, len(rebuild))
477+
results := make(chan *buildResult, len(rebuild))
478+
var wg sync.WaitGroup
479+
480+
// Start workers
481+
for i := 0; i < numJobs; i++ {
482+
wg.Add(1)
483+
go func(workerID int) {
484+
defer wg.Done()
485+
for job := range jobs {
486+
log.Printf("Worker %d building: %s\n", workerID, job.src)
487+
result := builder.build(job.src, &job.version)
488+
if result.err != nil {
489+
log.Printf("Worker %d: building %s failed: %v\n", workerID, job.src, result.err)
490+
}
491+
results <- result
492+
}
493+
}(i)
494+
}
495+
496+
// Send jobs
497+
go func() {
498+
defer close(jobs)
499+
for src, versions := range rebuild {
500+
sort.Sort(sort.Reverse(version.Slice(versions)))
501+
newest := versions[0]
502+
jobs <- buildJob{src: src, version: newest}
503+
}
504+
}()
505+
506+
// Collect results
507+
go func() {
508+
wg.Wait()
509+
close(results)
510+
}()
511+
512+
buildresults := make(map[string]*buildResult)
513+
var dryRunBuilds []dryRunBuild
514+
for result := range results {
515+
buildresults[result.src] = result
516+
if *dryRun {
517+
cmd := builder.buildCommandLine(result.src, result.version)
518+
dryRunBuilds = append(dryRunBuilds, dryRunBuild{
519+
Package: result.src,
520+
Version: result.version.String(),
521+
SbuildCommand: strings.Join(cmd, " "),
522+
})
523+
}
524+
}
525+
526+
log.Printf("Completed building %d packages with %d workers\n", len(buildresults), numJobs)
527+
return buildresults, dryRunBuilds
528+
}
529+
432530
func main() {
433531
flag.Parse()
434532

435533
if *jsonOutput && !*dryRun {
436534
log.Fatal("-json can only be used together with -dry_run")
437535
}
438536

537+
if *jobs <= 0 {
538+
log.Fatal("-jobs must be a positive number")
539+
}
540+
439541
if flag.NArg() == 0 {
440542
log.Fatalf("Usage: %s [options] <path-to-changes-file>...\n", os.Args[0])
441543
}
@@ -569,28 +671,15 @@ func main() {
569671
dryRun: *dryRun,
570672
extraDebs: debs,
571673
}
572-
cnt := 1
674+
573675
buildresults := make(map[string](*buildResult))
574676
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
585677

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-
}
678+
if *parallel {
679+
log.Printf("Building packages in parallel using %d workers\n", *jobs)
680+
buildresults, dryRunBuilds = buildPackagesParallel(builder, rebuild, *jobs)
681+
} else {
682+
buildresults, dryRunBuilds = buildPackagesSequential(builder, rebuild)
594683
}
595684

596685
var toInclude []string

0 commit comments

Comments
 (0)