Skip to content

Commit 452d703

Browse files
authored
feat(librariangen): generate grpc stubs and resource helpers (#3967)
* Introduces the generation of gRPC stubs and resource helpers. * Modifies the `Generate` function to create output directories for GAPIC, gRPC, and proto files. * Updates the `invokeProtoc` function to pass an `OutputConfig` struct. * Updates the `restructureOutput` function to handle gRPC stubs and resource names. * Adds a `copyAndMerge` function to merge resource name files into the proto destination. * Updates the `cleanupIntermediateFiles` function to remove the GAPIC, gRPC, and proto directories. * Updates tests to reflect these changes. * Improvements in error handling.
1 parent 85057e8 commit 452d703

File tree

10 files changed

+274
-131
lines changed

10 files changed

+274
-131
lines changed

internal/librariangen/bazel/parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func (c *Config) Validate() error {
6565
}
6666

6767
var javaGapicLibraryRE = regexp.MustCompile(`java_gapic_library\((?s:.)*?\)`)
68+
6869
// Parse reads a BUILD.bazel file from the given directory and extracts the
6970
// relevant configuration from the java_gapic_library rule.
7071
func Parse(dir string) (*Config, error) {

internal/librariangen/bazel/parser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,4 @@ func TestParse_noBuildFile(t *testing.T) {
256256
if err == nil {
257257
t.Error("Parse() succeeded; want error")
258258
}
259-
}
259+
}

internal/librariangen/execv/execv.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ func Run(ctx context.Context, args []string, workingDir string) error {
4848
return fmt.Errorf("librariangen: command failed: %w", err)
4949
}
5050
return nil
51-
}
51+
}

internal/librariangen/execv/execv_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,4 @@ func TestRun(t *testing.T) {
7676
}
7777
})
7878
}
79-
}
79+
}

internal/librariangen/generate/generator.go

Lines changed: 85 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -78,26 +78,29 @@ func Generate(ctx context.Context, cfg *Config) error {
7878
return fmt.Errorf("librariangen: invalid configuration: %w", err)
7979
}
8080
slog.Debug("librariangen: generate command started")
81-
defer cleanupIntermediateFiles(cfg.OutputDir)
81+
outputConfig := &protoc.OutputConfig{
82+
GAPICDir: filepath.Join(cfg.OutputDir, "gapic"),
83+
GRPCDir: filepath.Join(cfg.OutputDir, "grpc"),
84+
ProtoDir: filepath.Join(cfg.OutputDir, "proto"),
85+
}
86+
defer func() {
87+
if err := cleanupIntermediateFiles(outputConfig); err != nil {
88+
slog.Error("librariangen: failed to clean up intermediate files", "error", err)
89+
}
90+
}()
8291

8392
generateReq, err := readGenerateReq(cfg.LibrarianDir)
8493
if err != nil {
8594
return fmt.Errorf("librariangen: failed to read request: %w", err)
8695
}
8796

