diff --git a/internal/librarian/golang/generate.go b/internal/librarian/golang/generate.go index e559a2ef4..d6316cfe4 100644 --- a/internal/librarian/golang/generate.go +++ b/internal/librarian/golang/generate.go @@ -28,6 +28,7 @@ import ( "github.com/googleapis/librarian/internal/command" "github.com/googleapis/librarian/internal/config" + "github.com/googleapis/librarian/internal/repometadata" "github.com/googleapis/librarian/internal/semver" "github.com/googleapis/librarian/internal/serviceconfig" ) @@ -36,6 +37,7 @@ const ( releaseLevelAlpha = "alpha" releaseLevelBeta = "beta" releaseLevelGA = "ga" + repo = "googleapis/google-cloud-go" ) var ( @@ -98,17 +100,30 @@ func Generate(ctx context.Context, library *config.Library, googleapisDir string if err := generateInternalVersionFile(moduleRoot, library.Version); err != nil { return err } - for _, api := range library.APIs { + info := &repometadata.LibraryInfo{ + DescriptionOverride: library.DescriptionOverride, + Name: library.Name, + ReleaseLevel: library.ReleaseLevel, + } + for i, api := range library.APIs { if err := generateClientVersionFile(library, api.Path); err != nil { return err } - } - api, err := serviceconfig.Find(googleapisDir, library.APIs[0].Path, serviceconfig.LangGo) - if err != nil { - return err - } - if err := generateREADME(library, api, moduleRoot); err != nil { - return err + svcAPI, err := serviceconfig.Find(googleapisDir, api.Path, serviceconfig.LangGo) + if err != nil { + return err + } + metadataDir, _ := resolveClientPath(library, api.Path) + if err := repometadata.FromAPI(svcAPI, info, serviceconfig.LangGo, repo, defaultVersion, metadataDir); err != nil { + return err + } + // We only need to generate README.md at module root once. + if i != 0 { + continue + } + if err := generateREADME(library, svcAPI, moduleRoot); err != nil { + return err + } } if err := updateSnippetMetadata(library, outdir); err != nil { return err @@ -217,18 +232,6 @@ func buildGAPICImportPath(apiPath string, library *config.Library, goAPI *config importPath, modulePathVersion, version, clientDir) } -func findGoAPI(library *config.Library, apiPath string) *config.GoAPI { - if library.Go == nil { - return nil - } - for _, ga := range library.Go.GoAPIs { - if ga.Path == apiPath { - return ga - } - } - return nil -} - // fixVersioning moves {name}/{version}/* up to {name}/ for versioned modules. func fixVersioning(outputDir, library, modPath string) error { // parts is the module path split by "/". diff --git a/internal/librarian/golang/version.go b/internal/librarian/golang/version.go index d0a048b5b..db33615d1 100644 --- a/internal/librarian/golang/version.go +++ b/internal/librarian/golang/version.go @@ -25,6 +25,10 @@ import ( "github.com/googleapis/librarian/internal/license" ) +const ( + defaultVersion = "0.0.0" +) + var ( //go:embed template/_version.go.txt clientVersionTmpl string @@ -35,7 +39,7 @@ var ( func generateInternalVersionFile(moduleDir, version string) (err error) { if version == "" { - version = "0.0.0" + version = defaultVersion } internalDir := filepath.Join(moduleDir, "internal") if err := os.MkdirAll(internalDir, 0755); err != nil { @@ -61,14 +65,7 @@ func generateInternalVersionFile(moduleDir, version string) (err error) { } func generateClientVersionFile(library *config.Library, apiPath string) (err error) { - version := filepath.Base(apiPath) - goAPI := findGoAPI(library, apiPath) - var clientDir string - if goAPI != nil && goAPI.ClientDirectory != "" { - clientDir = goAPI.ClientDirectory - } - - dir := filepath.Join(library.Output, library.Name, clientDir, "api"+version) + dir, clientDir := resolveClientPath(library, apiPath) if err := os.MkdirAll(dir, 0755); err != nil { return err } @@ -96,6 +93,33 @@ func generateClientVersionFile(library *config.Library, apiPath string) (err err }) } +// resolveClientPath constructs the full path for the API version and determines the client directory. +func resolveClientPath(library *config.Library, apiPath string) (string, string) { + version := filepath.Base(apiPath) + clientDir := clientDirectory(library, apiPath) + return filepath.Join(library.Output, library.Name, clientDir, "api"+version), clientDir +} + +func clientDirectory(library *config.Library, apiPath string) string { + goAPI := findGoAPI(library, apiPath) + if goAPI != nil { + return goAPI.ClientDirectory + } + return "" +} + +func findGoAPI(library *config.Library, apiPath string) *config.GoAPI { + if library.Go == nil { + return nil + } + for _, ga := range library.Go.GoAPIs { + if ga.Path == apiPath { + return ga + } + } + return nil +} + // writeLicenseHeader writes the license header as Go comments to the given file. func writeLicenseHeader(f *os.File) error { year := time.Now().Format("2006") diff --git a/internal/librarian/golang/version_test.go b/internal/librarian/golang/version_test.go index 4143b21e0..8bece770b 100644 --- a/internal/librarian/golang/version_test.go +++ b/internal/librarian/golang/version_test.go @@ -20,6 +20,7 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/googleapis/librarian/internal/config" ) @@ -113,3 +114,87 @@ func TestGenerateClientVersionFile(t *testing.T) { }) } } + +func TestResolveClientPath(t *testing.T) { + for _, test := range []struct { + name string + library *config.Library + apiPath string + wantVersionPath string + wantClientDir string + }{ + { + name: "from apiPath", + library: &config.Library{ + Name: "secretmanager", + APIs: []*config.API{ + { + Path: "google/cloud/secretmanager/v1", + }, + { + Path: "google/cloud/secretmanager/v1beta1", + }, + }, + }, + apiPath: "google/cloud/secretmanager/v1", + wantVersionPath: "secretmanager/apiv1", + wantClientDir: "", + }, + { + name: "non existing GoAPI", + library: &config.Library{ + Name: "secretmanager", + APIs: []*config.API{ + { + Path: "google/cloud/secretmanager/v1", + }, + }, + Go: &config.GoModule{ + GoAPIs: []*config.GoAPI{ + { + Path: "google/cloud/secretmanager/v1beta1", + }, + }, + }, + }, + apiPath: "google/cloud/secretmanager/v1", + wantVersionPath: "secretmanager/apiv1", + wantClientDir: "", + }, + { + name: "from apiPath and client directory", + library: &config.Library{ + Name: "ai", + APIs: []*config.API{ + { + Path: "google/cloud/ai/v1", + }, + }, + Go: &config.GoModule{ + GoAPIs: []*config.GoAPI{ + { + Path: "google/cloud/ai/v1", + ClientDirectory: "customdir", + }, + { + Path: "google/cloud/ai/v1beta1", + }, + }, + }, + }, + apiPath: "google/cloud/ai/v1", + wantVersionPath: "ai/customdir/apiv1", + wantClientDir: "customdir", + }, + } { + t.Run(test.name, func(t *testing.T) { + gotVersionPath, gotClientDir := resolveClientPath(test.library, test.apiPath) + if diff := cmp.Diff(test.wantVersionPath, gotVersionPath); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(test.wantClientDir, gotClientDir); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/repometadata/repometadata.go b/internal/repometadata/repometadata.go index 68725067c..7b3955d8a 100644 --- a/internal/repometadata/repometadata.go +++ b/internal/repometadata/repometadata.go @@ -153,9 +153,11 @@ func FromAPI(api *serviceconfig.API, info *LibraryInfo, language, repo, defaultV // buildClientDocURL builds the client documentation URL based on language. func buildClientDocURL(language, serviceName string) string { switch language { - case "python": + case serviceconfig.LangGo: + return fmt.Sprintf("https://cloud.google.com/go/docs/reference/cloud.google.com/go/%s/latest", serviceName) + case serviceconfig.LangPython: return fmt.Sprintf("https://cloud.google.com/python/docs/reference/%s/latest", serviceName) - case "rust": + case serviceconfig.LangRust: return fmt.Sprintf("https://docs.rs/google-cloud-%s/latest", serviceName) default: return "" diff --git a/internal/serviceconfig/serviceconfig.go b/internal/serviceconfig/serviceconfig.go index 1630bf5c2..27f451572 100644 --- a/internal/serviceconfig/serviceconfig.go +++ b/internal/serviceconfig/serviceconfig.go @@ -171,15 +171,19 @@ func findServiceConfig(googleapisDir, path string) (string, error) { } func populateFromServiceConfig(api *API, cfg *Service) *API { - if api.Title == "" { - api.Title = cfg.GetTitle() + if api.Description == "" && cfg.GetDocumentation() != nil { + api.Description = strings.TrimSpace(cfg.GetDocumentation().GetSummary()) } if api.ServiceName == "" { api.ServiceName = cfg.GetName() } - if api.Description == "" && cfg.GetDocumentation() != nil { - api.Description = strings.TrimSpace(cfg.GetDocumentation().GetSummary()) + if api.ShortName == "" { + api.ShortName = defaultShortName(api.ServiceName) } + if api.Title == "" { + api.Title = cfg.GetTitle() + } + api.ShortName = defaultShortName(api.ServiceName) publishing := cfg.GetPublishing() if publishing != nil { if api.NewIssueURI == "" { @@ -188,7 +192,7 @@ func populateFromServiceConfig(api *API, cfg *Service) *API { if api.DocumentationURI == "" { api.DocumentationURI = publishing.GetDocumentationUri() } - if api.ShortName == "" { + if publishing.GetApiShortName() != "" { api.ShortName = publishing.GetApiShortName() } } @@ -238,6 +242,11 @@ func isServiceConfigFile(path string) (bool, error) { return false, scanner.Err() } +// defaultShortName returns the default short name from serviceName. +func defaultShortName(serviceName string) string { + return strings.Split(serviceName, ".")[0] +} + // FindGRPCServiceConfig searches for gRPC service config files in the given // API directory. It returns the path relative to googleapisDir for use with // protoc's retry-config option. Returns empty string if no config is found. diff --git a/internal/serviceconfig/serviceconfig_test.go b/internal/serviceconfig/serviceconfig_test.go index 0637dee4e..6342c408b 100644 --- a/internal/serviceconfig/serviceconfig_test.go +++ b/internal/serviceconfig/serviceconfig_test.go @@ -126,6 +126,7 @@ func TestFind(t *testing.T) { Path: "google/cloud/aiplatform/v1/schema/predict/instance", ServiceConfig: "google/cloud/aiplatform/v1/schema/aiplatform_v1.yaml", ServiceName: "aiplatform.googleapis.com", + ShortName: "aiplatform", Title: "Vertex AI API", Transports: map[string]Transport{"python": "grpc"}, }, @@ -149,10 +150,11 @@ func TestFind(t *testing.T) { name: "discovery", api: "discoveries/compute.v1.json", want: &API{ - Path: "google/cloud/compute/v1", Discovery: "discoveries/compute.v1.json", + Path: "google/cloud/compute/v1", ServiceConfig: "google/cloud/compute/v1/compute_v1.yaml", ServiceName: "compute.googleapis.com", + ShortName: "compute", Title: "Google Compute Engine API", Transports: map[string]Transport{"csharp": "rest", "go": "rest", "java": "rest", "php": "rest"}, }, @@ -287,6 +289,17 @@ func TestPopulateFromServiceConfig(t *testing.T) { ShortName: "override short name", }, }, + { + name: "default short name", + api: &API{}, + cfg: &Service{ + Name: "accessapproval.googleapis.com", + }, + want: &API{ + ServiceName: "accessapproval.googleapis.com", + ShortName: "accessapproval", + }, + }, } { t.Run(test.name, func(t *testing.T) { got := populateFromServiceConfig(test.api, test.cfg)