Skip to content

Commit 64268c1

Browse files
authored
refactor(internal/librarian): create newLibrarianCommand (#2162)
Move logic out of init function into newLibrarianCommand. This makes the command creation more testable and avoids hidden side effects of global init functions.
1 parent b087209 commit 64268c1

File tree

9 files changed

+278
-313
lines changed

9 files changed

+278
-313
lines changed

internal/librarian/action_test.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,28 @@ import (
2222
"github.com/googleapis/librarian/internal/config"
2323
)
2424

25-
func TestGenerateAction(t *testing.T) {
26-
t.Parallel()
27-
testActionConfig(t, cmdGenerate)
28-
}
29-
30-
func TestInitAction(t *testing.T) {
31-
t.Parallel()
32-
testActionConfig(t, cmdInit)
33-
}
34-
35-
func TestTagAndReleaseAction(t *testing.T) {
36-
t.Parallel()
37-
testActionConfig(t, cmdTagAndRelease)
25+
func TestLibrarianAction(t *testing.T) {
26+
for _, test := range []struct {
27+
name string
28+
fn func() *cli.Command
29+
}{
30+
{
31+
name: "generate",
32+
fn: newCmdGenerate,
33+
},
34+
{
35+
name: "init",
36+
fn: newCmdInit,
37+
},
38+
{
39+
name: "tag-and-release",
40+
fn: newCmdTagAndRelease,
41+
},
42+
} {
43+
t.Run(test.name, func(t *testing.T) {
44+
testActionConfig(t, test.fn())
45+
})
46+
}
3847
}
3948

4049
// testActionConfig tests the execution flow for each Command.Action. The

internal/librarian/command_test.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,6 @@ import (
3434
"github.com/googleapis/librarian/internal/gitrepo"
3535
)
3636

37-
func TestCommandUsage(t *testing.T) {
38-
for _, c := range CmdLibrarian.Commands {
39-
t.Run(c.Name(), func(t *testing.T) {
40-
parts := strings.Fields(c.UsageLine)
41-
// The first word should always be "librarian".
42-
if parts[0] != "librarian" {
43-
t.Errorf("invalid usage text: %q (the first word should be `librarian`)", c.UsageLine)
44-
}
45-
if !strings.Contains(c.UsageLine, c.Name()) {
46-
t.Errorf("invalid usage text: %q (should contain command name %q)", c.UsageLine, c.Name())
47-
}
48-
})
49-
}
50-
}
51-
5237
func TestFindLibraryByID(t *testing.T) {
5338
lib1 := &config.LibraryState{ID: "lib1"}
5439
lib2 := &config.LibraryState{ID: "lib2"}

internal/librarian/generate.go

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

25-
"github.com/googleapis/librarian/internal/cli"
2625
"github.com/googleapis/librarian/internal/config"
2726
"github.com/googleapis/librarian/internal/docker"
2827
"github.com/googleapis/librarian/internal/gitrepo"
@@ -32,92 +31,6 @@ const (
3231
generateCmdName = "generate"
3332
)
3433

35-
var cmdGenerate = &cli.Command{
36-
Short: "generate onboards and generates client library code",
37-
UsageLine: "librarian generate [flags]",
38-
Long: `The generate command is the primary tool for all code generation
39-
tasks. It handles both the initial setup of a new library (onboarding) and the
40-
regeneration of existing ones. Librarian works by delegating language-specific
41-
tasks to a container, which is configured in the .librarian/state.yaml file.
42-
Librarian is environment aware and will check if the current directory is the
43-
root of a librarian repository. If you are not executing in such a directory the
44-
'--repo' flag must be provided.
45-
46-
# Onboarding a new library
47-
48-
To configure and generate a new library for the first time, you must specify the
49-
API to be generated and the library it will belong to. Librarian will invoke the
50-
'configure' command in the language container to set up the repository, add the
51-
new library's configuration to the '.librarian/state.yaml' file, and then
52-
proceed with generation.
53-
54-
Example:
55-
librarian generate --library=secretmanager --api=google/cloud/secretmanager/v1
56-
57-
# Regenerating existing libraries
58-
59-
You can regenerate a single, existing library by specifying either the library
60-
ID or the API path. If no specific library or API is provided, Librarian will
61-
regenerate all libraries listed in '.librarian/state.yaml'. If '--library' or
62-
'--api' is specified the whole library will be regenerated.
63-
64-
Examples:
65-
# Regenerate a single library by its ID
66-
librarian generate --library=secretmanager
67-
68-
# Regenerate a single library by its API path
69-
librarian generate --api=google/cloud/secretmanager/v1
70-
71-
# Regenerate all libraries in the repository
72-
librarian generate
73-
74-
# Workflow and Options:
75-
76-
The generation process involves delegating to the language container's
77-
'generate' command. After the code is generated, the tool cleans the destination
78-
directories and copies the new files into place, according to the configuration
79-
in '.librarian/state.yaml'.
80-
81-
- If the '--build' flag is specified, the 'build' command is also executed in
82-
the container to compile and validate the generated code.
83-
- If the '--push' flag is provided, the changes are committed to a new branch,
84-
and a pull request is created on GitHub. Otherwise, the changes are left in
85-
your local working directory for inspection.
86-
87-
Example with build and push:
88-
SDK_LIBRARIAN_GITHUB_TOKEN=xxx librarian generate --push --build`,
89-
Action: func(ctx context.Context, cmd *cli.Command) error {
90-
if err := cmd.Config.SetDefaults(); err != nil {
91-
return fmt.Errorf("failed to initialize config: %w", err)
92-
}
93-
if _, err := cmd.Config.IsValid(); err != nil {
94-
return fmt.Errorf("failed to validate config: %s", err)
95-
}
96-
runner, err := newGenerateRunner(cmd.Config)
97-
if err != nil {
98-
return err
99-
}
100-
return runner.run(ctx)
101-
},
102-
}
103-
104-
func init() {
105-
cmdGenerate.Init()
106-
fs := cmdGenerate.Flags
107-
cfg := cmdGenerate.Config
108-
109-
addFlagAPI(fs, cfg)
110-
addFlagAPISource(fs, cfg)
111-
addFlagBuild(fs, cfg)
112-
addFlagHostMount(fs, cfg)
113-
addFlagImage(fs, cfg)
114-
addFlagLibrary(fs, cfg)
115-
addFlagRepo(fs, cfg)
116-
addFlagBranch(fs, cfg)
117-
addFlagWorkRoot(fs, cfg)
118-
addFlagPush(fs, cfg)
119-
}
120-
12134
type generateRunner struct {
12235
api string
12336
apiSource string

internal/librarian/help.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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+
// https://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 librarian
16+
17+
const (
18+
librarianLongHelp = "Librarian manages client libraries for Google APIs."
19+
20+
versionLongHelp = "Version prints version information for the librarian binary."
21+
22+
releaseLongHelp = "Manages releases of libraries."
23+
24+
generateLongHelp = `The generate command is the primary tool for all code generation
25+
tasks. It handles both the initial setup of a new library (onboarding) and the
26+
regeneration of existing ones. Librarian works by delegating language-specific
27+
tasks to a container, which is configured in the .librarian/state.yaml file.
28+
Librarian is environment aware and will check if the current directory is the
29+
root of a librarian repository. If you are not executing in such a directory the
30+
'--repo' flag must be provided.
31+
32+
# Onboarding a new library
33+
34+
To configure and generate a new library for the first time, you must specify the
35+
API to be generated and the library it will belong to. Librarian will invoke the
36+
'configure' command in the language container to set up the repository, add the
37+
new library's configuration to the '.librarian/state.yaml' file, and then
38+
proceed with generation.
39+
40+
Example:
41+
librarian generate --library=secretmanager --api=google/cloud/secretmanager/v1
42+
43+
# Regenerating existing libraries
44+
45+
You can regenerate a single, existing library by specifying either the library
46+
ID or the API path. If no specific library or API is provided, Librarian will
47+
regenerate all libraries listed in '.librarian/state.yaml'. If '--library' or
48+
'--api' is specified the whole library will be regenerated.
49+
50+
Examples:
51+
# Regenerate a single library by its ID
52+
librarian generate --library=secretmanager
53+
54+
# Regenerate a single library by its API path
55+
librarian generate --api=google/cloud/secretmanager/v1
56+
57+
# Regenerate all libraries in the repository
58+
librarian generate
59+
60+
# Workflow and Options:
61+
62+
The generation process involves delegating to the language container's
63+
'generate' command. After the code is generated, the tool cleans the destination
64+
directories and copies the new files into place, according to the configuration
65+
in '.librarian/state.yaml'.
66+
67+
- If the '--build' flag is specified, the 'build' command is also executed in
68+
the container to compile and validate the generated code.
69+
- If the '--push' flag is provided, the changes are committed to a new branch,
70+
and a pull request is created on GitHub. Otherwise, the changes are left in
71+
your local working directory for inspection.
72+
73+
Example with build and push:
74+
SDK_LIBRARIAN_GITHUB_TOKEN=xxx librarian generate --push --build`
75+
76+
releaseInitLongHelp = `The 'release init' command is the primary entry point for initiating
77+
a new release. It automates the creation of a release pull request by parsing
78+
conventional commits, determining the next semantic version for each library,
79+
and generating a changelog. Librarian is environment aware and will check if the
80+
current directory is the root of a librarian repository. If you are not
81+
executing in such a directory the '--repo' flag must be provided.
82+
83+
This command scans the git history since the last release, identifies changes
84+
(feat, fix, BREAKING CHANGE), and calculates the appropriate version bump
85+
according to semver rules. It then delegates all language-specific file
86+
modifications, such as updating a CHANGELOG.md or bumping the version in a pom.xml,
87+
to the configured language-specific container.
88+
89+
By default, 'release init' leaves the changes in your local working directory
90+
for inspection. Use the '--push' flag to automatically commit the changes to
91+
a new branch and create a pull request on GitHub. The '--commit' flag may be
92+
used to create a local commit without creating a pull request; this flag is
93+
ignored if '--push' is also specified.
94+
95+
Examples:
96+
# Create a release PR for all libraries with pending changes.
97+
librarian release init --push
98+
99+
# Create a release PR for a single library.
100+
librarian release init --library=secretmanager --push
101+
102+
# Manually specify a version for a single library, overriding the calculation.
103+
librarian release init --library=secretmanager --library-version=2.0.0 --push`
104+
105+
tagAndReleaseLongHelp = `The 'tag-and-release' command is the final step in the release
106+
process. It is designed to be run after a release pull request, created by
107+
'release init', has been merged.
108+
109+
This command's primary responsibilities are to:
110+
111+
- Create a Git tag for each library version included in the merged pull request.
112+
- Create a corresponding GitHub Release for each tag, using the release notes
113+
from the pull request body.
114+
- Update the pull request's label from 'release:pending' to 'release:done' to
115+
mark the process as complete.
116+
117+
You can target a specific merged pull request using the '--pr' flag. If no pull
118+
request is specified, the command will automatically search for and process all
119+
merged pull requests with the 'release:pending' label from the last 30 days.
120+
121+
Examples:
122+
# Tag and create a GitHub release for a specific merged PR.
123+
librarian release tag-and-release --repo=https://github.com/googleapis/google-cloud-go --pr=https://github.com/googleapis/google-cloud-go/pull/123
124+
125+
# Find and process all pending merged release PRs in a repository.
126+
librarian release tag-and-release --repo=https://github.com/googleapis/google-cloud-go`
127+
)

0 commit comments

Comments
 (0)