Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions internal/librariangen/languagecontainer/languagecontainer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package languagecontainer defines LanguageContainer interface and
// the Run function to execute commands within the container.
// TODO(b/447404382): Move this package to the https://github.com/googleapis/librarian
// GitHub repository once the interface is finalized.
// This package must not have any Java-specific implementation.
package languagecontainer

import (
"context"
"log/slog"

"cloud.google.com/java/internal/librariangen/message"
)

// GenerateCommandContext holds the context (the file system paths) for
// the generate command. The createGenerateContext function creates
// an instance of this by reading the command line flags and the
// default values.
type GenerateCommandContext struct {
}
type GenerateCommandEnv struct {
GenerateContext *GenerateCommandContext
GenerateRequest *message.GenerateRequest
}

type ConfigureCommandContext struct {
}

type ConfigureCommandEnv struct {
ConfigureContext *ConfigureCommandContext
ConfigureRequest *message.ConfigureRequest
}

// LanguageContainer defines the interface for language-specific container operations.
type LanguageContainer interface {
Generate(context.Context, *GenerateCommandEnv) error
Configure(context.Context, *ConfigureCommandEnv) (*message.ConfigureResponse, error)
// Other container functions like ReleaseInit and Build would also be part of the interface.
}

// Run would accept an implementation of the LanguageContainer interface.
func Run(args []string, container LanguageContainer) int {
// Logic to parse args and call the appropriate method on the container.
// For example, if args[1] is "generate":
// request := ... // unmarshal the request from the expected location
// err := container.Generate(context.Background(), request)
// ...
if len(args) < 1 {
panic("args must not be empty")
}
Comment on lines +62 to +64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Panicking on invalid user input (like missing arguments) is ungraceful for a command-line tool. It's better to print an error message and exit with a non-zero status code. This provides a better user experience.

	if len(args) < 1 {
		slog.Error("librariangen: expected a command")
		return 1
	}

switch args[0] {
case "generate":
env := createGenerateEnv(args[1:])
err := container.Generate(context.Background(), env)
if err != nil {
// TODO: Save it as a response file.
panic(err)
}
Comment on lines +69 to +72

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using panic for handling errors from business logic can lead to abrupt program termination, which is generally undesirable for a CLI tool. It's better to log the error and return a non-zero exit code to indicate failure gracefully.

		if err != nil {
			// TODO: Save it as a response file.
			slog.Error("librariangen: generate command failed", "error", err)
			return 1
		}

return 0
case "configure":
env := createConfigureEnv(args[1:])
resp, err := container.Configure(context.Background(), env)
if err != nil {
panic(err)
}
Comment on lines +77 to +79

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using panic for handling errors can lead to abrupt program termination. It's better to log the error and return a non-zero exit code to indicate failure gracefully, which is more conventional for CLI applications.

		if err != nil {
			slog.Error("librariangen: configure command failed", "error", err)
			return 1
		}

// TODO: Save it as a response file.
_ = resp
return 0
case "release-init":
slog.Warn("librariangen: release-init command is not yet implemented")
return 1
case "build":
slog.Warn("librariangen: build command is not yet implemented")
return 1
default:
slog.Error("librariangen: unknown command: %s (with flags %v)", args[0], args)
return 1
}
return 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This return 0 statement is unreachable because all paths in the preceding switch statement return a value. This line can be removed.

}

// https://github.com/googleapis/librarian/blob/main/doc/language-onboarding.md#generate
func createGenerateEnv(args []string) *GenerateCommandEnv {
generateContext := createGenerateCommandContext(args)
return &GenerateCommandEnv{
GenerateContext: generateContext,
}
}

func createGenerateCommandContext(args []string) *GenerateCommandContext {
// TODO: Parse args and create the context.
return &GenerateCommandContext{}
}

func createConfigureCommandContext(args []string) *ConfigureCommandContext {
// TODO: Parse args and create the context.
return &ConfigureCommandContext{}
}

func createConfigureEnv(args []string) *ConfigureCommandEnv {
configureContext := createConfigureCommandContext(args)
return &ConfigureCommandEnv{
ConfigureContext: configureContext,
}
}
75 changes: 23 additions & 52 deletions internal/librariangen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,42 @@ package main

import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"strings"

