Skip to content

feat: detect transitive deps, deduplicate packages, and flag API compatibility issues#26

Merged
kbsteere merged 34 commits intochainguard-dev:mainfrom
kbsteere:missing-recommended-package-updates
Apr 15, 2026
Merged

feat: detect transitive deps, deduplicate packages, and flag API compatibility issues#26
kbsteere merged 34 commits intochainguard-dev:mainfrom
kbsteere:missing-recommended-package-updates

Conversation

@kbsteere
Copy link
Copy Markdown
Member

@kbsteere kbsteere commented Mar 6, 2026

Fixes several critical issues with omnibump's Go module analyzer. When updating packages like oras.land/oras-go that require newer versions of transitive dependencies, omnibump had no way to detect those requirements and builds would silently break. Additionally, omnibump was hanging on large workspace projects, producing duplicate packages in output, and displaying error messages that wrapped awkwardly across terminal lines.

This PR detects transitive dependency requirements by fetching the target package's go.mod from the Go proxy and flags packages needing co-updates. It eliminates duplicates by keeping the highest semver version and detects API compatibility issues to flag downstream packages like knative.dev/pkg that import updated dependencies. Error messages are reformatted with clear visual sections (REQUIRED CO-UPDATES, API COMPATIBILITY ALERTS, SUGGESTED UPDATE COMMAND) with each package on its own line. Workspace hanging is fixed by adding GOWORK=off, HTTP timeouts, and version scan caps. DirectUpdates are now sorted alphabetically. End-to-end testing with knative-eventing confirms all functionality works and the project builds successfully.

kbsteere added 5 commits March 6, 2026 11:45
Fixes issues where packages with +incompatible suffix were not resolved correctly,
and vendor directories failed due to missing go.sum entries.

- Resolve all semantic versions through go list to get canonical forms
- Handles +incompatible suffix automatically (e.g., v28.0.0 -> v28.0.0+incompatible)
- Skip commit hashes to preserve existing behavior
- Add GOWORK=off for workspace compatibility
- Run go mod tidy before go vendor to ensure go.sum is up-to-date
- Add test coverage for +incompatible version updates
Adds automatic detection of when updating a package requires co-updating
other dependencies to prevent build failures from incompatible versions.

When updating a package, omnibump now:
- Fetches the target version's go.mod from the Go module proxy
- Compares its requirements against current project versions
- Detects incompatibilities (required version > current version)
- Returns error with complete list of packages to update together
- Provides exact command to run with all required co-updates

Example:
  $ omnibump --packages "oras.land/oras-go@v1.2.7"
  Error: the following dependencies need to be co-updated:
    - github.com/docker/docker: current v28.0.0, required >= v28.5.1
    - github.com/docker/cli: current v25.0.1, required >= v28.5.1
    [... 13 more ...]

  To proceed, add these packages to your update:
    omnibump --packages "oras.land/oras-go@v1.2.7 github.com/docker/docker@v28.5.1 ..."

Features:
- Works in both update and analyze commands
- Automatically deduplicates (keeps highest required version)
- Filters out packages already being updated
- Validates entire update set together
- Available through standard Language interface

Implementation:
- Added CheckTransitiveRequirements() to check single package
- Integrated into resolveAndFilterPackages() for update flow
- Integrated into RecommendStrategy() for analyze flow
- Added comprehensive test coverage with real proxy data
- Added documentation for library consumers
@kbsteere kbsteere requested a review from a team March 6, 2026 23:58
@kbsteere kbsteere changed the title feat: detect transitive dependency requirements and resolve +incompatible versions feat: detect transitive dependency requirements Mar 18, 2026
@kbsteere kbsteere enabled auto-merge (squash) March 19, 2026 23:13
@kbsteere kbsteere removed the request for review from a team March 19, 2026 23:14
kbsteere added 15 commits March 20, 2026 12:39
…t hanging

Packages like github.com/elastic/beats/v7 can have thousands of
pseudo-versions. Iterating all of them and fetching each go.mod via
HTTP causes the analyze command to hang indefinitely. Cap the search
at 50 newer versions, which is sufficient to find a fix in practice
while keeping analysis responsive.
The same package (e.g. github.com/aquasecurity/trivy) was appended to
DirectUpdates multiple times when several indirect deps each resolved to
the same direct parent as their fix. addParentBumpsToStrategy had no
cross-call deduplication, producing duplicate entries in the output
deps.yaml. Deduplicate at the end of RecommendStrategy, keeping the
highest semver version per package.
When updating a package that has schema/API breaking changes, check if
other packages in the project import from it. Flag those packages as
potentially needing updates, since breaking API changes can require
co-updates even if go.mod doesn't explicitly bump version requirements.

Example: When opentelemetry/otel/sdk is updated with schema changes,
packages like knative.dev/pkg that import from it may need updating to
maintain compatibility, even if their explicit requires don't change.
Add unit tests to verify that CheckAPICompatibility detects packages
that import from an updated dependency. Tests cover basic detection,
skipping of indirect dependencies, and error handling.
Sort direct dependency updates alphabetically by package name using
slices.SortStableFunc to ensure consistent, readable output regardless
of the order in which dependencies are discovered.
…ectUpdates

