fix(java): add hash of GAV+root pom file path for pkgID for packages from pom.xml files (#9880)

Co-authored-by: Teppei Fukuda <knqyf263@gmail.com>
This commit is contained in:
DmitriyLewen
2026-01-15 13:31:21 +06:00
committed by GitHub
parent 5fced3ae49
commit 809db46231
21 changed files with 1051 additions and 424 deletions

View File

@@ -13,9 +13,11 @@ import (
"path/filepath"
"slices"
"sort"
"strconv"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/hashstructure/v2"
"github.com/samber/lo"
"golang.org/x/net/html/charset"
"golang.org/x/xerrors"
@@ -124,7 +126,9 @@ func (p *Parser) Parse(ctx context.Context, r xio.ReadSeekerAt) ([]ftypes.Packag
}
// Analyze root POM
result, err := p.analyze(ctx, root, analysisOptions{})
result, err := p.analyze(ctx, root, analysisOptions{
rootFilePath: p.rootPath,
})
if err != nil {
return nil, nil, xerrors.Errorf("analyze error (%s): %w", p.rootPath, err)
}
@@ -134,12 +138,17 @@ func (p *Parser) Parse(ctx context.Context, r xio.ReadSeekerAt) ([]ftypes.Packag
rootArt := root.artifact()
rootArt.Relationship = ftypes.RelationshipRoot
rootArt.RootFilePath = p.rootPath
return p.parseRoot(ctx, rootArt, set.New[string]())
}
// nolint: gocyclo
func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.Set[string]) ([]ftypes.Package, []ftypes.Dependency, error) {
if root.RootFilePath == "" {
return nil, nil, xerrors.New("root file path is required for package ID generation")
}
// Prepare a queue for dependencies
queue := newArtifactQueue()
@@ -162,10 +171,11 @@ func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.S
// Modules should be handled separately so that they can have independent dependencies.
// It means multi-module allows for duplicate dependencies.
if art.Module {
if uniqModules.Contains(art.String()) {
id := packageID(art.Name(), art.Version.String(), art.RootFilePath)
if uniqModules.Contains(id) {
continue
}
uniqModules.Append(art.String())
uniqModules.Append(id)
modulePkgs, moduleDeps, err := p.parseRoot(ctx, art, uniqModules)
if err != nil {
@@ -246,14 +256,14 @@ func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.S
dependsOn := xslices.Map(result.dependencies, func(a artifact) string {
return a.Name()
})
uniqDeps[packageID(art.Name(), art.Version.String())] = dependsOn
uniqDeps[packageID(art.Name(), art.Version.String(), root.RootFilePath)] = dependsOn
}
}
// Convert to []ftypes.Package and []ftypes.Dependency
for name, art := range uniqArtifacts {
pkg := ftypes.Package{
ID: packageID(name, art.Version.String()),
ID: packageID(name, art.Version.String(), root.RootFilePath),
Name: name,
Version: art.Version.String(),
Licenses: art.Licenses,
@@ -265,7 +275,7 @@ func (p *Parser) parseRoot(ctx context.Context, root artifact, uniqModules set.S
// Convert dependency names into dependency IDs
dependsOn := lo.FilterMap(uniqDeps[pkg.ID], func(dependOnName string, _ int) (string, bool) {
ver := depVersion(dependOnName, uniqArtifacts)
return packageID(dependOnName, ver), ver != ""
return packageID(dependOnName, ver, root.RootFilePath), ver != ""
})
// `mvn` shows modules separately from the root package and does not show module nesting.
@@ -304,13 +314,16 @@ func (p *Parser) parseModule(ctx context.Context, currentPath, relativePath stri
return artifact{}, xerrors.Errorf("unable to open the relative path: %w", err)
}
result, err := p.analyze(ctx, module, analysisOptions{})
result, err := p.analyze(ctx, module, analysisOptions{
rootFilePath: module.filePath,
})
if err != nil {
return artifact{}, xerrors.Errorf("analyze error: %w", err)
}
moduleArtifact := module.artifact()
moduleArtifact.Module = true
moduleArtifact.RootFilePath = module.filePath
moduleArtifact.Relationship = ftypes.RelationshipWorkspace
p.cache.put(moduleArtifact, result)
@@ -344,6 +357,7 @@ func (p *Parser) resolve(ctx context.Context, art artifact, rootDepManagement []
result, err := p.analyze(ctx, pomContent, analysisOptions{
exclusions: art.Exclusions,
depManagement: rootDepManagement,
rootFilePath: art.RootFilePath,
})
if err != nil {
return analysisResult{}, xerrors.Errorf("analyze error: %w", err)
@@ -365,6 +379,7 @@ type analysisResult struct {
type analysisOptions struct {
exclusions set.Set[string]
depManagement []pomDependency // from the root POM
rootFilePath string // File path of the root POM or module POM
}
func (p *Parser) analyze(ctx context.Context, pom *pom, opts analysisOptions) (analysisResult, error) {
@@ -394,9 +409,12 @@ func (p *Parser) analyze(ctx context.Context, pom *pom, opts analysisOptions) (a
}
deps = p.filterDependencies(deps, opts.exclusions)
art := pom.artifact()
art.RootFilePath = opts.rootFilePath
return analysisResult{
filePath: pom.filePath,
artifact: pom.artifact(),
artifact: art,
dependencies: deps,
dependencyManagement: depManagement,
properties: props,
@@ -876,8 +894,22 @@ func parseMavenMetadata(r io.Reader) (*Metadata, error) {
return parsed, nil
}
func packageID(name, version string) string {
return dependency.ID(ftypes.Pom, name, version)
func packageID(name, version, pomFilePath string) string {
gav := dependency.ID(ftypes.Pom, name, version)
v := map[string]any{
"gav": gav,
"path": filepath.ToSlash(pomFilePath),
}
h, err := hashstructure.Hash(v, hashstructure.FormatV2, &hashstructure.HashOptions{
ZeroNil: true,
IgnoreZeroValue: true,
})
if err != nil {
log.Warn("Failed to calculate hash", log.Err(err))
return gav // fallback to GAV only
}
// Append 8-character hash suffix
return fmt.Sprintf("%s::%s", gav, strconv.FormatUint(h, 16)[:8])
}
// cf. https://github.com/apache/maven/blob/259404701402230299fe05ee889ecdf1c9dae816/maven-artifact/src/main/java/org/apache/maven/artifact/DefaultArtifact.java#L482-L486