diff --git a/doc/config-schema.md b/doc/config-schema.md index 4d507c0ac26..1a5194b72f4 100644 --- a/doc/config-schema.md +++ b/doc/config-schema.md @@ -299,6 +299,7 @@ This document describes the schema for the librarian.yaml. | Field | Type | Description | | :--- | :--- | :--- | +| `custom_group_ids` | map[string]string | Maps API path prefixes (e.g., "google/shopping") to their corresponding Maven Group IDs (e.g., "com.google.shopping"). Use this to override the default "com.google.cloud" Group ID for specific API paths (e.g., maps, ads, shopping). | | `libraries_bom_version` | string | Is the version of the libraries-bom to use for Java. | ## JavaFileCopy Configuration diff --git a/internal/config/language.go b/internal/config/language.go index bd72cb1e373..91fc3c3d92a 100644 --- a/internal/config/language.go +++ b/internal/config/language.go @@ -458,6 +458,11 @@ type DartPackage struct { // JavaDefault contains Java-specific default configuration. type JavaDefault struct { + // CustomGroupIDs maps API path prefixes (e.g., "google/shopping") to their + // corresponding Maven Group IDs (e.g., "com.google.shopping"). + // Use this to override the default "com.google.cloud" Group ID for specific API + // paths (e.g., maps, ads, shopping). + CustomGroupIDs map[string]string `yaml:"custom_group_ids,omitempty"` // LibrariesBOMVersion is the version of the libraries-bom to use for Java. LibrariesBOMVersion string `yaml:"libraries_bom_version,omitempty"` } diff --git a/internal/librarian/library.go b/internal/librarian/library.go index 140ab30e849..1f9e834746f 100644 --- a/internal/librarian/library.go +++ b/internal/librarian/library.go @@ -44,22 +44,22 @@ func fillDefaults(lib *config.Library, d *config.Default) *config.Library { if lib.Output == "" { lib.Output = d.Output } - if d.Go != nil { + switch { + case d.Go != nil: return fillGo(lib, d) - } - if d.Rust != nil { + case d.Java != nil: + return fillJava(lib, d) + case d.Rust != nil: return fillRust(lib, d) - } - if d.Dart != nil { + case d.Dart != nil: return fillDart(lib, d) - } - if d.Python != nil { + case d.Python != nil: return fillPython(lib, d) - } - if d.Swift != nil { + case d.Swift != nil: return fillSwift(lib, d) + default: + return lib } - return lib } // fillGo populates empty Go-specific fields in lib from the provided default. @@ -97,6 +97,32 @@ func union(a, b []string) []string { return res } +// fillJava populates empty Java-specific fields in lib from the provided default. +func fillJava(lib *config.Library, d *config.Default) *config.Library { + if lib.Java == nil { + lib.Java = &config.JavaModule{} + } + fillGroupIDIfEmpty(lib, d) + return lib +} + +// fillGroupIDIfEmpty sets the Java group ID on lib if one is not already configured. +// It matches the library's API paths against the custom group ID prefixes in default +// and assigns the first matching group ID. +func fillGroupIDIfEmpty(lib *config.Library, d *config.Default) { + if lib.Java.GroupID != "" || d.Java.CustomGroupIDs == nil { + return + } + for _, api := range lib.APIs { + for apiPrefix, groupID := range d.Java.CustomGroupIDs { + if api.Path == apiPrefix || strings.HasPrefix(api.Path, apiPrefix+"/") { + lib.Java.GroupID = groupID + return + } + } + } +} + // fillRust populates empty Rust-specific fields in lib from the provided default. func fillRust(lib *config.Library, d *config.Default) *config.Library { if lib.Rust == nil { diff --git a/internal/librarian/library_test.go b/internal/librarian/library_test.go index 464e97654d4..237d6e3b997 100644 --- a/internal/librarian/library_test.go +++ b/internal/librarian/library_test.go @@ -233,6 +233,217 @@ func TestFillDefaults(t *testing.T) { } } +func TestFillDefaults_Java(t *testing.T) { + defaults := &config.Default{ + Java: &config.JavaDefault{ + CustomGroupIDs: map[string]string{ + "google/shopping": "com.google.shopping", + "google/exact/v1": "com.google.exact", + "google/maps": "com.google.maps", + "google/ads": "com.google.api-ads", + "google/analytics": "com.google.analytics", + }, + }, + } + for _, test := range []struct { + name string + lib *config.Library + defaults *config.Default + want *config.Library + }{ + { + name: "shopping library", + lib: &config.Library{ + Name: "shopping-merchant-issue-resolution", + APIs: []*config.API{ + {Path: "google/shopping/merchant/issueresolution/v1"}, + {Path: "google/shopping/merchant/issueresolution/v1beta"}, + }, + }, + defaults: defaults, + want: &config.Library{ + Name: "shopping-merchant-issue-resolution", + APIs: []*config.API{ + {Path: "google/shopping/merchant/issueresolution/v1"}, + {Path: "google/shopping/merchant/issueresolution/v1beta"}, + }, + Java: &config.JavaModule{ + GroupID: "com.google.shopping", + }, + }, + }, + { + name: "do not override custom artifact id", + lib: &config.Library{ + Name: "custom-shopping", + APIs: []*config.API{ + {Path: "google/shopping/merchant/issueresolution/v1"}, + {Path: "google/shopping/merchant/issueresolution/v1beta"}, + }, + Java: &config.JavaModule{ + ArtifactID: "custom-shopping-id", + }, + }, + defaults: defaults, + want: &config.Library{ + Name: "custom-shopping", + APIs: []*config.API{ + {Path: "google/shopping/merchant/issueresolution/v1"}, + {Path: "google/shopping/merchant/issueresolution/v1beta"}, + }, + Java: &config.JavaModule{ + ArtifactID: "custom-shopping-id", + GroupID: "com.google.shopping", + }, + }, + }, + { + name: "maps library", + lib: &config.Library{ + Name: "maps-routeoptimization", + APIs: []*config.API{{Path: "google/maps/routeoptimization/v1"}}, + }, + defaults: defaults, + want: &config.Library{ + Name: "maps-routeoptimization", + APIs: []*config.API{{Path: "google/maps/routeoptimization/v1"}}, + Java: &config.JavaModule{ + GroupID: "com.google.maps", + }, + }, + }, + { + name: "ads library", + lib: &config.Library{ + Name: "admanager", + APIs: []*config.API{{Path: "google/ads/admanager/v1"}}, + }, + defaults: defaults, + want: &config.Library{ + Name: "admanager", + APIs: []*config.API{{Path: "google/ads/admanager/v1"}}, + Java: &config.JavaModule{ + GroupID: "com.google.api-ads", + }, + }, + }, + { + name: "analytics library", + lib: &config.Library{ + Name: "analytics-admin", + APIs: []*config.API{ + {Path: "google/analytics/admin/v1beta"}, + {Path: "google/analytics/admin/v1alpha"}, + }, + }, + defaults: defaults, + want: &config.Library{ + Name: "analytics-admin", + APIs: []*config.API{ + {Path: "google/analytics/admin/v1beta"}, + {Path: "google/analytics/admin/v1alpha"}, + }, + Java: &config.JavaModule{ + GroupID: "com.google.analytics", + }, + }, + }, + { + name: "do not fill if group id already set", + lib: &config.Library{ + Name: "common-protos", + APIs: []*config.API{ + {Path: "google/shopping/type"}, + }, + Java: &config.JavaModule{ + GroupID: "com.google.api.grpc", + }, + }, + defaults: defaults, + want: &config.Library{ + Name: "common-protos", + APIs: []*config.API{ + {Path: "google/shopping/type"}, + }, + Java: &config.JavaModule{ + GroupID: "com.google.api.grpc", + }, + }, + }, + { + name: "prefix match respects path segment boundaries", + lib: &config.Library{ + Name: "shopping-foo", + APIs: []*config.API{ + {Path: "google/shopping-foo/v1"}, + }, + }, + defaults: defaults, + want: &config.Library{ + Name: "shopping-foo", + APIs: []*config.API{ + {Path: "google/shopping-foo/v1"}, + }, + Java: &config.JavaModule{}, + }, + }, + { + name: "no matching api prefix leaves group id empty", + lib: &config.Library{ + Name: "unknown", + APIs: []*config.API{{Path: "google/unknown/v1"}}, + }, + defaults: defaults, + want: &config.Library{ + Name: "unknown", + APIs: []*config.API{{Path: "google/unknown/v1"}}, + Java: &config.JavaModule{}, + }, + }, + { + name: "library does not change with nil map", + lib: &config.Library{ + Name: "lib", + APIs: []*config.API{{Path: "google/example/v1"}}, + }, + defaults: &config.Default{ + Java: &config.JavaDefault{}, + }, + want: &config.Library{ + Name: "lib", + APIs: []*config.API{{Path: "google/example/v1"}}, + Java: &config.JavaModule{}, + }, + }, + { + name: "api path exact match", + lib: &config.Library{ + Name: "exact", + APIs: []*config.API{ + {Path: "google/exact/v1"}, + }, + }, + defaults: defaults, + want: &config.Library{ + Name: "exact", + APIs: []*config.API{ + {Path: "google/exact/v1"}, + }, + Java: &config.JavaModule{ + GroupID: "com.google.exact", + }, + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + got := fillJava(test.lib, test.defaults) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} + func TestFillDefaults_Rust(t *testing.T) { defaults := &config.Default{ Rust: &config.RustDefault{