Skip to content

Commit 6fc5ed6

Browse files
authored
Merge pull request #19 from git-pkgs/use-pom-resolver
Use git-pkgs/pom for Maven effective-POM resolution
2 parents b5d86c5 + 244d343 commit 6fc5ed6

4 files changed

Lines changed: 130 additions & 171 deletions

File tree

go.mod

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ go 1.25.6
44

55
require (
66
github.com/cenk/backoff v2.2.1+incompatible
7-
github.com/git-pkgs/purl v0.1.10
7+
github.com/git-pkgs/pom v0.1.2
8+
github.com/git-pkgs/purl v0.1.11
89
github.com/git-pkgs/spdx v0.1.2
910
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529
1011
github.com/rubyist/circuitbreaker v2.2.1+incompatible
1112
)
1213

1314
require (
1415
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
15-
github.com/git-pkgs/packageurl-go v0.3.1 // indirect
1616
github.com/git-pkgs/vers v0.2.4 // indirect
1717
github.com/github/go-spdx/v2 v2.4.0 // indirect
18+
github.com/package-url/packageurl-go v0.1.6 // indirect
1819
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea // indirect
1920
golang.org/x/sync v0.20.0 // indirect
2021
)
21-
22-
replace github.com/package-url/packageurl-go => github.com/git-pkgs/packageurl-go v0.0.0-20260115093137-a0c26f7ee19e

go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
44
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
66
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
7-
github.com/git-pkgs/packageurl-go v0.3.1 h1:WM3RBABQZLaRBxgKyYughc3cVBE8KyQxbSC6Jt5ak7M=
8-
github.com/git-pkgs/packageurl-go v0.3.1/go.mod h1:rcIxiG37BlQLB6FZfgdj9Fm7yjhRQd3l+5o7J0QPAk4=
9-
github.com/git-pkgs/purl v0.1.10 h1:NMjeF10nzFn3tdQlz6rbmHB+i+YkyrFQxho3e33ePTQ=
10-
github.com/git-pkgs/purl v0.1.10/go.mod h1:C5Vp/kyZ/wGckCLexx4wPVfUxEiToRkdsOPh5Z7ig/I=
7+
github.com/git-pkgs/pom v0.1.2 h1:Y2hn8G9p88odySbELABvJtcBXogjwSnRgNJARDRigz8=
8+
github.com/git-pkgs/pom v0.1.2/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk=
9+
github.com/git-pkgs/purl v0.1.11 h1:VS3tux2BHgOZEg/uhrbOUyoX0NSEPEfhEuF7EbzOAOM=
10+
github.com/git-pkgs/purl v0.1.11/go.mod h1:jCXDpAOwFQZR27ALTaOLH1ByhEgkcC+06Hp9uzh02ZE=
1111
github.com/git-pkgs/spdx v0.1.2 h1:wHSK+CqFsO5N7yDTPvxDmer5LgNEa7vAsiZhi5Aci0A=
1212
github.com/git-pkgs/spdx v0.1.2/go.mod h1:V98MgZapNgYw54/pdGR82d7RU93qzJoybahbpZqTfw8=
1313
github.com/git-pkgs/vers v0.2.4 h1:Zr3jR/Xf1i/6cvBaJKPxhCwjzqz7uvYHE0Fhid/GPBk=
1414
github.com/git-pkgs/vers v0.2.4/go.mod h1:biTbSQK1qdbrsxDEKnqe3Jzclxz8vW6uDcwKjfUGcOo=
1515
github.com/github/go-spdx/v2 v2.4.0 h1:+4IwVwJJbm3rzvrQ6P1nI9BDMcy3la4RchRy5uehV/M=
1616
github.com/github/go-spdx/v2 v2.4.0/go.mod h1:/5rwgS0txhGtRdUZwc02bTglzg6HK3FfuEbECKlK2Sg=
17+
github.com/package-url/packageurl-go v0.1.6 h1:YO3p6u1XmCUliivUg/qWphaY8vI6hxSnnPv7Bfg3m5M=
18+
github.com/package-url/packageurl-go v0.1.6/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
1719
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea h1:sKwxy1H95npauwu8vtF95vG/syrL0p8fSZo/XlDg5gk=
1820
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg=
1921
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

internal/maven/maven.go

Lines changed: 68 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ package maven
44
import (
55
"context"
66
"encoding/xml"
7+
"errors"
78
"fmt"
89
"net/url"
910
"strings"
1011
"time"
1112

13+
"github.com/git-pkgs/pom"
1214
"github.com/git-pkgs/registries/internal/core"
1315
"github.com/git-pkgs/registries/internal/urlparser"
1416
)
1517