88-
if err := invokeProtoc(ctx, cfg, generateReq); err != nil {
97+
if err := invokeProtoc(ctx, cfg, generateReq, outputConfig); err != nil {
8998
return fmt.Errorf("librariangen: gapic generation failed: %w", err)
9099
}
91100

92-
// Unzip the generated zip file.
93-
zipPath := filepath.Join(cfg.OutputDir, "java_gapic.zip")
94-
if err := unzip(zipPath, cfg.OutputDir); err != nil {
95-
return fmt.Errorf("librariangen: failed to unzip %s: %w", zipPath, err)
96-
}
97-
98-
// Unzip the inner temp-codegen.srcjar.
99-
srcjarPath := filepath.Join(cfg.OutputDir, "temp-codegen.srcjar")
100-
srcjarDest := filepath.Join(cfg.OutputDir, "java_gapic_srcjar")
101+
// Unzip the temp-codegen.srcjar.
102+
srcjarPath := filepath.Join(outputConfig.GAPICDir, "temp-codegen.srcjar")
103+
srcjarDest := outputConfig.GAPICDir
101104
if err := unzip(srcjarPath, srcjarDest); err != nil {
102105
return fmt.Errorf("librariangen: failed to unzip %s: %w", srcjarPath, err)
103106
}
@@ -113,20 +116,28 @@ func Generate(ctx context.Context, cfg *Config) error {
113116
// invokeProtoc handles the protoc GAPIC generation logic for the 'generate' CLI command.
114117
// It reads a request file, and for each API specified, it invokes protoc
115118
// 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 {
119+
func invokeProtoc(ctx context.Context, cfg *Config, generateReq *message.Library, outputConfig *protoc.OutputConfig) error {
117120
for _, api := range generateReq.APIs {
118121
apiServiceDir := filepath.Join(cfg.SourceDir, api.Path)
119122
slog.Info("processing api", "service_dir", apiServiceDir)
120123
bazelConfig, err := bazelParse(apiServiceDir)
121124
if err != nil {
122125
return fmt.Errorf("librariangen: failed to parse BUILD.bazel for %s: %w", apiServiceDir, err)
123126
}
124-
args, err := protocBuild(apiServiceDir, bazelConfig, cfg.SourceDir, cfg.OutputDir)
127+
args, err := protocBuild(apiServiceDir, bazelConfig, cfg.SourceDir, outputConfig)
125128
if err != nil {
126129
return fmt.Errorf("librariangen: failed to build protoc command for api %q in library %q: %w", api.Path, generateReq.ID, err)
127130
}
131+
132+
// Create protoc output directories.
133+
for _, dir := range []string{outputConfig.ProtoDir, outputConfig.GRPCDir, outputConfig.GAPICDir} {
134+
if err := os.MkdirAll(dir, 0755); err != nil {
135+
return err
136+
}
137+
}
138+
128139
if err := execvRun(ctx, args, cfg.OutputDir); err != nil {
129-
return fmt.Errorf("librariangen: protoc failed for api %q in library %q: %w", api.Path, generateReq.ID, err)
140+
return fmt.Errorf("librariangen: protoc failed for api %q in library %q: %w, execvRun error: %v", api.Path, generateReq.ID, err, err)
130141
}
131142
}
132143
return nil
@@ -158,7 +169,7 @@ func moveFiles(sourceDir, targetDir string) error {
158169
newPath := filepath.Join(targetDir, f.Name())
159170
slog.Debug("librariangen: moving file", "from", oldPath, "to", newPath)
160171
if err := os.Rename(oldPath, newPath); err != nil {
161-
return fmt.Errorf("librariangen: failed to move %s to %s: %w", oldPath, newPath, err)
172+
return fmt.Errorf("librariangen: failed to move %s to %s: %w, os.Rename error: %v", oldPath, newPath, err, err)
162173
}
163174
}
164175
return nil
@@ -168,29 +179,43 @@ func restructureOutput(outputDir, libraryID string) error {
168179
slog.Debug("librariangen: restructuring output directory", "dir", outputDir)
169180

170181
// Define source and destination directories.
171-
gapicSrcDir := filepath.Join(outputDir, "java_gapic_srcjar", "src", "main", "java")
172-
gapicTestDir := filepath.Join(outputDir, "java_gapic_srcjar", "src", "test", "java")
173-
protoSrcDir := filepath.Join(outputDir, "com")
174-
samplesDir := filepath.Join(outputDir, "java_gapic_srcjar", "samples", "snippets")
175-
182+
gapicSrcDir := filepath.Join(outputDir, "gapic", "src", "main", "java")
183+
gapicTestDir := filepath.Join(outputDir, "gapic", "src", "test", "java")
184+
protoSrcDir := filepath.Join(outputDir, "proto")
185+
resourceNameSrcDir := filepath.Join(outputDir, "gapic", "proto", "src", "main", "java")
186+
grpcSrcDir := filepath.Join(outputDir, "grpc")
187+
samplesDir := filepath.Join(outputDir, "gapic", "samples", "snippets")
188+
189+
// TODO(meltsufin): currently we assume we have a single API variant v1
176190
gapicDestDir := filepath.Join(outputDir, fmt.Sprintf("google-cloud-%s", libraryID), "src", "main", "java")
177191
gapicTestDestDir := filepath.Join(outputDir, fmt.Sprintf("google-cloud-%s", libraryID), "src", "test", "java")
178192
protoDestDir := filepath.Join(outputDir, fmt.Sprintf("proto-google-cloud-%s-v1", libraryID), "src", "main", "java")
193+
resourceNameDestDir := filepath.Join(outputDir, fmt.Sprintf("proto-google-cloud-%s-v1", libraryID), "src", "main", "java")
194+
grpcDestDir := filepath.Join(outputDir, fmt.Sprintf("grpc-google-cloud-%s-v1", libraryID), "src", "main", "java")
179195
samplesDestDir := filepath.Join(outputDir, "samples", "snippets")
180196

181197
// Create destination directories.
182-
destDirs := []string{gapicDestDir, gapicTestDestDir, protoDestDir, samplesDestDir}
198+
destDirs := []string{gapicDestDir, gapicTestDestDir, protoDestDir, samplesDestDir, grpcDestDir}
183199
for _, dir := range destDirs {
184200
if err := os.MkdirAll(dir, 0755); err != nil {
185201
return err
186202
}
187203
}
188204

189-
// Move files.
205+
// The resource name directory is not created if there are no resource names
206+
// to generate. We create it here to avoid errors later.
207+
if _, err := os.Stat(resourceNameSrcDir); os.IsNotExist(err) {
208+
if err := os.MkdirAll(resourceNameSrcDir, 0755); err != nil {
209+
return err
210+
}
211+
}
212+
213+
// Move files that won't have conflicts.
190214
moves := map[string]string{
191215
gapicSrcDir: gapicDestDir,
192216
gapicTestDir: gapicTestDestDir,
193217
protoSrcDir: protoDestDir,
218+
grpcSrcDir: grpcDestDir,
194219
samplesDir: samplesDestDir,
195220
}
196221
for src, dest := range moves {
@@ -199,23 +224,51 @@ func restructureOutput(outputDir, libraryID string) error {
199224
}
200225
}
201226

227+
// Merge the resource name files into the proto destination.
228+
if err := copyAndMerge(resourceNameSrcDir, resourceNameDestDir); err != nil {
229+
return err
230+
}
231+
202232
return nil
203233
}
204234

205-
func cleanupIntermediateFiles(outputDir string) {
206-
slog.Debug("librariangen: cleaning up intermediate files", "dir", outputDir)
207-
filesToRemove := []string{
208-
"java_gapic_srcjar",
209-
"com",
210-
"java_gapic.zip",
211-
"temp-codegen.srcjar",
235+
// copyAndMerge recursively copies the contents of src to dest, merging directories.
236+
func copyAndMerge(src, dest string) error {
237+
entries, err := os.ReadDir(src)
238+
if os.IsNotExist(err) {
239+
return nil
240+
}
241+
if err != nil {
242+
return err
243+
}
244+
245+
for _, entry := range entries {
246+
srcPath := filepath.Join(src, entry.Name())
247+
destPath := filepath.Join(dest, entry.Name())
248+
if entry.IsDir() {
249+
if err := os.MkdirAll(destPath, 0755); err != nil {
250+
return err
251+
}
252+
if err := copyAndMerge(srcPath, destPath); err != nil {
253+
return err
254+
}
255+
} else {
256+
if err := os.Rename(srcPath, destPath); err != nil {
257+
return fmt.Errorf("librariangen: failed to move %s to %s: %w, os.Rename error: %v", srcPath, destPath, err, err)
258+
}
259+
}
212260
}
213-
for _, file := range filesToRemove {
214-
path := filepath.Join(outputDir, file)
261+
return nil
262+
}
263+
264+
func cleanupIntermediateFiles(outputConfig *protoc.OutputConfig) error {
265+
slog.Debug("librariangen: cleaning up intermediate files")
266+
for _, path := range []string{outputConfig.GAPICDir, outputConfig.GRPCDir, outputConfig.ProtoDir} {
215267
if err := os.RemoveAll(path); err != nil {
216-
slog.Error("librariangen: failed to clean up intermediate file", "path", path, "error", err)
268+
return fmt.Errorf("failed to clean up intermediate file at %s: %w", path, err)
217269
}
218270
}
271+
return nil
219272
}
220273

221274
func unzip(src, dest string) error {

0 commit comments

Comments
 (0)