Skip to content

Commit 0a1bbea

Browse files
authored
chore(librariangen): Generate to use languagecontainer.Run (#3968)
1 parent 452d703 commit 0a1bbea

File tree

8 files changed

+405
-203
lines changed

8 files changed

+405
-203
lines changed

internal/librariangen/generate/generator.go

Lines changed: 15 additions & 70 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,85 +26,46 @@ 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 {
78-
return fmt.Errorf("librariangen: invalid configuration: %w", err)
79-
}
43+
func Generate(ctx context.Context, cfg *generate.Config) error {
8044
slog.Debug("librariangen: generate command started")
8145
outputConfig := &protoc.OutputConfig{
82-
GAPICDir: filepath.Join(cfg.OutputDir, "gapic"),
83-
GRPCDir: filepath.Join(cfg.OutputDir, "grpc"),
84-
ProtoDir: filepath.Join(cfg.OutputDir, "proto"),
46+
GAPICDir: filepath.Join(cfg.Context.OutputDir, "gapic"),
47+
GRPCDir: filepath.Join(cfg.Context.OutputDir, "grpc"),
48+
ProtoDir: filepath.Join(cfg.Context.OutputDir, "proto"),
8549
}
8650
defer func() {
8751
if err := cleanupIntermediateFiles(outputConfig); err != nil {
8852
slog.Error("librariangen: failed to clean up intermediate files", "error", err)
8953
}
9054
}()
9155

92-
generateReq, err := readGenerateReq(cfg.LibrarianDir)
93-
if err != nil {
94-
return fmt.Errorf("librariangen: failed to read request: %w", err)
95-
}
56+
generateReq := cfg.Request
9657

97-
if err := invokeProtoc(ctx, cfg, generateReq, outputConfig); err != nil {
58+
if err := invokeProtoc(ctx, cfg.Context, generateReq, outputConfig); err != nil {
9859
return fmt.Errorf("librariangen: gapic generation failed: %w", err)
9960
}
100-
10161
// Unzip the temp-codegen.srcjar.
10262
srcjarPath := filepath.Join(outputConfig.GAPICDir, "temp-codegen.srcjar")
10363
srcjarDest := outputConfig.GAPICDir
10464
if err := unzip(srcjarPath, srcjarDest); err != nil {
10565
return fmt.Errorf("librariangen: failed to unzip %s: %w", srcjarPath, err)
10666
}
10767

108-
if err := restructureOutput(cfg.OutputDir, generateReq.ID); err != nil {
68+
if err := restructureOutput(cfg.Context.OutputDir, generateReq.ID); err != nil {
10969
return fmt.Errorf("librariangen: failed to restructure output: %w", err)
11070
}
11171

@@ -116,15 +76,15 @@ func Generate(ctx context.Context, cfg *Config) error {
11676
// invokeProtoc handles the protoc GAPIC generation logic for the 'generate' CLI command.
11777
// It reads a request file, and for each API specified, it invokes protoc
11878
// to generate the client library. It returns the module path and the path to the service YAML.
119-
func invokeProtoc(ctx context.Context, cfg *Config, generateReq *message.Library, outputConfig *protoc.OutputConfig) error {
79+
func invokeProtoc(ctx context.Context, genCtx *generate.Context, generateReq *message.Library, outputConfig *protoc.OutputConfig) error {
12080
for _, api := range generateReq.APIs {
121-
apiServiceDir := filepath.Join(cfg.SourceDir, api.Path)
81+
apiServiceDir := filepath.Join(genCtx.SourceDir, api.Path)
12282
slog.Info("processing api", "service_dir", apiServiceDir)
12383
bazelConfig, err := bazelParse(apiServiceDir)
12484
if err != nil {
12585
return fmt.Errorf("librariangen: failed to parse BUILD.bazel for %s: %w", apiServiceDir, err)
12686
}
127-
args, err := protocBuild(apiServiceDir, bazelConfig, cfg.SourceDir, outputConfig)
87+
args, err := protocBuild(apiServiceDir, bazelConfig, genCtx.SourceDir, outputConfig)
12888
if err != nil {
12989
return fmt.Errorf("librariangen: failed to build protoc command for api %q in library %q: %w", api.Path, generateReq.ID, err)
13090
}
@@ -136,28 +96,13 @@ func invokeProtoc(ctx context.Context, cfg *Config, generateReq *message.Library
13696
}
13797
}
13898

139-
if err := execvRun(ctx, args, cfg.OutputDir); err != nil {
99+
if err := execvRun(ctx, args, genCtx.OutputDir); err != nil {
140100
return fmt.Errorf("librariangen: protoc failed for api %q in library %q: %w, execvRun error: %v", api.Path, generateReq.ID, err, err)
141101
}
142102
}
143103
return nil
144104
}
145105

146-
// readGenerateReq reads generate-request.json from the librarian-tool input directory.
147-
// The request file tells librariangen which library and APIs to generate.
148-
// It is prepared by the Librarian tool and mounted at /librarian.
149-
func readGenerateReq(librarianDir string) (*message.Library, error) {
150-
reqPath := filepath.Join(librarianDir, "generate-request.json")
151-
slog.Debug("librariangen: reading generate request", "path", reqPath)
152-
153-
generateReq, err := requestParse(reqPath)
154-
if err != nil {
155-
return nil, err
156-
}
157-
slog.Debug("librariangen: successfully unmarshalled request", "library_id", generateReq.ID)
158-
return generateReq, nil
159-
}
160-
161106
// moveFiles moves all files (and directories) from sourceDir to targetDir.
162107
func moveFiles(sourceDir, targetDir string) error {
163108
files, err := os.ReadDir(sourceDir)

internal/librariangen/generate/generator_test.go

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"path/filepath"
2323
"testing"
2424

25+
"cloud.google.com/java/internal/librariangen/languagecontainer/generate"
2526
"cloud.google.com/java/internal/librariangen/protoc"
2627
)
2728

@@ -252,12 +253,20 @@ java_gapic_library(
252253
protocRunCount++
253254
return tt.protocErr
254255
}
255-
cfg := &Config{
256+
genCtx := &generate.Context{
256257
LibrarianDir: e.librarianDir,
257258
InputDir: "fake-input",
258259
OutputDir: e.outputDir,
259260
SourceDir: e.sourceDir,
260261
}
262+
cfg, err := generate.NewConfig(genCtx)
263+
if err != nil && tt.wantErr {
264+
// If we expect an error, and NewConfig fails, that's ok.
265+
return
266+
}
267+
if err != nil {
268+
t.Fatalf("failed to create generate config: %v", err)
269+
}
261270
if err := Generate(context.Background(), cfg); (err != nil) != tt.wantErr {
262271
t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr)
263272
}
@@ -268,68 +277,6 @@ java_gapic_library(
268277
}
269278
}
270279

271-
func TestConfig_Validate(t *testing.T) {
272-
tests := []struct {
273-
name string
274-
cfg *Config
275-
wantErr bool
276-
}{
277-
{
278-
name: "valid",
279-
cfg: &Config{
280-
LibrarianDir: "a",
281-
InputDir: "b",
282-
OutputDir: "c",
283-
SourceDir: "d",
284-
},
285-
wantErr: false,
286-
},
287-
{
288-
name: "missing librarian dir",
289-
cfg: &Config{
290-
InputDir: "b",
291-
OutputDir: "c",
292-
SourceDir: "d",
293-
},
294-
wantErr: true,
295-
},
296-
{
297-
name: "missing input dir",
298-
cfg: &Config{
299-
LibrarianDir: "a",
300-
OutputDir: "c",
301-
SourceDir: "d",
302-
},
303-
wantErr: true,
304-
},
305-
{
306-
name: "missing output dir",
307-
cfg: &Config{
308-
LibrarianDir: "a",
309-
InputDir: "b",
310-
SourceDir: "d",
311-
},
312-
wantErr: true,
313-
},
314-
{
315-
name: "missing source dir",
316-
cfg: &Config{
317-
LibrarianDir: "a",
318-
InputDir: "b",
319-
OutputDir: "c",
320-
},
321-
wantErr: true,
322-
},
323-
}
324-
for _, tt := range tests {
325-
t.Run(tt.name, func(t *testing.T) {
326-
if err := tt.cfg.Validate(); (err != nil) != tt.wantErr {
327-
t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
328-
}
329-
})
330-
}
331-
}
332-
333280
func TestRestructureOutput(t *testing.T) {
334281
e := newTestEnv(t)
335282
defer e.cleanup(t)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
"fmt"
21+
"log/slog"
22+
"path/filepath"
23+
24+
"cloud.google.com/java/internal/librariangen/message"
25+
)
26+
27+
// Context holds the directory paths for the generate command.
28+
// https://github.com/googleapis/librarian/blob/main/doc/language-onboarding.md#generate
29+
type Context struct {
30+
// LibrarianDir is the path to the librarian-tool input directory.
31+
// It is expected to contain the generate-request.json file.
32+
LibrarianDir string
33+
// InputDir is the path to the .librarian/generator-input directory from the
34+
// language repository.
35+
InputDir string
36+
// OutputDir is the path to the empty directory where librariangen writes
37+
// its output.
38+
OutputDir string
39+
// SourceDir is the path to a complete checkout of the googleapis repository.
40+
SourceDir string
41+
}
42+
43+
// Validate ensures that the context is valid.
44+
func (c *Context) Validate() error {
45+
if c.LibrarianDir == "" {
46+
return errors.New("languagecontainer: librarian directory must be set")
47+
}
48+
if c.InputDir == "" {
49+
return errors.New("languagecontainer: input directory must be set")
50+
}
51+
if c.OutputDir == "" {
52+
return errors.New("languagecontainer: output directory must be set")
53+
}
54+
if c.SourceDir == "" {
55+
return errors.New("languagecontainer: source directory must be set")
56+
}
57+
return nil
58+
}
59+
60+
// Config for the generate command. This holds the context (the directory paths)
61+
// and the request parsed from the generate-request.json file.
62+
type Config struct {
63+
Context *Context
64+
// This request is parsed from the generate-request.json file in
65+
// the LibrarianDir of the context.
66+
Request *message.Library
67+
}
68+
69+
// NewConfig creates a new Config, parsing the generate-request.json file
70+
// from the LibrarianDir in the given Context.
71+
func NewConfig(ctx *Context) (*Config, error) {
72+
if err := ctx.Validate(); err != nil {
73+
return nil, fmt.Errorf("invalid context: %w", err)
74+
}
75+
reqPath := filepath.Join(ctx.LibrarianDir, "generate-request.json")
76+
slog.Debug("languagecontainer: reading generate request", "path", reqPath)
77+
78+
generateReq, err := message.ParseLibrary(reqPath)
79+
if err != nil {
80+
return nil, err
81+
}
82+
slog.Debug("languagecontainer: successfully unmarshalled request", "library_id", generateReq.ID)
83+
return &Config{
84+
Context: ctx,
85+
Request: generateReq,
86+
}, nil
87+
}

0 commit comments

Comments
 (0)