Commit 5392bee
authored
fix(golang): reduce required co-package updates to minimal necessary set (#63)
* feat(golang): reduce required co-package updates to minimal necessary set
- Filter CheckTransitiveRequirements to only flag direct project deps;
indirect deps are resolved automatically by Go's MVS and cannot cause
API breakage in the project's own code
- Add FindVersionGroupPackages to detect tightly-coupled module ecosystems
(e.g. all go.opentelemetry.io/otel/* packages) using module family prefix
heuristics, covering both co-released and version-drifted siblings
- Add second API compat pass over discovered co-updates so community
packages that import a co-updated dep (e.g. otelgrpc importing otel)
are surfaced as API compat alerts
* feat(golang): find minimum compatible version for API compat alerts
When CheckAPICompatibilityWithCache flags a package (e.g. go-ldap/ldap/v3)
that imports a dep being updated (e.g. go-ntlmssp), walk the package's
version history to find the lowest release whose go.mod requires the
updated dep at >= the target version.
This converts API compat alerts from no-op suggestions (pkg@current) into
actionable version recommendations (pkg@min-compatible), directly addressing
build failures where a dep's API signature changed between versions.
* feat(golang): add API surface diff to detect genuine breaking changes
Add CheckAPIBreakingChanges which compares the exported API of a package
between two versions using golang.org/x/exp/apidiff. This provides a
precise signal for whether a dep version bump introduces incompatible
changes (e.g. ProcessChallenge gaining a fourth argument in go-ntlmssp),
as opposed to compatible additions (new functions, new fields).
The function type-checks both versions using golang.org/x/tools/go/packages
in isolated temp modules, then runs apidiff.Changes to classify each
exported symbol change as compatible or incompatible.
This can be used alongside findMinCompatibleVersion and
CheckAPICompatibilityWithCache to distinguish false-positive compat alerts
from confirmed breaking changes that require downstream packages to update.
* fix(golang): skip main module and detect unavailable package versions
- resolveAndFilterPackages now skips any package whose path matches the
main module of the go.mod being analyzed, logging a warning instead of
letting it reach gobump where it would fail with 'bumping the main
module is not allowed'
- checkMissingTransitiveDeps applies the same guard as a second line of
defence for co-updates discovered through transitive checks
- CheckAPIBreakingChanges now treats an unloadable new version (removed
package or internally broken release) as a breaking change rather than
returning an error, giving callers a clear signal not to bump to that
version
* chore(golang): fix golangci-lint violations
- Add sentinel errors errPackageNotFound and errNoTypeInfo to replace
dynamic fmt.Errorf calls in loadPackageTypes (err113)
- Wrap packages.Error with %w in loadPackageTypes (errorlint)
- Wrap defer os.RemoveAll in closure discarding return value (errcheck)
- Fix stdlib import grouping for go/types and errors (gci, gofumpt)
- Extract runCoUpdateAPICompatChecks helper from checkMissingTransitiveDeps
to reduce cyclomatic complexity below threshold (gocyclo)
* refactor(golang): extract mainModulePath helper to remove duplication
* chore: go mod tidy
* refactor(golang): consolidate co-update detection into shared detectCoUpdates
Extract the co-update detection core from checkMissingTransitiveDeps into
a new detectCoUpdates function so both the update path and analyzer path
use identical logic.
Previously checkTransitiveRequirementsForStrategy in analyzer.go had its
own diverged implementation: it used the deprecated CheckAPICompatibility,
had no version-group ecosystem detection, no findMinCompatibleVersion, no
main module skip, and included indirect project deps in version checks.
Both paths now share: FindVersionGroupPackages for ecosystem grouping,
CheckAPICompatibilityWithCache with findMinCompatibleVersion for actionable
version recommendations, main module skip, direct-only dep filtering, and
the second API compat pass for co-update deps.
* fix(golang): apply main module skip in analyze path
RecommendStrategy was adding the main module to strategy.DirectUpdates
without checking whether the package being updated is the go.mod's own
module. The update path had this guard in resolveAndFilterPackages; now
both paths produce consistent output.
* fix(golang): surface API compat co-updates in all output types
When findMinCompatibleVersion identifies a concrete minimum version for
an API compat alert (e.g. otelgrpc@v0.68.0 when bumping otel to v1.43.0),
add it to strategy.DirectUpdates so it appears in JSON, YAML, and deps
file output — not just as a human-readable warning string.
Packages already being updated are skipped to avoid duplicates.
* chore(golang): add map capacity and eliminate redundant go.mod parse
- Set initial capacity on allMissingDeps and apiCompatibilityAlerts in
detectCoUpdates to avoid realloc when co-updates match update count
- Parse go.mod once in RecommendStrategy and pass it to
checkTransitiveRequirementsForStrategy to avoid reading the same file
twice; fall back to parsing internally when caller passes nil
* docs(golang): document why GONOSUMCHECK is set in loadPackageTypes
* fix(golang): skip version group packages on different major version track
Packages like go.opentelemetry.io/otel/exporters/* use a v0.x cadence
while core otel uses v1.x. FindVersionGroupPackages was including them
in the version group and recommending them at the wrong target version
(e.g. v1.43.0 instead of v0.19.0).
Skip version group candidates whose current major differs from the target
major. The second-pass API compat check handles these packages correctly
via findMinCompatibleVersion, which finds the right v0.x version.
* test(golang): regression tests for cross-major version group handling
Add TestDetectCoUpdates_CrossMajorVersionGroupSkipped to guard against
recommending the wrong version for otel exporter packages. The otel
exporter family (go.opentelemetry.io/otel/exporters/*) uses v0.x while
core otel uses v1.x — the fix must find v0.19.0 via the API compat chain
rather than v1.43.0 from the version group.
Add a FindVersionGroupPackages test case documenting that the function
itself returns cross-major family members; the filter lives in
detectCoUpdates.
* fix(golang): exclude cross-major packages from version group in FindVersionGroupPackages
Move the cross-major version guard into FindVersionGroupPackages itself so
that all callers are protected, not just detectCoUpdates. Previously,
otel/exporters/prometheus@v0.60.0 would be included in the version group
when bumping core otel to v1.43.0, and the function would return it with
a target of v1.43.0 — a version that does not exist, causing go mod tidy
to fail.
The fix compares each candidate's semver major against the source package's
current version major and excludes any mismatch. The second-pass API compat
chain (findMinCompatibleVersion) then finds the correct v0.x version instead.
Removes the now-redundant duplicate guard that was in detectCoUpdates.
* fix(golang): actively resolve correct version for cross-major family packages
When FindVersionGroupPackages returns a package on a different major
version track (e.g. otel/exporters/prometheus v0.60.0 when targeting
otel v1.43.0), detectCoUpdates now calls findMinCompatibleVersion to
find the correct v0.x version (e.g. v0.65.0) rather than either
applying the wrong v1.x target or relying on the second-pass API compat
chain to catch it.
FindVersionGroupPackages returns all family members regardless of major
version — the caller is responsible for resolving the right version.
* chore(golang): fix log message and reason string for cross-major co-updates
- Move familyRoot declaration inside the cross-major branch where it is used
- Remove duplicate familyRoot argument from log message
- Use distinct Reason string for cross-major packages so it doesn't
incorrectly say 'both at X' when the versions differ1 parent 96a8f2e commit 5392bee
7 files changed
Lines changed: 877 additions & 124 deletions
File tree
- pkg/languages/golang
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
17 | 19 | | |
18 | 20 | | |
19 | 21 | | |
| |||
40 | 42 | | |
41 | 43 | | |
42 | 44 | | |
43 | | - | |
44 | | - | |
| 45 | + | |
| 46 | + | |
45 | 47 | | |
| 48 | + | |
46 | 49 | | |
47 | 50 | | |
48 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
75 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
76 | 78 | | |
77 | | - | |
78 | | - | |
| 79 | + | |
| 80 | + | |
79 | 81 | | |
80 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
81 | 89 | | |
82 | 90 | | |
83 | 91 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
377 | 378 | | |
378 | 379 | | |
379 | 380 | | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
380 | 392 | | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
381 | 398 | | |
382 | 399 | | |
383 | 400 | | |
| |||
399 | 416 | | |
400 | 417 | | |
401 | 418 | | |
402 | | - | |
403 | | - | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
404 | 422 | | |
405 | 423 | | |
406 | 424 | | |
| |||
417 | 435 | | |
418 | 436 | | |
419 | 437 | | |
| 438 | + | |
420 | 439 | | |
421 | 440 | | |
422 | 441 | | |
423 | | - | |
424 | | - | |
425 | | - | |
426 | | - | |
427 | | - | |
428 | | - | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | | - | |
433 | | - | |
434 | | - | |
435 | | - | |
436 | | - | |
437 | | - | |
438 | | - | |
439 | | - | |
440 | | - | |
441 | | - | |
442 | | - | |
443 | | - | |
444 | | - | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
453 | 448 | | |
454 | | - | |
455 | | - | |
456 | | - | |
| 449 | + | |
| 450 | + | |
457 | 451 | | |
458 | | - | |
459 | | - | |
460 | | - | |
461 | | - | |
462 | | - | |
463 | | - | |
464 | | - | |
465 | | - | |
466 | | - | |
| 452 | + | |
| 453 | + | |
467 | 454 | | |
| 455 | + | |
468 | 456 | | |
469 | | - | |
470 | | - | |
471 | | - | |
472 | | - | |
473 | | - | |
474 | | - | |
475 | | - | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
482 | | - | |
483 | | - | |
484 | | - | |
485 | | - | |
486 | | - | |
487 | | - | |
488 | | - | |
489 | | - | |
490 | | - | |
491 | | - | |
492 | | - | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
493 | 460 | | |
494 | 461 | | |
495 | | - | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
496 | 465 | | |
497 | 466 | | |
498 | 467 | | |
499 | | - | |
500 | 468 | | |
501 | 469 | | |
502 | 470 | | |
| |||
515 | 483 | | |
516 | 484 | | |
517 | 485 | | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
518 | 540 | | |
519 | 541 | | |
520 | 542 | | |
| |||
0 commit comments