Skip to content

Commit 0b8a9c5

Browse files
committed
Merge branch 'main' into chore/update-state
2 parents 628ccca + 836df0a commit 0b8a9c5

File tree

17 files changed

+534
-49
lines changed

17 files changed

+534
-49
lines changed

doc/config-schema.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ This document describes the schema for the librarian.yaml.
201201
| `import_path` | string | Is the Go import path for the API. |
202202
| `nested_protos` | list of string | Is a list of nested proto files. |
203203
| `no_metadata` | bool | Indicates whether to skip generating gapic_metadata.json. This is typically false. |
204+
| `no_snippets` | bool | Indicates whether to skip generating snippets. This is typically false. |
204205
| `path` | string | Is the source path. |
205206
| `proto_only` | bool | Determines whether to generate a Proto-only client. A proto-only client does not define a service in the proto files. |
206207
| `proto_package` | string | Is the proto package name. |

internal/config/language.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ type GoAPI struct {
8080
// NoMetadata indicates whether to skip generating gapic_metadata.json.
8181
// This is typically false.
8282
NoMetadata bool `yaml:"no_metadata,omitempty"`
83+
// NoSnippets indicates whether to skip generating snippets.
84+
// This is typically false.
85+
NoSnippets bool `yaml:"no_snippets,omitempty"`
8386
// Path is the source path.
8487
Path string `yaml:"path,omitempty"`
8588
// ProtoOnly determines whether to generate a Proto-only client.

internal/librarian/generate.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ func defaultOutput(language string, name, api, defaultOut string) string {
269269
return python.DefaultOutput(name, defaultOut)
270270
case config.LanguageRust:
271271
return rust.DefaultOutput(api, defaultOut)
272+
case config.LanguageSwift:
273+
return swift.DefaultOutput(api, defaultOut)
272274
default:
273275
return defaultOut
274276
}

internal/librarian/golang/generate.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ func buildGAPICOpts(apiPath string, goAPI *config.GoAPI, googleapisDir string) (
145145
if !goAPI.NoMetadata {
146146
opts = append(opts, "metadata")
147147
}
148+
if goAPI.NoSnippets {
149+
opts = append(opts, "omit-snippets")
150+
}
148151
if sc != nil && sc.HasRESTNumericEnums(config.LanguageGo) {
149152
opts = append(opts, "rest-numeric-enums")
150153
}
@@ -199,14 +202,14 @@ func moveAPIDirectory(library *config.Library, goAPI *config.GoAPI, outDir strin
199202
// moveAndUpdateSnippets moves the generated snippets from the temporary location to their final
200203
// destination and updates their library versions.
201204
func moveAndUpdateSnippets(library *config.Library, goAPI *config.GoAPI, outDir string) error {
202-
snippetDirPrefix := filepath.Join(outDir, "cloud.google.com", "go", "internal", "generated", "snippets")
203205
snippetDest := findSnippetDirectory(library, goAPI, outDir)
204206
if snippetDest == "" {
205207
return nil
206208
}
207209
if err := os.MkdirAll(snippetDest, 0755); err != nil {
208210
return err
209211
}
212+
snippetDirPrefix := filepath.Join(outDir, "cloud.google.com", "go", "internal", "generated", "snippets")
210213
snippetSrc := filepath.Join(snippetDirPrefix, goAPI.ImportPath)
211214
if err := filesystem.MoveAndMerge(snippetSrc, snippetDest); err != nil {
212215
return err

internal/librarian/golang/generate_test.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,26 @@ func TestBuildGAPICOpts(t *testing.T) {
594594
"release-level=ga",
595595
},
596596
},
597+
{
598+
name: "no snippets",
599+
apiPath: "google/cloud/gkehub/v1",
600+
goAPI: &config.GoAPI{
601+
ClientPackage: "gkehub",
602+
ImportPath: "gkehub/apiv1",
603+
NoSnippets: true,
604+
Path: "google/cloud/gkehub/v1",
605+
},
606+
googleapisDir: googleapisDir,
607+
want: []string{
608+
"go-gapic-package=cloud.google.com/go/gkehub/apiv1;gkehub",
609+
"metadata",
610+
"omit-snippets",
611+
"rest-numeric-enums",
612+
"api-service-config=" + filepath.Join(googleapisDir, "google/cloud/gkehub/v1/gkehub_v1.yaml"),
613+
"transport=grpc+rest",
614+
"release-level=ga",
615+
},
616+
},
597617
{
598618
name: "generator features",
599619
apiPath: "google/cloud/bigquery/v2",
@@ -802,6 +822,37 @@ func TestMoveGeneratedFiles(t *testing.T) {
802822
return outDir, filepath.Join(outDir, "apiv1"), filepath.Join(repoRoot, "internal", "generated", "snippets", "lib", "apiv1"), lib
803823
},
804824
},
825+
{
826+
name: "no snippets",
827+
setup: func(t *testing.T, tmpDir string) (string, string, string, *config.Library) {
828+
repoRoot := filepath.Join(tmpDir, "repo")
829+
outDir := filepath.Join(repoRoot, "lib")
830+
srcDir := filepath.Join(outDir, "cloud.google.com", "go", "lib", "apiv1")
831+
if err := os.MkdirAll(srcDir, 0755); err != nil {
832+
t.Fatal(err)
833+
}
834+
if err := os.WriteFile(filepath.Join(srcDir, "main.go"), []byte("package foo"), 0644); err != nil {
835+
t.Fatal(err)
836+
}
837+
// Even if snippet source exists in cloud.google.com/go, it should not be moved.
838+
snippetDirSuffix := filepath.Join("internal", "generated", "snippets", "lib", "apiv1")
839+
snippetSrcDir := filepath.Join(outDir, "cloud.google.com", "go", snippetDirSuffix)
840+
if err := os.MkdirAll(snippetSrcDir, 0755); err != nil {
841+
t.Fatal(err)
842+
}
843+
if err := os.WriteFile(filepath.Join(snippetSrcDir, "snippet.go"), []byte("package internal"), 0644); err != nil {
844+
t.Fatal(err)
845+
}
846+
lib := &config.Library{
847+
Name: "lib",
848+
APIs: []*config.API{{Path: "lib/v1"}},
849+
Go: &config.GoModule{
850+
GoAPIs: []*config.GoAPI{{Path: "lib/v1", ImportPath: "lib/apiv1", NoSnippets: true}},
851+
},
852+
}
853+
return outDir, filepath.Join(outDir, "apiv1"), filepath.Join(repoRoot, snippetDirSuffix), lib
854+
},
855+
},
805856
} {
806857
t.Run(test.name, func(t *testing.T) {
807858
tmpDir := t.TempDir()
@@ -813,8 +864,14 @@ func TestMoveGeneratedFiles(t *testing.T) {
813864
if _, err := os.Stat(filepath.Join(apiDir, "main.go")); err != nil {
814865
t.Errorf("expected main.go to exist, got err: %v", err)
815866
}
816-
if _, err := os.Stat(filepath.Join(snippetDir, "snippet.go")); err != nil {
817-
t.Errorf("expected snippet.go to exist, got err: %v", err)
867+
if lib.Go.GoAPIs[0].NoSnippets {
868+
if _, err := os.Stat(filepath.Join(snippetDir, "snippet.go")); !errors.Is(err, os.ErrNotExist) {
869+
t.Errorf("expected snippet.go to not exist, got err: %v", err)
870+
}
871+
} else {
872+
if _, err := os.Stat(filepath.Join(snippetDir, "snippet.go")); err != nil {
873+
t.Errorf("expected snippet.go to exist, got err: %v", err)
874+
}
818875
}
819876
})
820877
}

internal/librarian/golang/module.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,10 @@ func snippetDirectory(output, importPath string) string {
174174
}
175175

176176
// findSnippetDirectory returns the path to the snippet directory for the given API path and library output directory.
177-
// It returns an empty string if the API is proto-only or if the snippet directory is in a path marked for deletion
178-
// after generation.
177+
// It returns an empty string if the API is proto-only, if snippet generation is disabled,
178+
// or if the snippet directory is in a path marked for deletion after generation.
179179
func findSnippetDirectory(library *config.Library, goAPI *config.GoAPI, output string) string {
180-
if goAPI.ProtoOnly {
180+
if goAPI.ProtoOnly || goAPI.NoSnippets {
181181
return ""
182182
}
183183
snippetDir := snippetDirectory(repoRootPath(output, library.Name), clientPathFromRepoRoot(library, goAPI))

internal/librarian/golang/tidy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func isEmptyAPI(goAPI *config.GoAPI) bool {
5959
goAPI.ImportPath == "" &&
6060
len(goAPI.NestedProtos) == 0 &&
6161
!goAPI.NoMetadata &&
62+
!goAPI.NoSnippets &&
6263
!goAPI.ProtoOnly &&
6364
goAPI.ProtoPackage == ""
6465
}

internal/librarian/golang/tidy_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ func TestIsEmptyAPI(t *testing.T) {
249249
NoMetadata: true,
250250
},
251251
},
252+
{
253+
name: "not empty with NoSnippets",
254+
goAPI: &config.GoAPI{
255+
NoSnippets: true,
256+
},
257+
},
252258
{
253259
name: "not empty with ProtoOnly",
254260
goAPI: &config.GoAPI{

internal/librarian/java/generate.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,9 @@ const (
3838
)
3939

4040
var (
41-
// errExtractVersion is returned when an API version cannot be extracted from its path.
42-
errExtractVersion = errors.New("failed to extract version")
43-
// errNoProtos is returned when no proto files are found in an API directory.
44-
errNoProtos = errors.New("no protos found")
41+
errExtractVersion = errors.New("failed to extract version")
42+
errNoProtos = errors.New("no protos found")
43+
errMonorepoVersion = fmt.Errorf("failed to find monorepo version for %q in config", rootLibrary)
4544
)
4645

4746
// Generate generates a Java client library.
@@ -65,16 +64,32 @@ func Generate(ctx context.Context, cfg *config.Config, library *config.Library,
6564
if err != nil {
6665
return fmt.Errorf("failed to generate .repo-metadata.json: %w", err)
6766
}
67+
68+
transports := make(map[string]serviceconfig.Transport)
6869
for _, api := range library.APIs {
70+
apiCfg, err := serviceconfig.Find(googleapisDir, api.Path, config.LanguageJava)
71+
if err != nil {
72+
return fmt.Errorf("failed to find api config for %s: %w", api.Path, err)
73+
}
74+
transports[api.Path] = apiCfg.Transport(config.LanguageJava)
6975
// metadata is needed for pom.xml generation in post process
70-
if err := generateAPI(ctx, cfg, api, library, googleapisDir, outdir, metadata); err != nil {
76+
if err := generateAPI(ctx, cfg, api, library, googleapisDir, outdir, metadata, apiCfg); err != nil {
7177
return fmt.Errorf("failed to generate api %q: %w", api.Path, err)
7278
}
7379
}
80+
81+
monorepoVersion, err := findMonorepoVersion(cfg)
82+
if err != nil {
83+
return err
84+
}
85+
if err := generatePomsIfMissing(library, outdir, monorepoVersion, metadata, transports); err != nil {
86+
return fmt.Errorf("failed to generate poms: %w", err)
87+
}
88+
7489
return nil
7590
}
7691

77-
func generateAPI(ctx context.Context, cfg *config.Config, api *config.API, library *config.Library, googleapisDir, outdir string, metadata *repoMetadata) error {
92+
func generateAPI(ctx context.Context, cfg *config.Config, api *config.API, library *config.Library, googleapisDir, outdir string, metadata *repoMetadata, apiCfg *serviceconfig.API) error {
7893
version := serviceconfig.ExtractVersion(api.Path)
7994
if version == "" {
8095
return fmt.Errorf("%s: %w", api.Path, errExtractVersion)
@@ -125,10 +140,6 @@ func generateAPI(ctx context.Context, cfg *config.Config, api *config.API, libra
125140
return fmt.Errorf("failed to generate proto: %w", err)
126141
}
127142
// 2. Generate gRPC service stubs (skipped if transport is rest).
128-
apiCfg, err := serviceconfig.Find(googleapisDir, api.Path, config.LanguageJava)
129-
if err != nil {
130-
return fmt.Errorf("failed to find api config: %w", err)
131-
}
132143
transport := apiCfg.Transport(config.LanguageJava)
133144
if transport != "rest" {
134145
if err := runProtoc(ctx, grpcProtocArgs(apiProtos, googleapisDir, grpcDir)); err != nil {

internal/librarian/java/generate_test.go

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,11 @@ func TestGenerateAPI(t *testing.T) {
389389
if err := os.MkdirAll(templatesDir, 0755); err != nil {
390390
t.Fatal(err)
391391
}
392-
err := generateAPI(
392+
apiCfg, err := serviceconfig.Find(googleapisDir, "google/cloud/secretmanager/v1", config.LanguageJava)
393+
if err != nil {
394+
t.Fatal(err)
395+
}
396+
err = generateAPI(
393397
t.Context(),
394398
cfg,
395399
&config.API{Path: "google/cloud/secretmanager/v1"},
@@ -400,6 +404,7 @@ func TestGenerateAPI(t *testing.T) {
400404
NamePretty: "Secret Manager",
401405
APIDescription: "Secret Manager API",
402406
},
407+
apiCfg,
403408
)
404409
if err != nil {
405410
t.Fatal(err)
@@ -453,10 +458,14 @@ func TestGenerateAPI_NoTools(t *testing.T) {
453458
if err := os.MkdirAll(templatesDir, 0755); err != nil {
454459
t.Fatal(err)
455460
}
456-
err := generateAPI(t.Context(), cfg, api, library, googleapisDir, outdir, &repoMetadata{
461+
apiCfg, err := serviceconfig.Find(googleapisDir, api.Path, config.LanguageJava)
462+
if err != nil {
463+
t.Fatal(err)
464+
}
465+
err = generateAPI(t.Context(), cfg, api, library, googleapisDir, outdir, &repoMetadata{
457466
NamePretty: "Secret Manager",
458467
APIDescription: "Secret Manager API",
459-
})
468+
}, apiCfg)
460469
if err != nil {
461470
t.Fatal(err)
462471
}
@@ -525,12 +534,46 @@ func TestGenerateLibrary_Error(t *testing.T) {
525534
},
526535
wantErr: syscall.ENOTDIR,
527536
},
537+
{
538+
name: "missing monorepo version",
539+
library: &config.Library{
540+
Name: "secretmanager",
541+
Output: t.TempDir(),
542+
APIs: []*config.API{
543+
{Path: "google/cloud/secretmanager/v1"},
544+
},
545+
},
546+
setup: func(t *testing.T, library *config.Library) {
547+
// Ensure output artifacts exist for postProcessAPI to succeed.
548+
for _, artifact := range []string{"google-cloud-secretmanager", "proto-google-cloud-secretmanager-v1", "grpc-google-cloud-secretmanager-v1", "google-cloud-secretmanager-bom"} {
549+
if err := os.MkdirAll(filepath.Join(library.Output, artifact), 0755); err != nil {
550+
t.Fatal(err)
551+
}
552+
}
553+
if err := os.WriteFile(filepath.Join(library.Output, "owlbot.py"), []byte("#!/usr/bin/env python3\npass"), 0755); err != nil {
554+
t.Fatal(err)
555+
}
556+
templatesDir := filepath.Join(filepath.Dir(library.Output), owlbotTemplatesRelPath)
557+
if err := os.MkdirAll(templatesDir, 0755); err != nil {
558+
t.Fatal(err)
559+
}
560+
},
561+
wantErr: errMonorepoVersion,
562+
},
528563
} {
529564
t.Run(test.name, func(t *testing.T) {
565+
// Temporarily mock runProtoc to avoid external tool requirements.
566+
oldRunProtoc := runProtoc
567+
defer func() { runProtoc = oldRunProtoc }()
568+
runProtoc = func(ctx context.Context, args []string) error { return nil }
569+
530570
if test.setup != nil {
531571
test.setup(t, test.library)
532572
}
533-
cfg := &config.Config{Language: "java"}
573+
cfg := &config.Config{
574+
Language: config.LanguageJava,
575+
Libraries: []*config.Library{test.library},
576+
}
534577
err := Generate(t.Context(), cfg, test.library, &sources.Sources{Googleapis: googleapisDir})
535578
if !errors.Is(err, test.wantErr) {
536579
t.Errorf("generate() error = %v, wantErr %v", err, test.wantErr)
@@ -539,6 +582,54 @@ func TestGenerateLibrary_Error(t *testing.T) {
539582
}
540583
}
541584

585+
func TestGenerate_Logic(t *testing.T) {
586+
// Tests the orchestration logic, temporarily mock runProtoc to avoid external tool requirements.
587+
oldRunProtoc := runProtoc
588+
defer func() { runProtoc = oldRunProtoc }()
589+
runProtoc = func(ctx context.Context, args []string) error { return nil }
590+
591+
outdir := t.TempDir()
592+
library := &config.Library{
593+
Name: "secretmanager",
594+
Version: "0.1.2",
595+
Output: outdir,
596+
APIs: []*config.API{
597+
{Path: "google/cloud/secretmanager/v1"},
598+
},
599+
}
600+
cfg := &config.Config{
601+
Language: config.LanguageJava,
602+
Repo: "googleapis/google-cloud-java",
603+
Libraries: []*config.Library{
604+
library,
605+
{Name: rootLibrary, Version: "1.2.3"},
606+
},
607+
}
608+
// Setup mandatory files for postProcessAPI and generatePomsIfMissing
609+
for _, artifact := range []string{"google-cloud-secretmanager", "proto-google-cloud-secretmanager-v1", "grpc-google-cloud-secretmanager-v1", "google-cloud-secretmanager-bom"} {
610+
if err := os.MkdirAll(filepath.Join(outdir, artifact), 0755); err != nil {
611+
t.Fatal(err)
612+
}
613+
}
614+
if err := os.WriteFile(filepath.Join(outdir, "owlbot.py"), []byte("#!/usr/bin/env python3\npass"), 0755); err != nil {
615+
t.Fatal(err)
616+
}
617+
templatesDir := filepath.Join(filepath.Dir(outdir), owlbotTemplatesRelPath)
618+
if err := os.MkdirAll(templatesDir, 0755); err != nil {
619+
t.Fatal(err)
620+
}
621+
622+
err := Generate(t.Context(), cfg, library, &sources.Sources{Googleapis: googleapisDir})
623+
if err != nil {
624+
t.Fatal(err)
625+
}
626+
627+
// Verify that parent pom was generated in the library root.
628+
if _, err := os.Stat(filepath.Join(outdir, "pom.xml")); err != nil {
629+
t.Errorf("expected parent pom.xml to exist: %v", err)
630+
}
631+
}
632+
542633
func TestFormat_Success(t *testing.T) {
543634
testhelper.RequireCommand(t, "google-java-format")
544635
for _, test := range []struct {

0 commit comments

Comments
 (0)