Skip to content

Commit cdd77aa

Browse files
authored
feat(internal/librarian): sync new libraries to legacy state.yaml (#5030)
The librarian add command is modified to automatically update the legacy `.librarian/state.yaml` when a new library is added to `librarian.yaml`. This ensures that tools still relying on the legacy manifest remain in sync during the migration period. Only Go is enabled. Fixes #5011
1 parent 6a37505 commit cdd77aa

2 files changed

Lines changed: 242 additions & 0 deletions

File tree

internal/librarian/add.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"path/filepath"
2122
"slices"
2223
"sort"
2324
"strconv"
2425
"strings"
2526
"time"
2627

2728
"github.com/googleapis/librarian/internal/config"
29+
"github.com/googleapis/librarian/internal/legacylibrarian/legacyconfig"
2830
"github.com/googleapis/librarian/internal/librarian/dart"
2931
"github.com/googleapis/librarian/internal/librarian/golang"
3032
"github.com/googleapis/librarian/internal/librarian/python"
@@ -70,6 +72,13 @@ func runAdd(ctx context.Context, cfg *config.Config, apis ...string) error {
7072
if err != nil {
7173
return err
7274
}
75+
if cfg.Language == config.LanguageGo {
76+
// TODO(https://github.com/googleapis/librarian/issues/5029): Remove this function after
77+
// fully migrating off legacylibrarian.
78+
if err := syncToStateYAML(".", cfg); err != nil {
79+
return err
80+
}
81+
}
7382
return RunTidyOnConfig(ctx, ".", cfg)
7483
}
7584

@@ -177,3 +186,31 @@ func addLibrary(cfg *config.Config, apis ...string) (string, *config.Config, err
177186
})
178187
return name, cfg, nil
179188
}
189+
190+
// syncToStateYAML updates the .librarian/state.yaml with any new libraries.
191+
func syncToStateYAML(repoDir string, cfg *config.Config) error {
192+
stateFile := filepath.Join(repoDir, legacyconfig.LibrarianDir, legacyconfig.LibrarianStateFile)
193+
state, err := yaml.Read[legacyconfig.LibrarianState](stateFile)
194+
if err != nil {
195+
return err
196+
}
197+
for _, lib := range cfg.Libraries {
198+
if state.LibraryByID(lib.Name) != nil {
199+
continue
200+
}
201+
state.Libraries = append(state.Libraries, createLegacyLibrary(lib))
202+
}
203+
sort.Slice(state.Libraries, func(i, j int) bool {
204+
return state.Libraries[i].ID < state.Libraries[j].ID
205+
})
206+
return yaml.Write(stateFile, state)
207+
}
208+
209+
func createLegacyLibrary(lib *config.Library) *legacyconfig.LibraryState {
210+
return &legacyconfig.LibraryState{
211+
ID: lib.Name,
212+
Version: lib.Version,
213+
SourceRoots: []string{lib.Name},
214+
TagFormat: "{id}/v{version}",
215+
}
216+
}

internal/librarian/add_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package librarian
1616

