Skip to content

Commit b68161a

Browse files
committed
chore(librariangen): Generate to use languagecontainer.Run
1 parent a26a6d9 commit b68161a

File tree

9 files changed

+318
-197
lines changed

9 files changed

+318
-197
lines changed

internal/librariangen/generate/generator.go

Lines changed: 18 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package generate
1717
import (
1818
"archive/zip"
1919
"context"
20-
"errors"
2120
"fmt"
2221
"io"
2322
"log/slog"
@@ -27,82 +26,47 @@ import (
2726

2827
"cloud.google.com/java/internal/librariangen/bazel"
2928
"cloud.google.com/java/internal/librariangen/execv"
29+
"cloud.google.com/java/internal/librariangen/languagecontainer/generate"
3030
"cloud.google.com/java/internal/librariangen/message"
3131
"cloud.google.com/java/internal/librariangen/protoc"
3232
)
3333

3434
// Test substitution vars.
3535
var (
36-
bazelParse = bazel.Parse
37-
execvRun = execv.Run
38-
requestParse = message.ParseLibrary
39-
protocBuild = protoc.Build
36+
bazelParse = bazel.Parse
37+
execvRun = execv.Run
38+
protocBuild = protoc.Build
4039
)
4140

42-
// Config holds the internal librariangen configuration for the generate command.
43-
type Config struct {
44-
// LibrarianDir is the path to the librarian-tool input directory.
45-
// It is expected to contain the generate-request.json file.
46-
LibrarianDir string
47-
// InputDir is the path to the .librarian/generator-input directory from the
48-
// language repository.
49-
InputDir string
50-
// OutputDir is the path to the empty directory where librariangen writes
51-
// its output.
52-
OutputDir string
53-
// SourceDir is the path to a complete checkout of the googleapis repository.
54-
SourceDir string
55-
}
56-
57-
// Validate ensures that the configuration is valid.
58-
func (c *Config) Validate() error {
59-
if c.LibrarianDir == "" {
60-
return errors.New("librariangen: librarian directory must be set")
61-
}
62-
if c.InputDir == "" {
63-
return errors.New("librariangen: input directory must be set")
64-
}
65-
if c.OutputDir == "" {
66-
return errors.New("librariangen: output directory must be set")
67-
}
68-
if c.SourceDir == "" {
69-
return errors.New("librariangen: source directory must be set")
70-
}
71-
return nil
72-
}
73-
7441
// Generate is the main entrypoint for the `generate` command. It orchestrates
7542
// the entire generation process.
76-
func Generate(ctx context.Context, cfg *Config) error {
77-
if err := cfg.Validate(); err != nil {
43+
func Generate(ctx context.Context, cfg *generate.Config) error {
44+
if err := cfg.Context.Validate(); err != nil {
7845
return fmt.Errorf("librariangen: invalid configuration: %w", err)
7946
}
8047
slog.Debug("librariangen: generate command started")
81-
defer cleanupIntermediateFiles(cfg.OutputDir)
48+
defer cleanupIntermediateFiles(cfg.Context.OutputDir)
8249

83-
generateReq, err := readGenerateReq(cfg.LibrarianDir)
84-
if err != nil {
85-
return fmt.Errorf("librariangen: failed to read request: %w", err)
86-
}
50+
generateReq := cfg.Request
8751

88-
if err := invokeProtoc(ctx, cfg, generateReq); err != nil {
52+
if err := invokeProtoc(ctx, cfg.Context, generateReq); err != nil {
8953
return fmt.Errorf("librariangen: gapic generation failed: %w", err)
9054
}
9155

9256
// Unzip the generated zip file.
93-
zipPath := filepath.Join(cfg.OutputDir, "java_gapic.zip")
94-
if err := unzip(zipPath, cfg.OutputDir); err != nil {
57+
zipPath := filepath.Join(cfg.Context.OutputDir, "java_gapic.zip")
58+
if err := unzip(zipPath, cfg.Context.OutputDir); err != nil {
9559
return fmt.Errorf("librariangen: failed to unzip %s: %w", zipPath, err)
9660
}
9761

9862
// Unzip the inner temp-codegen.srcjar.
99-
srcjarPath := filepath.Join(cfg.OutputDir, "temp-codegen.srcjar")
100-
srcjarDest := filepath.Join(cfg.OutputDir, "java_gapic_srcjar")
63+
srcjarPath := filepath.Join(cfg.Context.OutputDir, "temp-codegen.srcjar")
64+
srcjarDest := filepath.Join(cfg.Context.OutputDir, "java_gapic_srcjar")
10165
if err := unzip(srcjarPath, srcjarDest); err != nil {
10266
return fmt.Errorf("librariangen: failed to unzip %s: %w", srcjarPath, err)
10367
}
10468

105-
if err := restructureOutput(cfg.OutputDir, generateReq.ID); err != nil {
69+
if err := restructureOutput(cfg.Context.OutputDir, generateReq.ID); err != nil {
10670
return fmt.Errorf("librariangen: failed to restructure output: %w", err)
10771
}
10872

@@ -113,40 +77,25 @@ func Generate(ctx context.Context, cfg *Config) error {
11377
// invokeProtoc handles the protoc GAPIC generation logic for the 'generate' CLI command.
11478
// It reads a request file, and for each API specified, it invokes protoc
11579
// to generate the client library. It returns the module path and the path to the service YAML.
116-
func invokeProtoc(ctx context.Context, cfg *Config, generateReq *message.Library) error {
80+
func invokeProtoc(ctx context.Context, genCtx *generate.Context, generateReq *message.Library) error {
11781
for _, api := range generateReq.APIs {
118-
apiServiceDir := filepath.Join(cfg.SourceDir, api.Path)
82+
apiServiceDir := filepath.Join(genCtx.SourceDir, api.Path)
11983
slog.Info("processing api", "service_dir", apiServiceDir)
12084
bazelConfig, err := bazelParse(apiServiceDir)
12185
if err != nil {
12286
return fmt.Errorf("librariangen: failed to parse BUILD.bazel for %s: %w", apiServiceDir, err)
12387
}
124-
args, err := protocBuild(apiServiceDir, bazelConfig, cfg.SourceDir, cfg.OutputDir)
88+
args, err := protocBuild(apiServiceDir, bazelConfig, genCtx.SourceDir, genCtx.OutputDir)
12589
if err != nil {
12690
return fmt.Errorf("librariangen: failed to build protoc command for api %q in library %q: %w", api.Path, generateReq.ID, err)
12791
}
128-
if err := execvRun(ctx, args, cfg.OutputDir); err != nil {
92+
if err := execvRun(ctx, args, genCtx.OutputDir); err != nil {
12993
return fmt.Errorf("librariangen: protoc failed for api %q in library %q: %w", api.Path, generateReq.ID, err)
13094
}
13195
}
13296
return nil
13397
}
13498

135-
// readGenerateReq reads generate-request.json from the librarian-tool input directory.
136-
// The request file tells librariangen which library and APIs to generate.
137-
// It is prepared by the Librarian tool and mounted at /librarian.
138-
func readGenerateReq(librarianDir string) (*message.Library, error) {
139-
reqPath := filepath.Join(librarianDir, "generate-request.json")
140-
slog.Debug("librariangen: reading generate request", "path", reqPath)
141-
142-
generateReq, err := requestParse(reqPath)
143-
if err != nil {
144-
return nil, err
145-
}
146-
slog.Debug("librariangen: successfully unmarshalled request", "library_id", generateReq.ID)
147-
return generateReq, nil
148-
}
149-
15099
// moveFiles moves all files (and directories) from sourceDir to targetDir.
151100
func moveFiles(sourceDir, targetDir string) error {
152101
files, err := os.ReadDir(sourceDir)

internal/librariangen/generate/generator_test.go

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"path/filepath"
2525
"strings"
2626
"testing"
27+
28+
"cloud.google.com/java/internal/librariangen/languagecontainer/generate"
2729
)
2830

2931
// testEnv encapsulates a temporary test environment.
@@ -267,12 +269,20 @@ java_gapic_library(
267269
protocRunCount++
268270
return tt.protocErr
269271
}
270-
cfg := &Config{
272+
genCtx := &generate.Context{
271273
LibrarianDir: e.librarianDir,
272274
InputDir: "fake-input",
273275
OutputDir: e.outputDir,
274276
SourceDir: e.sourceDir,
275277
}
278+
cfg, err := generate.NewConfig(genCtx)
279+
if err != nil && tt.wantErr {
280+
// If we expect an error, and NewConfig fails, that's ok.
281+
return
282+
}
283+
if err != nil {
284+
t.Fatalf("failed to create generate config: %v", err)
285+
}
276286
if err := Generate(context.Background(), cfg); (err != nil) != tt.wantErr {
277287
t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr)
278288
}
@@ -283,68 +293,6 @@ java_gapic_library(
283293
}
284294
}
285295

286-
func TestConfig_Validate(t *testing.T) {
287-
tests := []struct {
288-
name string
289-
cfg *Config
290-
wantErr bool
291-
}{
292-
{
293-
name: "valid",
294-
cfg: &Config{
295-
LibrarianDir: "a",
296-
InputDir: "b",
297-
OutputDir: "c",
298-
SourceDir: "d",
299-
},
300-
wantErr: false,
301-
},
302-
{
303-
name: "missing librarian dir",
304-
cfg: &Config{
305-
InputDir: "b",
306-
OutputDir: "c",
307-
SourceDir: "d",
308-
},
309-
wantErr: true,
310-
},
311-
{
312-
name: "missing input dir",
313-
cfg: &Config{
314-
LibrarianDir: "a",
315-
OutputDir: "c",
316-
SourceDir: "d",
317-
},
318-
wantErr: true,
319-
},
320-
{
321-
name: "missing output dir",
322-
cfg: &Config{
323-
LibrarianDir: "a",
324-
InputDir: "b",
325-
SourceDir: "d",
326-
},
327-
wantErr: true,
328-
},
329-
{
330-
name: "missing source dir",
331-
cfg: &Config{
332-
LibrarianDir: "a",
333-
InputDir: "b",
334-
OutputDir: "c",
335-
},
336-
wantErr: true,
337-
},
338-
}
339-
for _, tt := range tests {
340-
t.Run(tt.name, func(t *testing.T) {
341-
if err := tt.cfg.Validate(); (err != nil) != tt.wantErr {
342-
t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
343-
}
344-
})
345-
}
346-
}
347-
348296
func TestRestructureOutput(t *testing.T) {
349297
e := newTestEnv(t)
350298
defer e.cleanup(t)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package generate contains types for language container's generate command.
16+
package generate
17+
18+
import (
19+
"errors"
20+
"log/slog"
21+
"path/filepath"
22+
23+
"cloud.google.com/java/internal/librariangen/message"
24+
)
25+
26+
// Context holds the directory paths for the generate command.
27+
// https://github.com/googleapis/librarian/blob/main/doc/language-onboarding.md#generate
28+
type Context struct {
29+
// LibrarianDir is the path to the librarian-tool input directory.
30+
// It is expected to contain the generate-request.json file.
31+
LibrarianDir string
32+
// InputDir is the path to the .librarian/generator-input directory from the
33+
// language repository.
34+
InputDir string
35+
// OutputDir is the path to the empty directory where librariangen writes
36+
// its output.
37+
OutputDir string
38+
// SourceDir is the path to a complete checkout of the googleapis repository.
39+
SourceDir string
40+
}
41+
42+
// Validate ensures that the context is valid.
43+
func (c *Context) Validate() error {
44+
if c.LibrarianDir == "" {
45+
return errors.New("librariangen: librarian directory must be set")
46+
}
47+
if c.InputDir == "" {
48+
return errors.New("librariangen: input directory must be set")
49+
}
50+
if c.OutputDir == "" {
51+
return errors.New("librariangen: output directory must be set")
52+
}
53+
if c.SourceDir == "" {
54+
return errors.New("librariangen: source directory must be set")
55+
}
56+
return nil
57+
}
58+
59+
// Config for the generate command. This holds the context (the directory paths)
60+
// and the request parsed from the generate-request.json file.
61+
type Config struct {
62+
Context *Context
63+
// This request is parsed from the generate-request.json file in
64+
// the LibrarianDir of the context.
65+
Request *message.Library
66+
}
67+
68+
// NewConfig creates a new Config, parsing the generate-request.json file
69+
// from the LibrarianDir in the given Context.
70+
func NewConfig(ctx *Context) (*Config, error) {
71+
reqPath := filepath.Join(ctx.LibrarianDir, "generate-request.json")
72+
slog.Debug("librariangen: reading generate request", "path", reqPath)
73+
74+
generateReq, err := message.ParseLibrary(reqPath)
75+
if err != nil {
76+
return nil, err
77+
}
78+
slog.Debug("librariangen: successfully unmarshalled request", "library_id", generateReq.ID)
79+
return &Config{
80+
Context: ctx,
81+
Request: generateReq,
82+
}, nil
83+
}
84+

0 commit comments

Comments
 (0)