"cloud.google.com/java/internal/librariangen/languagecontainer"
"cloud.google.com/java/internal/librariangen/message"
)

const version = "0.1.0"

// main is the entrypoint for the librariangen CLI.
func main() {
os.Exit(runCLI(os.Args))
// javaContainer implements the LanguageContainer interface for Java.
type javaContainer struct{}

func (c *javaContainer) Generate(context.Context, *languagecontainer.GenerateCommandEnv) error {
// Java-specific implementation for the "generate" command.
slog.Warn("librariangen: generate command is not yet implemented")

return nil
}

func runCLI(args []string) int {
func (c *javaContainer) Configure(ctx context.Context,
request *languagecontainer.ConfigureCommandEnv) (*message.ConfigureResponse, error) {
// Java-specific implementation for the "configure" command.
slog.Warn("librariangen: configure command is not yet implemented")
return nil, nil
}

// main is the entrypoint for the librariangen CLI.
func main() {
logLevel := parseLogLevel(os.Getenv("GOOGLE_SDK_JAVA_LOGGING_LEVEL"))
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: logLevel,
})))
args := os.Args
slog.Info("librariangen: invoked", "args", args)
if err := run(context.Background(), args[1:]); err != nil {
slog.Error("librariangen: failed", "error", err)
return 1
}
slog.Info("librariangen: finished successfully")
return 0
container := javaContainer{}
os.Exit(languagecontainer.Run(os.Args, &container))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The Run function in languagecontainer expects the command-line arguments without the program name. Currently, os.Args is passed, which includes the program name as the first element (os.Args[0]). This will cause the command dispatching in Run to fail, as it will try to match the program name against commands like generate. You should pass os.Args[1:] instead.

Suggested change
os.Exit(languagecontainer.Run(os.Args, &container))
os.Exit(languagecontainer.Run(os.Args[1:], &container))

}

func parseLogLevel(logLevelEnv string) slog.Level {
Expand All @@ -54,42 +64,3 @@ func parseLogLevel(logLevelEnv string) slog.Level {
return slog.LevelInfo
}
}

// run executes the appropriate command based on the CLI's invocation arguments.
// The idiomatic structure is `librariangen [command] [flags]`.
func run(ctx context.Context, args []string) error {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The removed run function handled the --version flag. This logic has not been moved to the new implementation, which is a regression. The --version flag handling should be added back, likely in the main function before calling languagecontainer.Run.

if len(args) < 1 {
return errors.New("librariangen: expected a command")
}

// The --version flag is a special case and not a command.
if args[0] == "--version" {
fmt.Println(version)
return nil
}

cmd := args[0]
flags := args[1:]

if strings.HasPrefix(cmd, "-") {
return fmt.Errorf("librariangen: command cannot be a flag: %s", cmd)
}

switch cmd {
case "generate":
slog.Warn("librariangen: generate command is not yet implemented")
return nil
case "release-init":
slog.Warn("librariangen: release-init command is not yet implemented")
return nil
case "configure":
slog.Warn("librariangen: configure command is not yet implemented")
return nil
case "build":
slog.Warn("librariangen: build command is not yet implemented")
return nil
default:
return fmt.Errorf("librariangen: unknown command: %s (with flags %s)", cmd, flags)
}

}
42 changes: 42 additions & 0 deletions internal/librariangen/message/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package message defines data types which the Librarian CLI and language
// containers exchange.
// There shouldn't be CLI-specific data type or language container-specific
// data types in this package.
// TODO(b/447404382): Move this package to the https://github.com/googleapis/librarian
// GitHub repository once the interface is finalized.
// This package must not have any Java-specific implementation.
package message

// GenerateRequest is the JSON message sent to the language container by
// the Librarian CLI when the "generate" command is invoked.
type GenerateRequest struct {
}

// GenerateResponse is the JSON message sent back to the Librarian CLI
// by the language container after processing the "generate" command.
type GenerateResponse struct {
}

// ConfigureRequest is the JSON message sent to the language container by
// the Librarian CLI when the "configure" command is invoked.
type ConfigureRequest struct {
}

// ConfigureResponse is the JSON message sent back to the Librarian CLI
// by the language container after processing the "configure" command.
type ConfigureResponse struct {
}
Loading