1717
import (
1818
"errors"
19+
"io/fs"
20+
"os"
1921
"path/filepath"
2022
"sort"
2123
"strconv"
@@ -24,6 +26,7 @@ import (
2426

2527
"github.com/google/go-cmp/cmp"
2628
"github.com/googleapis/librarian/internal/config"
29+
"github.com/googleapis/librarian/internal/legacylibrarian/legacyconfig"
2730
"github.com/googleapis/librarian/internal/sample"
2831
"github.com/googleapis/librarian/internal/yaml"
2932
)
@@ -426,3 +429,205 @@ func TestDeriveLibraryName(t *testing.T) {
426429
})
427430
}
428431
}
432+
433+
func TestSyncToStateYAML(t *testing.T) {
434+
t.Parallel()
435+
for _, test := range []struct {
436+
name string
437+
initialState *legacyconfig.LibrarianState
438+
cfg *config.Config
439+
wantState *legacyconfig.LibrarianState
440+
}{
441+
{
442+
name: "sync new library",
443+
initialState: &legacyconfig.LibrarianState{
444+
Image: "gcr.io/my-image:latest",
445+
Libraries: []*legacyconfig.LibraryState{
446+
{
447+
ID: "existing",
448+
Version: "1.2.3",
449+
PreserveRegex: []string{},
450+
RemoveRegex: []string{},
451+
APIs: []*legacyconfig.API{},
452+
SourceRoots: []string{"existing"},
453+
},
454+
},
455+
},
456+
cfg: &config.Config{
457+
Language: config.LanguageRust,
458+
Libraries: []*config.Library{
459+
{
460+
Name: "existing",
461+
Version: "1.2.3",
462+
APIs: []*config.API{
463+
{Path: "google/cloud/existing/v1"},
464+
},
465+
},
466+
{
467+
Name: "new",
468+
Version: "0.1.0",
469+
APIs: []*config.API{
470+
{Path: "google/cloud/new/v1"},
471+
},
472+
},
473+
},
474+
},
475+
wantState: &legacyconfig.LibrarianState{
476+
Image: "gcr.io/my-image:latest",
477+
Libraries: []*legacyconfig.LibraryState{
478+
{
479+
ID: "existing",
480+
Version: "1.2.3",
481+
PreserveRegex: []string{},
482+
RemoveRegex: []string{},
483+
APIs: []*legacyconfig.API{},
484+
SourceRoots: []string{"existing"},
485+
},
486+
{
487+
ID: "new",
488+
Version: "0.1.0",
489+
PreserveRegex: []string{},
490+
RemoveRegex: []string{},
491+
APIs: []*legacyconfig.API{},
492+
SourceRoots: []string{"new"},
493+
TagFormat: "{id}/v{version}",
494+
},
495+
},
496+
},
497+
},
498+
{
499+
name: "multiple new libraries",
500+
initialState: &legacyconfig.LibrarianState{
501+
Image: "gcr.io/my-image:latest",
502+
Libraries: []*legacyconfig.LibraryState{},
503+
},
504+
cfg: &config.Config{
505+
Libraries: []*config.Library{
506+
{Name: "lib-b", Version: "1.0.0"},
507+
{Name: "lib-a", Version: "2.0.0"},
508+
},
509+
},
510+
wantState: &legacyconfig.LibrarianState{
511+
Image: "gcr.io/my-image:latest",
512+
Libraries: []*legacyconfig.LibraryState{
513+
{
514+
ID: "lib-a",
515+
Version: "2.0.0",
516+
PreserveRegex: []string{},
517+
RemoveRegex: []string{},
518+
APIs: []*legacyconfig.API{},
519+
SourceRoots: []string{"lib-a"},
520+
TagFormat: "{id}/v{version}",
521+
},
522+
{
523+
ID: "lib-b",
524+
Version: "1.0.0",
525+
PreserveRegex: []string{},
526+
RemoveRegex: []string{},
527+
APIs: []*legacyconfig.API{},
528+
SourceRoots: []string{"lib-b"},
529+
TagFormat: "{id}/v{version}",
530+
},
531+
},
532+
},
533+
},
534+
{
535+
name: "no new libraries",
536+
initialState: &legacyconfig.LibrarianState{
537+
Image: "gcr.io/my-image:latest",
538+
Libraries: []*legacyconfig.LibraryState{
539+
{
540+
ID: "lib-a",
541+
Version: "2.0.0",
542+
PreserveRegex: []string{},
543+
RemoveRegex: []string{},
544+
APIs: []*legacyconfig.API{},
545+
SourceRoots: []string{"lib-a"},
546+
TagFormat: "{id}/v{version}",
547+
},
548+
{
549+
ID: "lib-b",
550+
Version: "1.0.0",
551+
PreserveRegex: []string{},
552+
RemoveRegex: []string{},
553+
APIs: []*legacyconfig.API{},
554+
SourceRoots: []string{"lib-b"},
555+
TagFormat: "{id}/v{version}",
556+
},
557+
},
558+
},
559+
cfg: &config.Config{
560+
Libraries: []*config.Library{
561+
{Name: "lib-b", Version: "1.0.0"},
562+
{Name: "lib-a", Version: "2.0.0"},
563+
},
564+
},
565+
wantState: &legacyconfig.LibrarianState{
566+
Image: "gcr.io/my-image:latest",
567+
Libraries: []*legacyconfig.LibraryState{
568+
{
569+
ID: "lib-a",
570+
Version: "2.0.0",
571+
PreserveRegex: []string{},
572+
RemoveRegex: []string{},
573+
APIs: []*legacyconfig.API{},
574+
SourceRoots: []string{"lib-a"},
575+
TagFormat: "{id}/v{version}",
576+
},
577+
{
578+
ID: "lib-b",
579+
Version: "1.0.0",
580+
PreserveRegex: []string{},
581+
RemoveRegex: []string{},
582+
APIs: []*legacyconfig.API{},
583+
SourceRoots: []string{"lib-b"},
584+
TagFormat: "{id}/v{version}",
585+
},
586+
},
587+
},
588+
},
589+
} {
590+
t.Run(test.name, func(t *testing.T) {
591+
t.Parallel()
592+
tmpDir := t.TempDir()
593+
stateFile := filepath.Join(tmpDir, legacyconfig.LibrarianDir, legacyconfig.LibrarianStateFile)
594+
if err := os.Mkdir(filepath.Join(tmpDir, legacyconfig.LibrarianDir), 0755); err != nil {
595+
t.Fatal(err)
596+
}
597+
if err := yaml.Write(stateFile, test.initialState); err != nil {
598+
t.Fatal(err)
599+
}
600+
if err := syncToStateYAML(tmpDir, test.cfg); err != nil {
601+
t.Fatal(err)
602+
}
603+
gotState, err := yaml.Read[legacyconfig.LibrarianState](stateFile)
604+
if err != nil {
605+
t.Fatal(err)
606+
}
607+
if diff := cmp.Diff(test.wantState, gotState); diff != "" {
608+
t.Errorf("mismatch (-want +got):\n%s", diff)
609+
}
610+
})
611+
}
612+
}
613+
614+
func TestSyncToStateYAML_Error(t *testing.T) {
615+
for _, test := range []struct {
616+
name string
617+
initialState *legacyconfig.LibrarianState
618+
cfg *config.Config
619+
wantError error
620+
}{
621+
{
622+
name: "state.yaml does not exist",
623+
wantError: fs.ErrNotExist,
624+
},
625+
} {
626+
t.Run(test.name, func(t *testing.T) {
627+
err := syncToStateYAML(t.TempDir(), test.cfg)
628+
if !errors.Is(err, test.wantError) {
629+
t.Errorf("syncToStateYAML(%s): got error %v, want %v", test.name, err, test.wantError)
630+
}
631+
})
632+
}
633+
}

0 commit comments

Comments
 (0)