1618
const (
17-
DefaultURL = "https://repo1.maven.org/maven2"
18-
SearchURL = "https://search.maven.org"
19-
ecosystem = "maven"
20-
maxParentDepth = 5
19+
DefaultURL = "https://repo1.maven.org/maven2"
20+
SearchURL = "https://search.maven.org"
21+
ecosystem = "maven"
2122
// minCoordParts is the minimum number of parts in a Maven coordinate (group:artifact)
2223
minCoordParts = 2
2324
// coordPartsWithVersion is the number of parts when version is included (group:artifact:version)
@@ -34,6 +35,7 @@ type Registry struct {
3435
baseURL string
3536
searchURL string
3637
client *core.Client
38+
resolver *pom.Resolver
3739
urls *URLs
3840
}
3941

@@ -47,9 +49,30 @@ func New(baseURL string, client *core.Client) *Registry {
4749
client: client,
4850
}
4951
r.urls = &URLs{baseURL: r.baseURL}
52+
r.resolver = pom.NewResolver(pom.NewCachingFetcher(&clientFetcher{client: client, baseURL: r.baseURL}))
5053
return r
5154
}
5255

56+
// clientFetcher adapts core.Client to pom.Fetcher so the resolver's HTTP
57+
// goes through the same rate limiting, retries and circuit breaker as
58+
// every other registry call.
59+
type clientFetcher struct {
60+
client *core.Client
61+
baseURL string
62+
}
63+
64+
func (f *clientFetcher) Fetch(ctx context.Context, gav pom.GAV) (*pom.POM, error) {
65+
body, err := f.client.GetBody(ctx, pom.POMURL(f.baseURL, gav))
66+
if err != nil {
67+
return nil, err
68+
}
69+
return pom.ParsePOM(body)
70+
}
71+
72+
func (r *Registry) effectivePOM(ctx context.Context, groupID, artifactID, version string) (*pom.EffectivePOM, error) {
73+
return r.resolver.Resolve(ctx, pom.GAV{GroupID: groupID, ArtifactID: artifactID, Version: version}, pom.Options{})
74+
}
75+
5376
func (r *Registry) Ecosystem() string {
5477
return ecosystem
5578
}
@@ -69,58 +92,18 @@ type searchResponseBody struct {
6992
}
7093

7194
type searchDoc struct {
72-
ID string `json:"id"`
73-
GroupID string `json:"g"`
74-
ArtifactID string `json:"a"`
75-
Version string `json:"latestVersion"`
76-
Timestamp int64 `json:"timestamp"`
77-
VersionCount int `json:"versionCount"`
95+
ID string `json:"id"`
96+
GroupID string `json:"g"`
97+
ArtifactID string `json:"a"`
98+
Version string `json:"latestVersion"`
99+
Timestamp int64 `json:"timestamp"`
100+
VersionCount int `json:"versionCount"`
78101
}
79102

