Skip to content

Commit b29d5cb

Browse files
authored
refactor(internal/librarian): introduce SourceConfig for structured source root handling (#4255)
Introduce the `SourceConfig` type in the `internal/sidekick/source` package to replace raw map-based source root tracking. This commit adds: - `SourceConfig` struct for structured source root configuration. - `Resolve` method to handle absolute path resolution across active roots. - Unit tests for new methods.
1 parent 9091fd0 commit b29d5cb

2 files changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2026 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+
"fmt"
19+
"os"
20+
"path/filepath"
21+
)
22+
23+
// Sources contains the directory paths for source repositories used by
24+
// sidekick.
25+
type Sources struct {
26+
Conformance string
27+
Discovery string
28+
Googleapis string
29+
ProtobufSrc string
30+
Showcase string
31+
}
32+
33+
// SourceConfig holds the configuration for source roots and path resolution.
34+
type SourceConfig struct {
35+
Sources Sources
36+
ActiveRoots []string
37+
IncludeList []string
38+
ExcludeList []string
39+
}
40+
41+
// Root returns the directory path for the given root name.
42+
func (c SourceConfig) Root(name string) (string, error) {
43+
switch name {
44+
case "googleapis":
45+
return c.Sources.Googleapis, nil
46+
case "discovery":
47+
return c.Sources.Discovery, nil
48+
case "showcase":
49+
return c.Sources.Showcase, nil
50+
case "protobuf", "protobuf-src":
51+
return c.Sources.ProtobufSrc, nil
52+
case "conformance":
53+
return c.Sources.Conformance, nil
54+
default:
55+
return "", fmt.Errorf("unknown source name: %s", name)
56+
}
57+
}
58+
59+
// Resolve returns an absolute path for the given relative path if it is found
60+
// within the active source roots. Otherwise, it returns the original path.
61+
func (c SourceConfig) Resolve(relPath string) (string, error) {
62+
for _, root := range c.ActiveRoots {
63+
rootPath, err := c.Root(root)
64+
if err != nil {
65+
return "", err
66+
}
67+
if rootPath == "" {
68+
continue
69+
}
70+
fullName := filepath.Join(rootPath, relPath)
71+
if stat, err := os.Stat(fullName); err == nil && !stat.IsDir() {
72+
return fullName, nil
73+
}
74+
}
75+
return relPath, nil
76+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2026 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+
"os"
19+
"path/filepath"
20+
"testing"
21+
22+
"github.com/google/go-cmp/cmp"
23+
)
24+
25+
func TestRoot(t *testing.T) {
26+
cfg := SourceConfig{
27+
Sources: Sources{
28+
Googleapis: "googleapis-path",
29+
Discovery: "discovery-path",
30+
},
31+
}
32+
for _, test := range []struct {
33+
name string
34+
root string
35+
want string
36+
wantErr bool
37+
}{
38+
{
39+
name: "googleapis",
40+
root: "googleapis",
41+
want: "googleapis-path",
42+
},
43+
{
44+
name: "discovery",
45+
root: "discovery",
46+
want: "discovery-path",
47+
},
48+
{
49+
name: "unknown",
50+
root: "unknown",
51+
wantErr: true,
52+
},
53+
} {
54+
t.Run(test.name, func(t *testing.T) {
55+
got, err := cfg.Root(test.root)
56+
if (err != nil) != test.wantErr {
57+
t.Fatalf("Root(%q) error = %v, wantErr %v", test.root, err, test.wantErr)
58+
}
59+
if diff := cmp.Diff(test.want, got); diff != "" {
60+
t.Errorf("Root(%q) mismatch (-want +got):\n%s", test.root, diff)
61+
}
62+
})
63+
}
64+
}
65+
66+
func TestResolve(t *testing.T) {
67+
tempDir := t.TempDir()
68+
googleapis := filepath.Join(tempDir, "googleapis")
69+
if err := os.Mkdir(googleapis, 0755); err != nil {
70+
t.Fatal(err)
71+
}
72+
73+
specPath := "google/cloud/secretmanager/v1/secretmanager.yaml"
74+
fullPath := filepath.Join(googleapis, specPath)
75+
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
76+
t.Fatal(err)
77+
}
78+
if err := os.WriteFile(fullPath, []byte("test"), 0644); err != nil {
79+
t.Fatal(err)
80+
}
81+
82+
cfg := SourceConfig{
83+
Sources: Sources{
84+
Googleapis: googleapis,
85+
},
86+
ActiveRoots: []string{"googleapis"},
87+
}
88+
89+
for _, test := range []struct {
90+
name string
91+
relPath string
92+
want string
93+
wantErr bool
94+
}{
95+
{
96+
name: "found",
97+
relPath: specPath,
98+
want: fullPath,
99+
},
100+
{
101+
name: "not found",
102+
relPath: "not/found",
103+
want: "not/found",
104+
},
105+
{
106+
name: "unknown root",
107+
relPath: specPath,
108+
wantErr: true,
109+
},
110+
} {
111+
t.Run(test.name, func(t *testing.T) {
112+
if test.name == "unknown root" {
113+
cfg.ActiveRoots = []string{"unknown"}
114+
}
115+
got, err := cfg.Resolve(test.relPath)
116+
if (err != nil) != test.wantErr {
117+
t.Fatalf("Resolve(%q) error = %v, wantErr %v", test.relPath, err, test.wantErr)
118+
}
119+
if diff := cmp.Diff(test.want, got); diff != "" {
120+
t.Errorf("Resolve(%q) mismatch (-want +got):\n%s", test.relPath, diff)
121+
}
122+
})
123+
}
124+
}

0 commit comments

Comments
 (0)