When a user explicitly requests an indirect package to be updated via
--packages, it should appear in DirectUpdates even if parent bump
alternatives exist. Previously, handleIndirectDependency returning true
would cause us to skip adding the package. Now we add it and offer
parent bumps as additional options in warnings.
…g deps

API compatibility checks flag packages that import an updated
dependency. These should be warnings for manual review, not implicit
version requirements (since we don't know what version is needed).
Instead of adding them to missing deps (which become no-ops), add them
as warnings so users see: 'knative.dev/pkg imports
go.opentelemetry.io/otel/sdk which is being updated (may need manual
version bump)'.
…arnings

- Add API compatibility checks to checkMissingTransitiveDeps so alerts
  appear in both analyze and update commands
- Group all API compatibility alerts into a single 'API Compatibility
  Alerts' section instead of individual warnings
- Display packages that import updated dependencies for manual review
- Example: when updating go.opentelemetry.io/otel/sdk, knative.dev/pkg
  and other packages that import it are flagged in one section
Add a note in the error message directing users to review packages
listed in the 'API Compatibility Alerts' section and add them to the
update command if needed. This makes the connection clear between the
flagged packages and the suggested update command.
…ommand

API compatibility alerts indicate packages that import updated
dependencies and need to be co-updated. Include them in the 'add these
packages' suggestion with their current versions so omnibump makes the
requirement clear. Example: when updating go.opentelemetry.io/otel/sdk,
knative.dev/pkg is now included in the suggested command since it
imports the updated dependency.
Format the checkMissingTransitiveDeps error message with clear section headers,
decorative lines, and one package per line instead of wrapping a long command.
Improves readability of required co-updates, API compatibility alerts, and
suggested update commands.
…utput

Add tests that verify actual functionality:
- TestCheckMissingTransitiveDeps_FormattedOutput: ensures error message includes formatted sections
- TestGolang_Update_WithDuplicatePackagesDeduplicates: verifies update succeeds with duplicate packages
- TestRecommendStrategy_DeduplicatesDirectUpdates: tests deduplication in strategy phase
- Existing TestDeduplicateDependencies and TestCheckAPICompatibility provide comprehensive coverage

All tests pass (264 in golang package, all tests pass across project)
@kbsteere kbsteere changed the title feat: detect transitive dependency requirements feat: detect transitive deps, deduplicate packages, and flag API compatibility issues Apr 13, 2026
Comment thread omnibump Outdated
Comment thread pkg/languages/golang/indirect_resolver.go
Comment thread pkg/languages/golang/runner.go Outdated
Comment thread docs/transitive-detection-process.md Outdated
The transitive-dependency-detection.md file covers the same content and is
referenced throughout the documentation. Consolidating to a single doc.
Implement goModCache to cache fetched go.mod files during analysis. This
significantly reduces HTTP round trips when checking API compatibility across
multiple packages. With 50 direct dependencies and 10 packages being updated:
- Before: 500 HTTP requests (10 × 50)
- After: ~50-100 HTTP requests (cached after first fetch)

Addresses tcnghia's concern about excessive HTTP round trips.
Replace sequential HTTP calls with batch pre-fetching that dramatically reduces
total HTTP requests. Instead of fetching dependencies separately for each package
being updated, collect all unique dependencies upfront and fetch them once in
parallel with a semaphore-limited worker pool.

Impact with 50 direct dependencies and 10 packages being updated:
- Before: 500 sequential HTTP calls (10 packages × 50 dependencies)
- After: ~50 parallel HTTP calls (all dependencies fetched once)

Uses semaphore pooling to limit concurrency to 10 parallel requests and avoid
overwhelming the Go module proxy.
@kbsteere kbsteere requested review from a team and tcnghia April 14, 2026 15:02
Add TestGoModCache_BasicOperations to verify the caching mechanism works
correctly for storing and retrieving go.mod files. Tests verify:
- Cache starts empty
- Key generation with package@version format
- Values can be stored and retrieved
- Multiple packages can be cached independently

Signed-off-by: Kyle Steere <kyle.steere@chainguard.dev>
@kbsteere kbsteere force-pushed the missing-recommended-package-updates branch from f9e6b69 to 6596db1 Compare April 14, 2026 15:03
Comment thread pkg/languages/golang/indirect_resolver.go Outdated
Comment thread pkg/languages/golang/indirect_resolver.go Outdated
Comment thread pkg/languages/golang/indirect_resolver.go Outdated
…rency

Remove goroutine/semaphore pattern from preFetchDependencies. The concurrent fetching adds complexity without meaningful performance benefit since we call preFetchDependencies once per analysis.

Changes:
- Convert goModCache from struct wrapper to idiomatic type alias
- Change from *goModCache pointer to value type throughout
- Replace complex goroutine+semaphore pattern with simple sequential loop
- Update method receivers from pointer to value receivers (correct for map types)
- Remove obsolete TODO about batching (now addressed by batch pre-fetch)
- Reduce code from 51 lines removed, 29 added (net -22 lines)

Still achieves 90% reduction in HTTP calls (50 vs 500) through batch pre-fetching.
All 266 tests pass.
@kbsteere kbsteere requested a review from tcnghia April 15, 2026 16:27
@kbsteere kbsteere merged commit 29c353c into chainguard-dev:main Apr 15, 2026
8 checks passed
@kbsteere kbsteere deleted the missing-recommended-package-updates branch April 15, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants