Skip to content

Commit 9bb33ea

Browse files
authored
impl(sidekick): scaffold for rust-bump-version (#2104)
1 parent 4d2e4de commit 9bb33ea

8 files changed

Lines changed: 374 additions & 7 deletions

File tree

internal/sidekick/internal/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type Config struct {
5858
Source map[string]string `toml:"source,omitempty"`
5959
Codec map[string]string `toml:"codec,omitempty"`
6060
CommentOverrides []DocumentationOverride `toml:"documentation-overrides,omitempty"`
61+
Release *Release `toml:"release,omitempty"`
6162

6263
// Gcloud is used to pass data into gcloud.Generate. It does not use the
6364
// normal .sidekick.toml file, but instead reads a gcloud.yaml file.
@@ -133,6 +134,8 @@ func mergeConfigs(rootConfig, local *Config) *Config {
133134
Source: map[string]string{},
134135
Codec: map[string]string{},
135136
CommentOverrides: local.CommentOverrides,
137+
// Release does not accept local overrides
138+
Release: rootConfig.Release,
136139
}
137140
for k, v := range rootConfig.Codec {
138141
merged.Codec[k] = v
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 config
16+
17+
// Release holds the configuration parameter for any `${lang}-release` subcommand.
18+
type Release struct {
19+
// Remote sets the name of the source-of-truth remote for releases, typically `upstream`.
20+
Remote string
21+
22+
// ReleaseBranch sets the name of the release branch, typically `main`
23+
Branch string
24+
25+
// Tools defines the list of tools to install, indexed by installer.
26+
Tools map[string][]Tool
27+
28+
// Preinstalled tools defines the list of tools that must be pre-installed.
29+
//
30+
// This is indexed by the well-known name of the tool vs. its path, e.g.
31+
// [preinstalled]
32+
// cargo = /usr/bin/cargo
33+
Preinstalled map[string]string `toml:"pre-installed"`
34+
35+
// IgnoredChanges defines globs that are ignored in change analysis.
36+
IgnoredChanges []string `toml:"ignored-changes,omitempty"`
37+
}
38+
39+
// Tool defines the configuration required to install helper tools.
40+
type Tool struct {
41+
Name string `toml:"name"`
42+
Version string `toml:"version"`
43+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 config
16+
17+
import (
18+
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
21+
toml "github.com/pelletier/go-toml/v2"
22+
)
23+
24+
func TestReleaseTomlSpec(t *testing.T) {
25+
// Verify the toml annotations for `Release` work as expected.
26+
input := `
27+
[release]
28+
remote = 'upstream'
29+
branch = 'main'
30+
ignored-changes = [
31+
".sidekick.toml",
32+
".repo-metadata.json",
33+
"**/examples/**",
34+
]
35+
36+
[[release.tools.cargo]]
37+
name = 'release-plz'
38+
version = '1.2.3'
39+
40+
[[release.tools.cargo]]
41+
name = 'workspaces'
42+
version = '2.3.4'
43+
44+
[release.pre-installed]
45+
cargo = '/bin/true'
46+
git = '/bin/false'
47+
`
48+
49+
got := Config{}
50+
if err := toml.Unmarshal([]byte(input), &got); err != nil {
51+
t.Fatal(err)
52+
}
53+
54+
want := Config{
55+
Release: &Release{
56+
Remote: "upstream",
57+
Branch: "main",
58+
Tools: map[string][]Tool{
59+
"cargo": {
60+
{Name: "release-plz", Version: "1.2.3"},
61+
{Name: "workspaces", Version: "2.3.4"},
62+
},
63+
},
64+
Preinstalled: map[string]string{
65+
"cargo": "/bin/true",
66+
"git": "/bin/false",
67+
},
68+
IgnoredChanges: []string{
69+
".sidekick.toml",
70+
".repo-metadata.json",
71+
"**/examples/**",
72+
},
73+
},
74+
}
75+
if diff := cmp.Diff(want, got); len(diff) != 0 {
76+
t.Errorf("mismatch (-want, +got):\n%s", diff)
77+
}
78+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 rustrelease
16+
17+
import (
18+
"github.com/googleapis/librarian/internal/sidekick/internal/config"
19+
"github.com/googleapis/librarian/internal/sidekick/internal/external"
20+
)
21+
22+
// PreFlight() verifies all the necessary tools are installed.
23+
func PreFlight(config *config.Release) error {
24+
if err := external.Run(gitExe(config), "--version"); err != nil {
25+
return err
26+
}
27+
if err := external.Run(cargoExe(config), "--version"); err != nil {
28+
return err
29+
}
30+
return nil
31+
}
32+
33+
func gitExe(config *config.Release) string {
34+
if exe, ok := config.Preinstalled["git"]; ok {
35+
return exe
36+
}
37+
return "git"
38+
}
39+
40+
func cargoExe(config *config.Release) string {
41+
if exe, ok := config.Preinstalled["cargo"]; ok {
42+
return exe
43+
}
44+
return "cargo"
45+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 rustrelease
16+
17+
import (
18+
"os/exec"
19+
"testing"
20+
21+
"github.com/googleapis/librarian/internal/sidekick/internal/config"
22+
)
23+
24+
func TestPreflightSuccess(t *testing.T) {
25+
const echo = "/bin/echo"
26+
requireCommand(t, echo)
27+
release := config.Release{
28+
Preinstalled: map[string]string{
29+
"git": echo,
30+
"cargo": echo,
31+
},
32+
}
33+
if err := PreFlight(&release); err != nil {
34+
t.Fatal(err)
35+
}
36+
}
37+
38+
func TestPreflightMissingGit(t *testing.T) {
39+
release := config.Release{
40+
Preinstalled: map[string]string{
41+
"git": "git-is-not-installed",
42+
},
43+
}
44+
if err := PreFlight(&release); err == nil {
45+
t.Fatal(err)
46+
}
47+
}
48+
49+
func TestPreflightMissingCargo(t *testing.T) {
50+
requireCommand(t, "git")
51+
release := config.Release{
52+
Preinstalled: map[string]string{
53+
"cargo": "cargo-is-not-installed",
54+
},
55+
}
56+
if err := PreFlight(&release); err == nil {
57+
t.Fatal(err)
58+
}
59+
}
60+
61+
func TestGitExe(t *testing.T) {
62+
release := config.Release{}
63+
if got := gitExe(&release); got != "git" {
64+
t.Errorf("mismatch in gitExe(), want=git, got=%s", got)
65+
}
66+
release = config.Release{
67+
Preinstalled: map[string]string{
68+
"git": "alternative",
69+
},
70+
}
71+
if got := gitExe(&release); got != "alternative" {
72+
t.Errorf("mismatch in gitExe(), want=alternative, got=%s", got)
73+
}
74+
}
75+
76+
func TestCargoExe(t *testing.T) {
77+
release := config.Release{}
78+
if got := cargoExe(&release); got != "cargo" {
79+
t.Errorf("mismatch in cargoExe(), want=cargo, got=%s", got)
80+
}
81+
release = config.Release{
82+
Preinstalled: map[string]string{
83+
"cargo": "alternative",
84+
},
85+
}
86+
if got := cargoExe(&release); got != "alternative" {
87+
t.Errorf("mismatch in cargoExe(), want=alternative, got=%s", got)
88+
}
89+
}
90+
91+
func requireCommand(t *testing.T, command string) {
92+
t.Helper()
93+
if _, err := exec.LookPath(command); err != nil {
94+
t.Skipf("skipping test because %s is not installed", command)
95+
}
96+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 sidekick
16+
17+
import (
18+
"testing"
19+
20+
"github.com/googleapis/librarian/internal/sidekick/internal/config"
21+
)
22+
23+
func TestPreflightSuccess(t *testing.T) {
24+
requireGit(t)
25+
config := config.Config{
26+
Release: &config.Release{
27+
Preinstalled: map[string]string{
28+
"git": "git",
29+
"cargo": "git",
30+
},
31+
},
32+
}
33+
cmdLine := CommandLine{}
34+
if err := rustBumpVersions(&config, &cmdLine); err != nil {
35+
t.Fatal(err)
36+
}
37+
}
38+
39+
func TestPreflightMissingCommand(t *testing.T) {
40+
requireGit(t)
41+
config := config.Config{
42+
Release: &config.Release{
43+
Preinstalled: map[string]string{
44+
"cargo": "not-a-valid-command-bad-bad",
45+
},
46+
},
47+
}
48+
cmdLine := CommandLine{}
49+
if err := rustBumpVersions(&config, &cmdLine); err == nil {
50+
t.Errorf("expected an error in rustBumpVersions() with a bad cargo command")
51+
}
52+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 sidekick
16+
17+
import (
18+
"github.com/googleapis/librarian/internal/sidekick/internal/config"
19+
rustrelease "github.com/googleapis/librarian/internal/sidekick/internal/rust_release"
20+
)
21+
22+
func init() {
23+
newCommand(
24+
"sidekick rust-bump-versions",
25+
"Increments the version numbers as needed.",
26+
`
27+
Finds all the changes since the last release and increments, if needed, the
28+
version numbers for all crates that have changed since then. The command changes
29+
both the '.sidekick.toml' and the 'Cargo.toml' files.
30+
31+
For crates where version number has already been increased, this command has no
32+
effect.
33+
`,
34+
cmdSidekick,
35+
rustBumpVersions,
36+
)
37+
}
38+
39+
// rustGenerate increments the version numbers as needed.
40+
func rustBumpVersions(rootConfig *config.Config, cmdLine *CommandLine) error {
41+
if err := rustrelease.PreFlight(rootConfig.Release); err != nil {
42+
return err
43+
}
44+
return nil
45+
}

0 commit comments

Comments
 (0)