80-
// POM XML structures
81-
type pomXML struct {
82-
XMLName xml.Name `xml:"project"`
83-
GroupID string `xml:"groupId"`
84-
ArtifactID string `xml:"artifactId"`
85-
Version string `xml:"version"`
86-
Name string `xml:"name"`
87-
Description string `xml:"description"`
88-
URL string `xml:"url"`
89-
Licenses []pomLicense `xml:"licenses>license"`
90-
SCM pomSCM `xml:"scm"`
91-
Parent *pomParent `xml:"parent"`
92-
Dependencies []pomDep `xml:"dependencies>dependency"`
93-
DependencyManagement struct {
94-
Dependencies []pomDep `xml:"dependencies>dependency"`
95-
} `xml:"dependencyManagement"`
103+
// pomDevelopers is the minimal XML shape needed for FetchMaintainers; the
104+
// pom library doesn't carry <developers>.
105+
type pomDevelopers struct {
96106
Developers []pomDeveloper `xml:"developers>developer"`
97-
Properties map[string]string
98-
}
99-
100-
type pomParent struct {
101-
GroupID string `xml:"groupId"`
102-
ArtifactID string `xml:"artifactId"`
103-
Version string `xml:"version"`
104-
}
105-
106-
type pomLicense struct {
107-
Name string `xml:"name"`
108-
URL string `xml:"url"`
109-
}
110-
111-
type pomSCM struct {
112-
URL string `xml:"url"`
113-
Connection string `xml:"connection"`
114-
DevConnection string `xml:"developerConnection"`
115-
}
116-
117-
type pomDep struct {
118-
GroupID string `xml:"groupId"`
119-
ArtifactID string `xml:"artifactId"`
120-
Version string `xml:"version"`
121-
Scope string `xml:"scope"`
122-
Optional string `xml:"optional"`
123-
Type string `xml:"type"`
124107
}
125108

126109
type pomDeveloper struct {
@@ -171,8 +154,8 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
171154
if err := r.client.GetJSON(ctx, searchURL, &searchResp); err == nil && searchResp.Response.NumFound > 0 {
172155
doc := searchResp.Response.Docs[0]
173156
// Fetch the POM for more details
174-
pom, _ := r.fetchPOM(ctx, groupID, artifactID, doc.Version, 0)
175-
return r.packageFromSearchAndPOM(doc, pom), nil
157+
ep, _ := r.effectivePOM(ctx, groupID, artifactID, doc.Version)
158+
return r.packageFromSearchAndPOM(doc, ep), nil
176159
}
177160

178161
// Fallback: try to get maven-metadata.xml
@@ -198,8 +181,8 @@ func (r *Registry) FetchPackage(ctx context.Context, name string) (*core.Package
198181
latestVersion = metadata.Versioning.Versions[len(metadata.Versioning.Versions)-1]
199182
}
200183

201-
pom, _ := r.fetchPOM(ctx, groupID, artifactID, latestVersion, 0)
202-
return r.packageFromMetadataAndPOM(metadata, pom), nil
184+
ep, _ := r.effectivePOM(ctx, groupID, artifactID, latestVersion)
185+
return r.packageFromMetadataAndPOM(metadata, ep), nil
203186
}
204187

205188
type mavenMetadata struct {
@@ -214,62 +197,7 @@ type versioning struct {
214197
Versions []string `xml:"versions>version"`
215198
}
216199

217-
func (r *Registry) fetchPOM(ctx context.Context, groupID, artifactID, version string, depth int) (*pomXML, error) {
218-
if depth > maxParentDepth {
219-
return nil, fmt.Errorf("max parent depth exceeded")
220-
}
221-
222-
pomURL := fmt.Sprintf("%s/%s/%s/%s/%s-%s.pom",
223-
r.baseURL, groupIDToPath(groupID), artifactID, version, artifactID, version)
224-
225-
body, err := r.client.GetBody(ctx, pomURL)
226-
if err != nil {
227-
return nil, err
228-
}
229-
230-
var pom pomXML
231-
if err := xml.Unmarshal(body, &pom); err != nil {
232-
return nil, err
233-
}
234-
235-
// Resolve parent POM if present
236-
if pom.Parent != nil && depth < maxParentDepth {
237-
parentPOM, err := r.fetchPOM(ctx, pom.Parent.GroupID, pom.Parent.ArtifactID, pom.Parent.Version, depth+1)
238-
if err == nil {
239-
mergePOMs(&pom, parentPOM)
240-
}
241-
}
242-
243-
// Fill in groupID/version from parent if not set
244-
if pom.GroupID == "" && pom.Parent != nil {
245-
pom.GroupID = pom.Parent.GroupID
246-
}
247-
if pom.Version == "" && pom.Parent != nil {
248-
pom.Version = pom.Parent.Version
249-
}
250-
251-
return &pom, nil
252-
}
253-
254-
func mergePOMs(child, parent *pomXML) {
255-
if child.Description == "" {
256-
child.Description = parent.Description
257-
}
258-
if child.URL == "" {
259-
child.URL = parent.URL
260-
}
261-
if len(child.Licenses) == 0 {
262-
child.Licenses = parent.Licenses
263-
}
264-
if child.SCM.URL == "" {
265-
child.SCM = parent.SCM
266-
}
267-
if len(child.Developers) == 0 {
268-
child.Developers = parent.Developers
269-
}
270-
}
271-
272-
func (r *Registry) packageFromSearchAndPOM(doc searchDoc, pom *pomXML) *core.Package {
200+
func (r *Registry) packageFromSearchAndPOM(doc searchDoc, ep *pom.EffectivePOM) *core.Package {
273201
pkg := &core.Package{
274202
Name: fmt.Sprintf("%s:%s", doc.GroupID, doc.ArtifactID),
275203
Namespace: doc.GroupID,
@@ -280,17 +208,11 @@ func (r *Registry) packageFromSearchAndPOM(doc searchDoc, pom *pomXML) *core.Pac
280208
},
281209
}
282210

283-
if pom != nil {
284-
pkg.Description = pom.Description
285-
pkg.Homepage = pom.URL
286-
pkg.Repository = extractRepository(pom)
287-
pkg.Licenses = formatLicenses(pom.Licenses)
288-
}
289-
211+
applyPOMMetadata(pkg, ep)
290212
return pkg
291213
}
292214

293-
func (r *Registry) packageFromMetadataAndPOM(metadata mavenMetadata, pom *pomXML) *core.Package {
215+
func (r *Registry) packageFromMetadataAndPOM(metadata mavenMetadata, ep *pom.EffectivePOM) *core.Package {
294216
pkg := &core.Package{
295217
Name: fmt.Sprintf("%s:%s", metadata.GroupID, metadata.ArtifactID),
296218
Namespace: metadata.GroupID,
@@ -300,26 +222,22 @@ func (r *Registry) packageFromMetadataAndPOM(metadata mavenMetadata, pom *pomXML
300222
},
301223
}
302224

303-
if pom != nil {
304-
pkg.Description = pom.Description
305-
pkg.Homepage = pom.URL
306-
pkg.Repository = extractRepository(pom)
307-
pkg.Licenses = formatLicenses(pom.Licenses)
308-
}
309-
225+
applyPOMMetadata(pkg, ep)
310226
return pkg
311227
}
312228

313-
func extractRepository(pom *pomXML) string {
314-
return urlparser.FirstRepoURL(pom.SCM.URL, pom.SCM.Connection)
315-
}
316-
317-
func formatLicenses(licenses []pomLicense) string {
318-
names := make([]string, len(licenses))
319-
for i, l := range licenses {
229+
func applyPOMMetadata(pkg *core.Package, ep *pom.EffectivePOM) {
230+
if ep == nil {
231+
return
232+
}
233+
pkg.Description = ep.Description
234+
pkg.Homepage = ep.URL
235+
pkg.Repository = urlparser.FirstRepoURL(ep.SCM.URL, ep.SCM.Connection)
236+
names := make([]string, len(ep.Licenses))
237+
for i, l := range ep.Licenses {
320238
names[i] = l.Name
321239
}
322-
return strings.Join(names, ",")
240+
pkg.Licenses = strings.Join(names, ",")
323241
}
324242

325243
func (r *Registry) FetchVersions(ctx context.Context, name string) ([]core.Version, error) {
@@ -381,28 +299,26 @@ func (r *Registry) FetchDependencies(ctx context.Context, name, version string)
381299
return nil, fmt.Errorf("invalid Maven coordinate: %s (expected groupId:artifactId)", name)
382300
}
383301

384-
pom, err := r.fetchPOM(ctx, groupID, artifactID, version, 0)
302+
ep, err := r.effectivePOM(ctx, groupID, artifactID, version)
385303
if err != nil {
386-
if httpErr, ok := err.(*core.HTTPError); ok && httpErr.IsNotFound() {
304+
var httpErr *core.HTTPError
305+
if errors.As(err, &httpErr) && httpErr.IsNotFound() {
387306
return nil, &core.NotFoundError{Ecosystem: ecosystem, Name: name, Version: version}
388307
}
389308
return nil, err
390309
}
391310

392-
var deps []core.Dependency
393-
for _, d := range pom.Dependencies {
311+
deps := make([]core.Dependency, 0, len(ep.Dependencies))
312+
for _, d := range ep.Dependencies {
394313
scope := mapMavenScope(d.Scope)
395-
optional := d.Optional == "true"
396-
397-
if optional {
314+
if d.Optional {
398315
scope = core.Optional
399316
}
400-
401317
deps = append(deps, core.Dependency{
402318
Name: fmt.Sprintf("%s:%s", d.GroupID, d.ArtifactID),
403319
Requirements: d.Version,
404320
Scope: scope,
405-
Optional: optional,
321+
Optional: d.Optional,
406322
})
407323
}
408324

@@ -440,13 +356,17 @@ func (r *Registry) FetchMaintainers(ctx context.Context, name string) ([]core.Ma
440356
}
441357

442358
latestVersion := versions[0].Number
443-
pom, err := r.fetchPOM(ctx, groupID, artifactID, latestVersion, 0)
359+
body, err := r.client.GetBody(ctx, pom.POMURL(r.baseURL, pom.GAV{GroupID: groupID, ArtifactID: artifactID, Version: latestVersion}))
444360
if err != nil {
445361
return nil, err
446362
}
363+
var devs pomDevelopers
364+
if err := xml.Unmarshal(body, &devs); err != nil {
365+
return nil, err
366+
}
447367

448-
maintainers := make([]core.Maintainer, len(pom.Developers))
449-
for i, dev := range pom.Developers {
368+
maintainers := make([]core.Maintainer, len(devs.Developers))
369+
for i, dev := range devs.Developers {
450370
maintainers[i] = core.Maintainer{
451371
UUID: dev.ID,
452372
Login: dev.ID,

0 commit comments

Comments
 (0)