diff --git a/internal/librarian/java/generate.go b/internal/librarian/java/generate.go index 54d87e776..e130c07ea 100644 --- a/internal/librarian/java/generate.go +++ b/internal/librarian/java/generate.go @@ -59,12 +59,17 @@ func Generate(ctx context.Context, cfg *config.Config, library *config.Library, if err := os.MkdirAll(outdir, 0755); err != nil { return fmt.Errorf("failed to create output directory %q: %w", outdir, err) } + // generate repo metadata prior to client because info is needed for + // owlbot.py to generate README.md + if err := generateRepoMetadata(cfg, library, outdir, googleapisDir); err != nil { + return fmt.Errorf("failed to generate .repo-metadata.json: %w", err) + } for _, api := range library.APIs { if err := generateAPI(ctx, cfg, api, library, googleapisDir, outdir); err != nil { return fmt.Errorf("failed to generate api %q: %w", api.Path, err) } } - return postProcessLibrary(cfg, library, outdir, googleapisDir) + return nil } func generateAPI(ctx context.Context, cfg *config.Config, api *config.API, library *config.Library, googleapisDir, outdir string) error { diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index 7fcc4a657..ce5b1b7cd 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -23,11 +23,8 @@ import ( "strings" "time" - "github.com/googleapis/librarian/internal/config" "github.com/googleapis/librarian/internal/filesystem" "github.com/googleapis/librarian/internal/license" - "github.com/googleapis/librarian/internal/repometadata" - "github.com/googleapis/librarian/internal/serviceconfig" ) type postProcessParams struct { @@ -217,108 +214,3 @@ func copyProtos(googleapisDir string, protos []string, destDir string) error { } return nil } - -// postProcessLibrary coordinates all library-level post-processing tasks, -// such as generating .repo-metadata.json. -func postProcessLibrary(cfg *config.Config, library *config.Library, outDir, googleapisDir string) error { - // TODO(https://github.com/googleapis/librarian/issues/4217): update pom files. - // TODO(https://github.com/googleapis/librarian/issues/4218): generate README.md - metadata, err := deriveRepoMetadata(cfg, library, googleapisDir) - if err != nil { - return fmt.Errorf("failed to derive repo metadata: %w", err) - } - if err := metadata.write(outDir); err != nil { - return fmt.Errorf("failed to write .repo-metadata.json: %w", err) - } - return nil -} - -// deriveRepoMetadata constructs the repoMetadata for a Java library using -// information from the primary service configuration and library-level overrides. -func deriveRepoMetadata(cfg *config.Config, library *config.Library, googleapisDir string) (*repoMetadata, error) { - serviceconfig.SortAPIs(library.APIs) - sharedMetadata, err := repometadata.FromLibrary(cfg, library, googleapisDir) - if err != nil { - return nil, err - } - - metadata := &repoMetadata{ - APIShortname: sharedMetadata.APIShortname, - NamePretty: sharedMetadata.NamePretty, - ProductDocumentation: sharedMetadata.ProductDocumentation, - APIDescription: sharedMetadata.APIDescription, - ReleaseLevel: sharedMetadata.ReleaseLevel, - Language: config.LanguageJava, - Repo: sharedMetadata.Repo, - RepoShort: fmt.Sprintf("%s-%s", config.LanguageJava, library.Name), - DistributionName: sharedMetadata.DistributionName, - APIID: sharedMetadata.APIID, - LibraryType: repometadata.GAPICAutoLibraryType, - RequiresBilling: true, - } - - // Java-specific overrides and optional fields - if library.Java.APIIDOverride != "" { - metadata.APIID = library.Java.APIIDOverride - } - if library.Java.APIDescriptionOverride != "" { - metadata.APIDescription = library.Java.APIDescriptionOverride - } - if library.Java.DistributionNameOverride != "" { - metadata.DistributionName = library.Java.DistributionNameOverride - } - if library.Java.IssueTrackerOverride != "" { - metadata.IssueTracker = library.Java.IssueTrackerOverride - } - if library.Java.LibraryTypeOverride != "" { - metadata.LibraryType = library.Java.LibraryTypeOverride - } - if library.Java.NamePrettyOverride != "" { - metadata.NamePretty = library.Java.NamePrettyOverride - } - if library.Java.ProductDocumentationOverride != "" { - metadata.ProductDocumentation = library.Java.ProductDocumentationOverride - } - if library.Java.ClientDocumentationOverride != "" { - metadata.ClientDocumentation = library.Java.ClientDocumentationOverride - } - metadata.RequiresBilling = !library.Java.BillingNotRequired - // Java only fields - metadata.CodeownerTeam = library.Java.CodeownerTeam - metadata.ExtraVersionedModules = library.Java.ExtraVersionedModules - metadata.ExcludedDependencies = library.Java.ExcludedDependencies - metadata.ExcludedPoms = library.Java.ExcludedPoms - metadata.MinJavaVersion = library.Java.MinJavaVersion - metadata.RecommendedPackage = library.Java.RecommendedPackage - metadata.RestDocumentation = library.Java.RestDocumentation - metadata.RpcDocumentation = library.Java.RpcDocumentation - - // distribution_name default for Java is groupId:artifactId - if !strings.Contains(metadata.DistributionName, ":") { - metadata.DistributionName = deriveDistributionName(library) - } - // Default ClientDocumentation uses artifact ID - if metadata.ClientDocumentation == "" { - parts := strings.Split(metadata.DistributionName, ":") - artifactID := parts[len(parts)-1] - metadata.ClientDocumentation = fmt.Sprintf("https://cloud.google.com/java/docs/reference/%s/latest/overview", artifactID) - } - // transport - apiCfg, err := serviceconfig.Find(googleapisDir, library.APIs[0].Path, config.LanguageJava) - if err != nil { - return nil, fmt.Errorf("failed to find api config: %w", err) - } - transport := serviceconfig.GRPCRest - if apiCfg != nil { - transport = apiCfg.Transport(config.LanguageJava) - } - switch transport { - case "grpc": - metadata.Transport = "grpc" - case "rest": - metadata.Transport = "http" - default: - metadata.Transport = "both" - } - return metadata, nil -} diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index f09ab6580..1a13f9406 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -23,9 +23,6 @@ import ( "strings" "testing" - - "github.com/google/go-cmp/cmp" - "github.com/googleapis/librarian/internal/config" ) func TestPostProcessAPI(t *testing.T) { @@ -307,51 +304,3 @@ func TestAddMissingHeaders(t *testing.T) { }) } } - -func TestDeriveRepoMetadata_Overrides(t *testing.T) { - t.Parallel() - apiPath := "google/cloud/secretmanager/v1" - googleapis := "internal/testdata/googleapis" - - cfg := &config.Config{ - Language: config.LanguageJava, - Repo: "googleapis/google-cloud-java", - } - library := &config.Library{ - Name: "secretmanager", - APIs: []*config.API{{Path: apiPath}}, - Java: &config.JavaModule{ - GroupID: "com.custom", - DistributionNameOverride: "com.custom:custom-artifact", - APIIDOverride: "custom.googleapis.com", - APIDescriptionOverride: "Custom description", - NamePrettyOverride: "Custom Pretty Name", - ProductDocumentationOverride: "https://custom.docs", - ClientDocumentationOverride: "https://custom.client.docs", - BillingNotRequired: true, - LibraryTypeOverride: "OTHER", - }, - } - got, err := deriveRepoMetadata(cfg, library, googleapis) - if err != nil { - t.Fatalf("deriveRepoMetadata failed: %v", err) - } - want := &repoMetadata{ - NamePretty: "Custom Pretty Name", - ProductDocumentation: "https://custom.docs", - APIDescription: "Custom description", - ClientDocumentation: "https://custom.client.docs", - ReleaseLevel: "stable", - Transport: "both", - Language: "java", - Repo: "googleapis/google-cloud-java", - RepoShort: "java-secretmanager", - DistributionName: "com.custom:custom-artifact", - APIID: "custom.googleapis.com", - LibraryType: "OTHER", - RequiresBilling: false, - } - if diff := cmp.Diff(want, got, cmp.AllowUnexported(repoMetadata{})); diff != "" { - t.Errorf("mismatch (-want +got):\n%s", diff) - } -} diff --git a/internal/librarian/java/repometadata.go b/internal/librarian/java/repometadata.go index c1775a64e..fd26ffedc 100644 --- a/internal/librarian/java/repometadata.go +++ b/internal/librarian/java/repometadata.go @@ -14,7 +14,14 @@ package java -import "github.com/googleapis/librarian/internal/repometadata" +import ( + "fmt" + "strings" + + "github.com/googleapis/librarian/internal/config" + "github.com/googleapis/librarian/internal/repometadata" + "github.com/googleapis/librarian/internal/serviceconfig" +) // repoMetadata represents the .repo-metadata.json file structure for Java. // @@ -63,3 +70,100 @@ type repoMetadata struct { func (metadata *repoMetadata) write(libraryOutputDir string) error { return repometadata.WriteJSON(metadata, " ", libraryOutputDir, ".repo-metadata.json") } + +// generateRepoMetadata coordinates all library-level post-processing tasks, +// such as generating .repo-metadata.json. +func generateRepoMetadata(cfg *config.Config, library *config.Library, outDir, googleapisDir string) error { + metadata, err := deriveRepoMetadata(cfg, library, googleapisDir) + if err != nil { + return fmt.Errorf("failed to derive repo metadata: %w", err) + } + if err := metadata.write(outDir); err != nil { + return fmt.Errorf("failed to write .repo-metadata.json: %w", err) + } + return nil +} + +// deriveRepoMetadata constructs the repoMetadata for a Java library using +// information from the primary service configuration and library-level overrides. +func deriveRepoMetadata(cfg *config.Config, library *config.Library, googleapisDir string) (*repoMetadata, error) { + serviceconfig.SortAPIs(library.APIs) + sharedMetadata, err := repometadata.FromLibrary(cfg, library, googleapisDir) + if err != nil { + return nil, err + } + + metadata := &repoMetadata{ + APIShortname: sharedMetadata.APIShortname, + NamePretty: sharedMetadata.NamePretty, + ProductDocumentation: sharedMetadata.ProductDocumentation, + APIDescription: sharedMetadata.APIDescription, + ReleaseLevel: sharedMetadata.ReleaseLevel, + Language: config.LanguageJava, + Repo: sharedMetadata.Repo, + RepoShort: fmt.Sprintf("%s-%s", config.LanguageJava, library.Name), + DistributionName: sharedMetadata.DistributionName, + APIID: sharedMetadata.APIID, + LibraryType: repometadata.GAPICAutoLibraryType, + RequiresBilling: true, + } + + // Java-specific overrides and optional fields + if library.Java != nil { + if library.Java.APIIDOverride != "" { + metadata.APIID = library.Java.APIIDOverride + } + if library.Java.APIDescriptionOverride != "" { + metadata.APIDescription = library.Java.APIDescriptionOverride + } + if library.Java.DistributionNameOverride != "" { + metadata.DistributionName = library.Java.DistributionNameOverride + } + if library.Java.IssueTrackerOverride != "" { + metadata.IssueTracker = library.Java.IssueTrackerOverride + } + if library.Java.LibraryTypeOverride != "" { + metadata.LibraryType = library.Java.LibraryTypeOverride + } + if library.Java.NamePrettyOverride != "" { + metadata.NamePretty = library.Java.NamePrettyOverride + } + if library.Java.ProductDocumentationOverride != "" { + metadata.ProductDocumentation = library.Java.ProductDocumentationOverride + } + if library.Java.ClientDocumentationOverride != "" { + metadata.ClientDocumentation = library.Java.ClientDocumentationOverride + } + metadata.RequiresBilling = !library.Java.BillingNotRequired + // Java only fields + metadata.CodeownerTeam = library.Java.CodeownerTeam + metadata.ExtraVersionedModules = library.Java.ExtraVersionedModules + metadata.ExcludedDependencies = library.Java.ExcludedDependencies + metadata.ExcludedPoms = library.Java.ExcludedPoms + metadata.MinJavaVersion = library.Java.MinJavaVersion + metadata.RecommendedPackage = library.Java.RecommendedPackage + metadata.RestDocumentation = library.Java.RestDocumentation + metadata.RpcDocumentation = library.Java.RpcDocumentation + } + + // distribution_name default for Java is groupId:artifactId + if !strings.Contains(metadata.DistributionName, ":") { + metadata.DistributionName = deriveDistributionName(library) + } + // Default ClientDocumentation uses artifact ID + if metadata.ClientDocumentation == "" { + parts := strings.Split(metadata.DistributionName, ":") + artifactID := parts[len(parts)-1] + metadata.ClientDocumentation = fmt.Sprintf("https://cloud.google.com/java/docs/reference/%s/latest/overview", artifactID) + } + // transport + apiCfg, err := serviceconfig.Find(googleapisDir, library.APIs[0].Path, config.LanguageJava) + if err != nil { + return nil, fmt.Errorf("failed to find api config: %w", err) + } + metadata.Transport = "both" + if apiCfg != nil { + metadata.Transport = apiCfg.RepoMetadataTransport(config.LanguageJava) + } + return metadata, nil +} diff --git a/internal/librarian/java/repometadata_test.go b/internal/librarian/java/repometadata_test.go index 50779ad4f..912823054 100644 --- a/internal/librarian/java/repometadata_test.go +++ b/internal/librarian/java/repometadata_test.go @@ -15,29 +15,33 @@ package java import ( + "encoding/json" "os" "path/filepath" "testing" "github.com/google/go-cmp/cmp" + "github.com/googleapis/librarian/internal/config" + "github.com/googleapis/librarian/internal/sample" ) func TestRepoMetadata_write(t *testing.T) { + s := sample.RepoMetadata() want := &repoMetadata{ - APIShortname: "secretmanager", - NamePretty: "Secret Manager", - ProductDocumentation: "https://cloud.google.com/secret-manager/", - APIDescription: "Stores sensitive data such as API keys, passwords, and certificates. Provides convenience while improving security.", + APIShortname: s.APIShortname, + NamePretty: s.NamePretty, + ProductDocumentation: s.ProductDocumentation, + APIDescription: s.APIDescription, ClientDocumentation: "https://cloud.google.com/java/docs/reference/google-cloud-secretmanager/latest/overview", - ReleaseLevel: "stable", + ReleaseLevel: s.ReleaseLevel, Transport: "grpc", Language: "java", Repo: "googleapis/google-cloud-java", RepoShort: "java-secretmanager", DistributionName: "com.google.cloud:google-cloud-secretmanager", - LibraryType: "GAPIC_AUTO", + LibraryType: s.LibraryType, CodeownerTeam: "cloud-java-team", - IssueTracker: "https://issuetracker.google.com/issues/new?component=187210&template=0", + IssueTracker: s.IssueTracker, RestDocumentation: "https://example.com/rest", RpcDocumentation: "https://example.com/rpc", RecommendedPackage: "com.google.cloud.secretmanager.v1", @@ -53,34 +57,63 @@ func TestRepoMetadata_write(t *testing.T) { if _, err := os.Stat(gotPath); err != nil { t.Fatalf("os.Stat(%q) = %v, want nil", gotPath, err) } - gotBytes, err := os.ReadFile(gotPath) if err != nil { t.Fatalf("os.ReadFile(%q) = %v, want nil", gotPath, err) } - - const wantJSON = `{ - "api_shortname": "secretmanager", - "name_pretty": "Secret Manager", - "product_documentation": "https://cloud.google.com/secret-manager/", - "api_description": "Stores sensitive data such as API keys, passwords, and certificates. Provides convenience while improving security.", - "client_documentation": "https://cloud.google.com/java/docs/reference/google-cloud-secretmanager/latest/overview", - "release_level": "stable", - "transport": "grpc", - "language": "java", - "repo": "googleapis/google-cloud-java", - "repo_short": "java-secretmanager", - "distribution_name": "com.google.cloud:google-cloud-secretmanager", - "library_type": "GAPIC_AUTO", - "requires_billing": false, - "codeowner_team": "cloud-java-team", - "issue_tracker": "https://issuetracker.google.com/issues/new?component=187210\u0026template=0", - "rest_documentation": "https://example.com/rest", - "rpc_documentation": "https://example.com/rpc", - "recommended_package": "com.google.cloud.secretmanager.v1", - "min_java_version": 8 -}` - if diff := cmp.Diff(wantJSON, string(gotBytes)); diff != "" { + var got repoMetadata + if err := json.Unmarshal(gotBytes, &got); err != nil { + t.Fatalf("json.Unmarshal() = %v, want nil", err) + } + if diff := cmp.Diff(want, &got, cmp.AllowUnexported(repoMetadata{})); diff != "" { t.Errorf("write() mismatch (-want +got):\n%s", diff) } } + +func TestDeriveRepoMetadata_Overrides(t *testing.T) { + t.Parallel() + apiPath := "google/cloud/secretmanager/v1" + googleapis := "internal/testdata/googleapis" + + cfg := sample.Config() + cfg.Language = config.LanguageJava + cfg.Repo = "googleapis/google-cloud-java" + library := &config.Library{ + Name: "secretmanager", + APIs: []*config.API{{Path: apiPath}}, + Java: &config.JavaModule{ + GroupID: "com.custom", + DistributionNameOverride: "com.custom:custom-artifact", + APIIDOverride: "custom.googleapis.com", + APIDescriptionOverride: "Custom description", + NamePrettyOverride: "Custom Pretty Name", + ProductDocumentationOverride: "https://custom.docs", + ClientDocumentationOverride: "https://custom.client.docs", + BillingNotRequired: true, + LibraryTypeOverride: "OTHER", + }, + } + got, err := deriveRepoMetadata(cfg, library, googleapis) + if err != nil { + t.Fatalf("deriveRepoMetadata failed: %v", err) + } + s := sample.RepoMetadata() + want := &repoMetadata{ + NamePretty: "Custom Pretty Name", + ProductDocumentation: "https://custom.docs", + APIDescription: "Custom description", + ClientDocumentation: "https://custom.client.docs", + ReleaseLevel: s.ReleaseLevel, + Transport: "both", + Language: cfg.Language, + Repo: cfg.Repo, + RepoShort: "java-secretmanager", + DistributionName: "com.custom:custom-artifact", + APIID: "custom.googleapis.com", + LibraryType: "OTHER", + RequiresBilling: false, + } + if diff := cmp.Diff(want, got, cmp.AllowUnexported(repoMetadata{})); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } +} diff --git a/internal/serviceconfig/api.go b/internal/serviceconfig/api.go index 2ca540ab2..7878767a7 100644 --- a/internal/serviceconfig/api.go +++ b/internal/serviceconfig/api.go @@ -187,6 +187,26 @@ func (api *API) RepoMetadataReleaseLevel(language string) string { return api.ReleaseLevel(language) } +// RepoMetadataTransport returns the transport for repo metadata. +// +// TODO(https://github.com/googleapis/librarian/issues/4854): delete +// once the issue is resolved. +// For Java, it maps the transport to "grpc", "http", or "both". +func (api *API) RepoMetadataTransport(language string) string { + transport := api.Transport(language) + if language == config.LanguageJava { + switch transport { + case GRPC: + return "grpc" + case Rest: + return "http" + default: + return "both" + } + } + return string(transport) +} + var ( //go:embed sdk.yaml sdkYaml []byte diff --git a/internal/serviceconfig/api_test.go b/internal/serviceconfig/api_test.go index 859cf4194..ae172a34b 100644 --- a/internal/serviceconfig/api_test.go +++ b/internal/serviceconfig/api_test.go @@ -302,3 +302,64 @@ func TestGetTransport(t *testing.T) { }) } } + +func TestRepoMetadataTransport(t *testing.T) { + for _, test := range []struct { + name string + sc *API + language string + want string + }{ + { + name: "java, default", + sc: &API{}, + language: config.LanguageJava, + want: "both", + }, + { + name: "java, grpc", + sc: &API{ + Transports: map[string]Transport{config.LanguageJava: GRPC}, + }, + language: config.LanguageJava, + want: "grpc", + }, + { + name: "java, rest", + sc: &API{ + Transports: map[string]Transport{config.LanguageJava: Rest}, + }, + language: config.LanguageJava, + want: "http", + }, + { + name: "non-java, default", + sc: &API{}, + language: config.LanguageGo, + want: "grpc+rest", + }, + { + name: "non-java, grpc", + sc: &API{ + Transports: map[string]Transport{config.LanguageGo: GRPC}, + }, + language: config.LanguageGo, + want: "grpc", + }, + { + name: "non-java, rest", + sc: &API{ + Transports: map[string]Transport{config.LanguageGo: Rest}, + }, + language: config.LanguageGo, + want: "rest", + }, + } { + t.Run(test.name, func(t *testing.T) { + got := test.sc.RepoMetadataTransport(test.language) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +}