Skip to content

Commit 502b063

Browse files
authored
feat(internal/sideflip/config): add initial config (#2965)
Add structs for librarian.yaml with read/write support, including support for migrating sidekick.toml data to new Rust types. For #2966
1 parent 12f4e94 commit 502b063

File tree

5 files changed

+516
-0
lines changed

5 files changed

+516
-0
lines changed

internal/sideflip/config/config.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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 config
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"gopkg.in/yaml.v3"
22+
)
23+
24+
// Config represents the complete librarian.yaml configuration file.
25+
type Config struct {
26+
// Version is the version of librarian that created this config.
27+
Version string `yaml:"version"`
28+
29+
// Language is the primary language for this repository (go, python, rust).
30+
Language string `yaml:"language"`
31+
32+
// Repo is the repository name (e.g., "googleapis/google-cloud-python").
33+
Repo string `yaml:"repo,omitempty"`
34+
35+
// Sources contains references to external source repositories.
36+
Sources *Sources `yaml:"sources,omitempty"`
37+
38+
// Default contains default generation settings.
39+
Default *Default `yaml:"default"`
40+
41+
// Libraries contains configuration overrides for libraries that need special handling.
42+
// Only include libraries that differ from defaults.
43+
// Versions are looked up from the Versions map below.
44+
Libraries []*Library `yaml:"libraries,omitempty"`
45+
46+
// Versions contains version numbers for all libraries.
47+
// This is the source of truth for release versions.
48+
// Key is library name, value is version string.
49+
Versions map[string]string `yaml:"versions,omitempty"`
50+
}
51+
52+
// Sources contains references to external source repositories.
53+
// Each entry maps a source name to its configuration.
54+
type Sources struct {
55+
// Discovery is the discovery-artifact-manager repository configuration.
56+
Discovery *Source `yaml:"discovery,omitempty"`
57+
58+
// Googleapis is the googleapis repository configuration.
59+
Googleapis *Source `yaml:"googleapis,omitempty"`
60+
}
61+
62+
// Source represents a single source repository configuration.
63+
type Source struct {
64+
// Commit is the git commit hash or tag to use.
65+
Commit string `yaml:"commit"`
66+
}
67+
68+
// Default contains default generation settings.
69+
type Default struct {
70+
// Output is the directory where generated code is written (relative to repository root).
71+
Output string `yaml:"output,omitempty"`
72+
73+
// Generate contains default generation configuration.
74+
Generate *DefaultGenerate `yaml:"generate,omitempty"`
75+
76+
// Release contains default release configuration.
77+
Release *DefaultRelease `yaml:"release,omitempty"`
78+
79+
// Rust contains Rust-specific default configuration.
80+
Rust *RustDefault `yaml:"rust,omitempty"`
81+
}
82+
83+
// DefaultGenerate contains default generation configuration.
84+
type DefaultGenerate struct {
85+
// Auto generates all client libraries with default configurations
86+
// for the language, unless otherwise specified.
87+
Auto bool `yaml:"auto,omitempty"`
88+
89+
// OneLibraryPer specifies packaging strategy: "api" or "channel".
90+
// - "api": Bundle all versions of a service into one library (Python, Go default)
91+
// - "channel": Create separate library per version (Rust, Dart default)
92+
OneLibraryPer string `yaml:"one_library_per,omitempty"`
93+
94+
// Transport is the default transport protocol (e.g., "grpc+rest", "grpc").
95+
Transport string `yaml:"transport,omitempty"`
96+
97+
// ReleaseLevel is the default release level ("stable" or "preview").
98+
ReleaseLevel string `yaml:"release_level,omitempty"`
99+
}
100+
101+
// DefaultRelease contains release configuration.
102+
type DefaultRelease struct {
103+
// TagFormat is the template for git tags (e.g., '{name}/v{version}').
104+
// Supported placeholders: {name}, {version}
105+
TagFormat string `yaml:"tag_format,omitempty"`
106+
107+
// Remote is the git remote name (e.g., "upstream", "origin").
108+
Remote string `yaml:"remote,omitempty"`
109+
110+
// Branch is the default branch for releases (e.g., "main", "master").
111+
Branch string `yaml:"branch,omitempty"`
112+
}
113+
114+
// Library represents a single library configuration entry.
115+
type Library struct {
116+
// APIServiceConfigs maps API paths to their service config file paths (runtime only, not serialized).
117+
// For single-API libraries: map[API]serviceConfigPath
118+
// For multi-API libraries: map[APIs[0]]path1, map[APIs[1]]path2, etc.
119+
APIServiceConfigs map[string]string `yaml:"-"`
120+
121+
// Channel specifies which googleapis Channel to generate from (for generated libraries).
122+
// Can be a string (protobuf Channel path) or an APIObject (for discovery APIs).
123+
// If both Channel and APIs are empty, this is a handwritten library.
124+
Channel string `yaml:"channel,omitempty"`
125+
126+
// Channels specifies multiple API versions to bundle into one library (for multi-version libraries).
127+
// Alternative to API field for libraries that bundle multiple versions.
128+
Channels []string `yaml:"channels,omitempty"`
129+
130+
// CopyrightYear is the copyright year for the library.
131+
CopyrightYear string `yaml:"copyright_year,omitempty"`
132+
133+
// Generate contains per-library generate configuration.
134+
Generate *LibraryGenerate `yaml:"generate,omitempty"`
135+
136+
// Keep lists files/directories to preserve during regeneration.
137+
Keep []string `yaml:"keep,omitempty"`
138+
139+
// Name is the library name (e.g., "secretmanager", "storage").
140+
Name string `yaml:"name,omitempty"`
141+
142+
// Output specifies the filesystem location (overrides computed location from defaults.output).
143+
// For generated libraries: overrides where code is generated to.
144+
// For handwritten libraries: specifies the source directory.
145+
Output string `yaml:"output,omitempty"`
146+
147+
// Publish contains per-library publish configuration.
148+
Publish *LibraryPublish `yaml:"publish,omitempty"`
149+
150+
// Release contains per-library release configuration.
151+
Release *LibraryRelease `yaml:"release,omitempty"`
152+
153+
// ReleaseLevel overrides the default release level.
154+
ReleaseLevel string `yaml:"release_level,omitempty"`
155+
156+
// Rust contains Rust-specific library configuration.
157+
Rust *RustCrate `yaml:"rust,omitempty"`
158+
159+
// Transport overrides the default transport.
160+
Transport string `yaml:"transport,omitempty"`
161+
162+
// Version is the library version.
163+
Version string `yaml:"version,omitempty"`
164+
}
165+
166+
// LibraryGenerate contains per-library generate configuration.
167+
type LibraryGenerate struct {
168+
// Disabled prevents library generation.
169+
Disabled bool `yaml:"disabled,omitempty"`
170+
}
171+
172+
// LibraryRelease contains per-library release configuration.
173+
type LibraryRelease struct {
174+
// Disabled prevents library release.
175+
Disabled bool `yaml:"disabled,omitempty"`
176+
}
177+
178+
// LibraryPublish contains per-library publish configuration.
179+
type LibraryPublish struct {
180+
// Disabled prevents library from being published to package registries.
181+
Disabled bool `yaml:"disabled,omitempty"`
182+
}
183+
184+
// Read reads the configuration from a file.
185+
func Read(path string) (*Config, error) {
186+
data, err := os.ReadFile(path)
187+
if err != nil {
188+
return nil, fmt.Errorf("failed to read config file: %w", err)
189+
}
190+
191+
var c Config
192+
if err := yaml.Unmarshal(data, &c); err != nil {
193+
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
194+
}
195+
return &c, nil
196+
}
197+
198+
// Write writes the configuration to a file.
199+
func (c *Config) Write(path string) error {
200+
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
201+
if err != nil {
202+
return fmt.Errorf("failed to open config file: %w", err)
203+
}
204+
defer f.Close()
205+
206+
enc := yaml.NewEncoder(f)
207+
enc.SetIndent(2)
208+
defer enc.Close()
209+
210+
if err := enc.Encode(c); err != nil {
211+
return fmt.Errorf("failed to marshal config: %w", err)
212+
}
213+
214+
return nil
215+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 config
16+
17+
import (
18+
"bytes"
19+
"testing"
20+
21+
"github.com/google/go-cmp/cmp"
22+
"gopkg.in/yaml.v3"
23+
)
24+
25+
func TestReadWrite(t *testing.T) {
26+
cfg, err := Read("testdata/rust/librarian.yaml")
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
var got bytes.Buffer
31+
enc := yaml.NewEncoder(&got)
32+
enc.SetIndent(2)
33+
if err := enc.Encode(cfg); err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
wantCfg, err := Read("testdata/rust/librarian.yaml")
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
var gotCfg Config
42+
if err := yaml.Unmarshal(got.Bytes(), &gotCfg); err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
if diff := cmp.Diff(wantCfg, &gotCfg); diff != "" {
47+
t.Errorf("mismatch (-want +got):\n%s", diff)
48+
}
49+
}

0 commit comments

Comments
 (0)