Skip to content

Commit 16bff80

Browse files
authored
Add maven lockfile v2 support and fix bzlmod label handling (#158)
* Add maven lockfile v2 support and fix bzlmod label handling - Add LockfileV2 struct to parse rules_jvm_external v2 lockfile format - Update NewResolver to try v2 format first, fallback to v1 - Sort artifact IDs for deterministic putSymbol iteration - Add get_apparent_label() to convert canonical labels to apparent names (e.g., @@rules_jvm_external~5.3~maven -> @maven) - Change preferred_deps to string_keyed_label_dict for proper label handling - Rename CleanupLabel to CleanupLabelName for clarity - Remove unused _ijar attribute from java_index rule - Add unit tests for maven resolver * Use + for repo separator (default in bazel 8) * cleanup
1 parent eb9c9f5 commit 16bff80

8 files changed

Lines changed: 320 additions & 20 deletions

File tree

pkg/bazel/bazel.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var (
1919
nonWordRe = regexp.MustCompile(`\W+`)
2020
)
2121

22-
func CleanupLabel(in string) string {
22+
func CleanupLabelName(in string) string {
2323
return nonWordRe.ReplaceAllString(in, "_")
2424
}
2525

pkg/maven/config.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,38 @@ type Dep struct {
4141
URL string `json:"url,omitempty"`
4242
Exclusions []string `json:"exclusions,omitempty"`
4343
}
44+
45+
// LockfileV2 represents the maven2_install.json structure
46+
type LockfileV2 struct {
47+
InputArtifactsHash int64 `json:"__INPUT_ARTIFACTS_HASH"`
48+
ResolvedArtifactsHash int64 `json:"__RESOLVED_ARTIFACTS_HASH"`
49+
Version string `json:"version"`
50+
Artifacts map[string]ArtifactV2 `json:"artifacts"`
51+
Dependencies map[string][]string `json:"dependencies"`
52+
Packages map[string][]string `json:"packages"`
53+
Repositories map[string][]string `json:"repositories"`
54+
Services map[string]map[string][]string `json:"services"`
55+
ConflictResolution map[string]string `json:"conflict_resolution"`
56+
Skipped []string `json:"skipped"`
57+
}
58+
59+
// ArtifactV2 represents metadata for a single maven artifact
60+
type ArtifactV2 struct {
61+
Version string `json:"version"`
62+
Shasums map[string]string `json:"shasums"`
63+
}
64+
65+
func loadLockfileV2(filename string) (*LockfileV2, error) {
66+
f, err := os.Open(filename)
67+
if err != nil {
68+
return nil, err
69+
}
70+
defer f.Close()
71+
72+
var lf LockfileV2
73+
if err := json.NewDecoder(f).Decode(&lf); err != nil {
74+
return nil, err
75+
}
76+
77+
return &lf, nil
78+
}

pkg/maven/resolver.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package maven
33
import (
44
"errors"
55
"fmt"
6+
"log"
7+
"sort"
68

79
"github.com/bazelbuild/bazel-gazelle/label"
810
"github.com/stackb/scala-gazelle/pkg/bazel"
@@ -38,17 +40,30 @@ func NewResolver(installFile, mavenWorkspaceName, lang string, warn warnFunc, pu
3840
artifacts: make(map[string]label.Label),
3941
}
4042

43+
log.Println("loading configuration from:", installFile)
44+
45+
// Try v2 format first
46+
v2, err := loadLockfileV2(installFile)
47+
if err == nil && v2.Version == "2" {
48+
return newResolverFromV2(&r, v2, mavenWorkspaceName, putSymbol)
49+
}
50+
51+
// Fallback to v1 format
4152
c, err := loadConfiguration(installFile)
4253
if err != nil {
4354
return nil, fmt.Errorf("loading configuration %s: %w", installFile, err)
4455
}
4556

57+
return newResolverFromV1(&r, c, mavenWorkspaceName, putSymbol)
58+
}
59+
60+
func newResolverFromV1(r *mavenResolver, c *configFile, mavenWorkspaceName string, putSymbol putSymbolFunc) (Resolver, error) {
4661
for _, dep := range c.DependencyTree.Dependencies {
47-
c, err := ParseCoordinate(dep.Coord)
62+
coord, err := ParseCoordinate(dep.Coord)
4863
if err != nil {
4964
return nil, fmt.Errorf("failed to parse coordinate %v: %w", dep.Coord, err)
5065
}
51-
from := label.Label{Repo: mavenWorkspaceName, Name: bazel.CleanupLabel(c.ArtifactString())}
66+
from := label.Label{Repo: mavenWorkspaceName, Name: bazel.CleanupLabelName(coord.ArtifactString())}
5267
labelString := from.String()
5368
r.artifacts[dep.Coord] = from
5469

@@ -58,7 +73,30 @@ func NewResolver(installFile, mavenWorkspaceName, lang string, warn warnFunc, pu
5873
}
5974
}
6075

61-
return &r, nil
76+
return r, nil
77+
}
78+
79+
func newResolverFromV2(r *mavenResolver, lf *LockfileV2, mavenWorkspaceName string, putSymbol putSymbolFunc) (Resolver, error) {
80+
// Sort artifact IDs for deterministic iteration
81+
ids := make([]string, 0, len(lf.Packages))
82+
for id := range lf.Packages {
83+
ids = append(ids, id)
84+
}
85+
sort.Strings(ids)
86+
87+
for _, id := range ids {
88+
packages := lf.Packages[id]
89+
from := label.Label{Repo: mavenWorkspaceName, Name: bazel.CleanupLabelName(id)}
90+
labelString := from.String()
91+
r.artifacts[id] = from
92+
93+
for _, pkg := range packages {
94+
r.data.Add(pkg, labelString)
95+
putSymbol(resolver.NewSymbol(sppb.ImportType_PACKAGE, pkg, mavenWorkspaceName, from))
96+
}
97+
}
98+
99+
return r, nil
62100
}
63101

64102
func (r *mavenResolver) Name() string {

pkg/maven/resolver_test.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package maven
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/bazelbuild/bazel-gazelle/label"
9+
"github.com/google/go-cmp/cmp"
10+
"github.com/stackb/scala-gazelle/pkg/resolver"
11+
)
12+
13+
func TestNewResolver(t *testing.T) {
14+
for name, tc := range map[string]struct {
15+
content string
16+
wantLabels []string
17+
}{
18+
"v1 format": {
19+
content: `{
20+
"dependency_tree": {
21+
"dependencies": [
22+
{
23+
"coord": "xml-apis:xml-apis:1.4.01",
24+
"dependencies": [],
25+
"directDependencies": [],
26+
"file": "xml-apis-1.4.01.jar",
27+
"packages": ["javax.xml", "javax.xml.parsers"],
28+
"sha256": "abc123"
29+
}
30+
],
31+
"version": "0.1.0"
32+
}
33+
}`,
34+
wantLabels: []string{
35+
"@maven//:xml_apis_xml_apis",
36+
"@maven//:xml_apis_xml_apis",
37+
},
38+
},
39+
"v2 format": {
40+
content: `{
41+
"__INPUT_ARTIFACTS_HASH": 123,
42+
"__RESOLVED_ARTIFACTS_HASH": 456,
43+
"version": "2",
44+
"artifacts": {
45+
"xml-apis:xml-apis": {
46+
"version": "1.4.01",
47+
"shasums": {"jar": "abc123"}
48+
}
49+
},
50+
"packages": {
51+
"xml-apis:xml-apis": ["javax.xml", "javax.xml.parsers"]
52+
},
53+
"dependencies": {},
54+
"repositories": {},
55+
"services": {},
56+
"conflict_resolution": {},
57+
"skipped": []
58+
}`,
59+
wantLabels: []string{
60+
"@maven//:xml_apis_xml_apis",
61+
"@maven//:xml_apis_xml_apis",
62+
},
63+
},
64+
} {
65+
t.Run(name, func(t *testing.T) {
66+
tmpDir := t.TempDir()
67+
installFile := filepath.Join(tmpDir, "maven_install.json")
68+
if err := os.WriteFile(installFile, []byte(tc.content), 0644); err != nil {
69+
t.Fatal(err)
70+
}
71+
72+
var gotSymbols []*resolver.Symbol
73+
putSymbol := func(s *resolver.Symbol) error {
74+
gotSymbols = append(gotSymbols, s)
75+
return nil
76+
}
77+
78+
r, err := NewResolver(installFile, "maven", "scala", func(format string, args ...interface{}) {}, putSymbol)
79+
if err != nil {
80+
t.Fatalf("NewResolver: %v", err)
81+
}
82+
83+
if r.Name() != "maven" {
84+
t.Errorf("Name() = %q, want %q", r.Name(), "maven")
85+
}
86+
87+
var gotLabels []string
88+
for _, s := range gotSymbols {
89+
gotLabels = append(gotLabels, s.Label.String())
90+
}
91+
92+
if diff := cmp.Diff(tc.wantLabels, gotLabels); diff != "" {
93+
t.Errorf("labels (-want +got):\n%s", diff)
94+
}
95+
})
96+
}
97+
}
98+
99+
func TestNewResolverV2Deterministic(t *testing.T) {
100+
// Create a v2 lockfile with multiple artifacts
101+
content := `{
102+
"__INPUT_ARTIFACTS_HASH": 123,
103+
"__RESOLVED_ARTIFACTS_HASH": 456,
104+
"version": "2",
105+
"artifacts": {},
106+
"packages": {
107+
"com.example:charlie": ["com.example.charlie"],
108+
"com.example:alpha": ["com.example.alpha"],
109+
"com.example:bravo": ["com.example.bravo"]
110+
},
111+
"dependencies": {},
112+
"repositories": {},
113+
"services": {},
114+
"conflict_resolution": {},
115+
"skipped": []
116+
}`
117+
118+
tmpDir := t.TempDir()
119+
installFile := filepath.Join(tmpDir, "maven_install.json")
120+
if err := os.WriteFile(installFile, []byte(content), 0644); err != nil {
121+
t.Fatal(err)
122+
}
123+
124+
// Run multiple times to verify deterministic ordering
125+
var firstOrder []string
126+
for i := 0; i < 10; i++ {
127+
var gotSymbols []*resolver.Symbol
128+
putSymbol := func(s *resolver.Symbol) error {
129+
gotSymbols = append(gotSymbols, s)
130+
return nil
131+
}
132+
133+
_, err := NewResolver(installFile, "maven", "scala", func(format string, args ...interface{}) {}, putSymbol)
134+
if err != nil {
135+
t.Fatalf("NewResolver: %v", err)
136+
}
137+
138+
var order []string
139+
for _, s := range gotSymbols {
140+
order = append(order, s.Name)
141+
}
142+
143+
if i == 0 {
144+
firstOrder = order
145+
// Verify alphabetical ordering
146+
want := []string{"com.example.alpha", "com.example.bravo", "com.example.charlie"}
147+
if diff := cmp.Diff(want, order); diff != "" {
148+
t.Errorf("expected alphabetical order (-want +got):\n%s", diff)
149+
}
150+
} else {
151+
if diff := cmp.Diff(firstOrder, order); diff != "" {
152+
t.Errorf("iteration %d: order is not deterministic (-first +current):\n%s", i, diff)
153+
}
154+
}
155+
}
156+
}
157+
158+
func TestResolverResolve(t *testing.T) {
159+
content := `{
160+
"__INPUT_ARTIFACTS_HASH": 123,
161+
"__RESOLVED_ARTIFACTS_HASH": 456,
162+
"version": "2",
163+
"artifacts": {},
164+
"packages": {
165+
"com.example:foo": ["com.example.foo", "com.example.foo.bar"]
166+
},
167+
"dependencies": {},
168+
"repositories": {},
169+
"services": {},
170+
"conflict_resolution": {},
171+
"skipped": []
172+
}`
173+
174+
tmpDir := t.TempDir()
175+
installFile := filepath.Join(tmpDir, "maven_install.json")
176+
if err := os.WriteFile(installFile, []byte(content), 0644); err != nil {
177+
t.Fatal(err)
178+
}
179+
180+
r, err := NewResolver(installFile, "maven", "scala", func(format string, args ...interface{}) {}, func(s *resolver.Symbol) error { return nil })
181+
if err != nil {
182+
t.Fatalf("NewResolver: %v", err)
183+
}
184+
185+
for name, tc := range map[string]struct {
186+
pkg string
187+
wantLabel label.Label
188+
wantErr bool
189+
}{
190+
"found package": {
191+
pkg: "com.example.foo",
192+
wantLabel: label.Label{Repo: "maven", Name: "com_example_foo"},
193+
},
194+
"found nested package": {
195+
pkg: "com.example.foo.bar",
196+
wantLabel: label.Label{Repo: "maven", Name: "com_example_foo"},
197+
},
198+
"not found": {
199+
pkg: "com.example.notfound",
200+
wantErr: true,
201+
},
202+
} {
203+
t.Run(name, func(t *testing.T) {
204+
got, err := r.Resolve(tc.pkg)
205+
if tc.wantErr {
206+
if err == nil {
207+
t.Error("expected error, got nil")
208+
}
209+
return
210+
}
211+
if err != nil {
212+
t.Fatalf("Resolve: %v", err)
213+
}
214+
if diff := cmp.Diff(tc.wantLabel, got); diff != "" {
215+
t.Errorf("Resolve (-want +got):\n%s", diff)
216+
}
217+
})
218+
}
219+
}

pkg/provider/java_provider.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ func (p *JavaProvider) readClassFile(classFile *jipb.ClassFile, from label.Label
173173
}
174174

175175
func (p *JavaProvider) putSymbol(impType sppb.ImportType, imp string, from label.Label) *resolver.Symbol {
176-
symbol := resolver.NewSymbol(impType, imp, p.Name(), from)
177-
p.scope.PutSymbol(symbol)
178-
return symbol
176+
sym := resolver.NewSymbol(impType, imp, p.Name(), from)
177+
p.scope.PutSymbol(sym)
178+
return sym
179179
}
180180

181181
// ParseLabelNonCanonical parses a label string and returns a non-canonical label.

pkg/provider/maven_provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func (p *MavenProvider) loadFile(dir string, filename string, scope resolver.Sco
6969
if !filepath.IsAbs(filename) {
7070
filename = filepath.Join(dir, filename)
7171
}
72+
7273
resolver, err := maven.NewResolver(filename, name, p.lang, func(format string, args ...interface{}) {
7374
log.Printf(format, args...)
7475
}, scope.PutSymbol)

0 commit comments

Comments
 (0)