diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e5366..78ab097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - 2026-02-13 + +### Changed +- Parallelize library analysis within the same topological level using goroutines + ## [0.3.0] - 2026-02-13 ### Added @@ -58,6 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Multi-stage Docker build - Automated vendor upgrade workflow +[0.4.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.2.5...v0.3.0 [0.2.5]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.2.4...v0.2.5 [0.2.4]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.2.3...v0.2.4 diff --git a/VERSION b/VERSION index 9325c3c..60a2d3e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 \ No newline at end of file +0.4.0 \ No newline at end of file diff --git a/main.go b/main.go index c61a405..02a5468 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "path/filepath" "sort" "strings" + "sync" "goodchanges/internal/analyzer" "goodchanges/internal/git" @@ -107,11 +108,17 @@ func main() { // Track affected exports per package for cross-package propagation. allUpstreamTaint := make(map[string]map[string]bool) + type pkgResult struct { + pkgName string + affected []analyzer.AffectedExport + } + for levelIdx, level := range levels { - // TODO: packages within the same level that don't depend on each other - // can be processed in parallel using goroutines. logf("--- Level %d (%d packages) ---\n\n", levelIdx, len(level)) + var wg sync.WaitGroup + resultsCh := make(chan pkgResult, len(level)) + for _, pkgName := range level { info := projectMap[pkgName] if info == nil { @@ -159,7 +166,8 @@ func main() { logf(" Changed external deps: %s\n", strings.Join(depNames, ", ")) } - // Build upstream taint for this package from its dependencies + // Build upstream taint for this package from its dependencies. + // allUpstreamTaint is only read here — writes happen after the level completes. pkgUpstreamTaint := make(map[string]map[string]bool) for _, dep := range info.DependsOn { for specifier, names := range allUpstreamTaint { @@ -174,27 +182,35 @@ func main() { } } - affected, err := analyzer.AnalyzeLibraryPackage(info.ProjectFolder, entrypoints, mergeBase, changedFiles, flagIncludeTypes, pkgUpstreamTaint, changedDeps) - if err != nil { - fmt.Fprintf(os.Stderr, " Error analyzing package: %v\n", err) - continue - } + wg.Add(1) + go func(pkgName string, projectFolder string, entrypoints []analyzer.Entrypoint, pkgUpstreamTaint map[string]map[string]bool, changedDeps map[string]bool) { + defer wg.Done() + affected, err := analyzer.AnalyzeLibraryPackage(projectFolder, entrypoints, mergeBase, changedFiles, flagIncludeTypes, pkgUpstreamTaint, changedDeps) + if err != nil { + fmt.Fprintf(os.Stderr, " Error analyzing package %s: %v\n", pkgName, err) + return + } + if len(affected) > 0 { + resultsCh <- pkgResult{pkgName: pkgName, affected: affected} + } + }(pkgName, info.ProjectFolder, entrypoints, pkgUpstreamTaint, changedDeps) + } - if len(affected) == 0 { - logf(" No affected exports found\n\n") - continue - } + wg.Wait() + close(resultsCh) - logf(" Affected exports:\n") - for _, ae := range affected { + // Merge results into allUpstreamTaint after all goroutines in this level are done + for res := range resultsCh { + logf(" Affected exports for %s:\n", res.pkgName) + for _, ae := range res.affected { logf(" Entrypoint %q:\n", ae.EntrypointPath) for _, name := range ae.ExportNames { logf(" - %s\n", name) } - specifier := pkgName + specifier := res.pkgName if ae.EntrypointPath != "." { - specifier = pkgName + strings.TrimPrefix(ae.EntrypointPath, ".") + specifier = res.pkgName + strings.TrimPrefix(ae.EntrypointPath, ".") } if allUpstreamTaint[specifier] == nil { allUpstreamTaint[specifier] = make(map[string]bool)