@@ -20,6 +20,7 @@ import (
2020 "flag"
2121 "fmt"
2222 "log/slog"
23+ "maps"
2324 "os"
2425 "path/filepath"
2526 "reflect"
@@ -34,6 +35,7 @@ import (
3435
3536const (
3637 sidekickFile = ".sidekick.toml"
38+ cargoFile = "Cargo.toml"
3739 discoveryArchivePrefix = "https://github.com/googleapis/discovery-artifact-manager/archive/"
3840 googleapisArchivePrefix = "https://github.com/googleapis/googleapis/archive/"
3941 tarballSuffix = ".tar.gz"
@@ -42,7 +44,6 @@ const (
4244var (
4345 errRepoNotFound = errors .New ("-repo flag is required" )
4446 errSidekickNotFound = errors .New (".sidekick.toml not found" )
45- errSrcNotFound = errors .New ("src/generated directory not found" )
4647 errTidyFailed = errors .New ("librarian tidy failed" )
4748 errUnableToCalculateOutputPath = errors .New ("unable to calculate output path" )
4849)
@@ -95,31 +96,43 @@ func run(args []string) error {
9596 }
9697 repoPath := flagSet .Arg (0 )
9798
98- slog .Info ("Reading sidekick.toml..." , "path" , repoPath )
99-
99+ // Read root .sidekick.toml for defaults
100100 defaults , err := readRootSidekick (repoPath )
101101 if err != nil {
102102 return fmt .Errorf ("failed to read root .sidekick.toml: %w" , err )
103103 }
104104
105- // Find all .sidekick.toml files
106- sidekickFiles , err := findSidekickFiles (repoPath )
105+ // Find all .sidekick.toml files for GAPIC libraries.
106+ sidekickFiles , err := findSidekickFiles (filepath . Join ( repoPath , "src" , "generated" ) )
107107 if err != nil {
108108 return fmt .Errorf ("failed to find sidekick.toml files: %w" , err )
109109 }
110110
111- // Read all sidekick.toml files
112- libraries , err := readSidekickFiles (sidekickFiles , repoPath )
111+ libraries , err := buildGAPIC (sidekickFiles , repoPath )
113112 if err != nil {
114113 return fmt .Errorf ("failed to read sidekick.toml files: %w" , err )
115114 }
116115
117- cfg := buildConfig (libraries , defaults )
116+ cargoFiles , err := findCargos (filepath .Join (repoPath , "src" ))
117+ if err != nil {
118+ return fmt .Errorf ("failed to find Cargo.toml files: %w" , err )
119+ }
120+
121+ veneers , err := buildVeneer (cargoFiles )
122+ if err != nil {
123+ return fmt .Errorf ("failed to build veneers: %w" , err )
124+ }
125+
126+ allLibraries := make (map [string ]* config.Library , len (libraries )+ len (veneers ))
127+ maps .Copy (allLibraries , libraries )
128+ maps .Copy (allLibraries , veneers )
129+
130+ cfg := buildConfig (allLibraries , defaults )
118131
119132 if err := yaml .Write (* outputPath , cfg ); err != nil {
120133 return fmt .Errorf ("failed to write config: %w" , err )
121134 }
122- slog .Info ("Wrote config to output file" , "path" , outputPath )
135+ slog .Info ("Wrote config to output file" , "path" , * outputPath )
123136
124137 if err := librarian .RunTidy (); err != nil {
125138 slog .Error (errTidyFailed .Error (), "error" , err )
@@ -215,16 +228,14 @@ func parsePackageDependency(name, spec string) *config.RustPackageDependency {
215228 return dep
216229}
217230
218- // findSidekickFiles finds all .sidekick.toml files in the repository .
219- func findSidekickFiles (repoPath string ) ([]string , error ) {
231+ // findSidekickFiles finds all .sidekick.toml files within the given path .
232+ func findSidekickFiles (path string ) ([]string , error ) {
220233 var files []string
221-
222- generatedPath := filepath .Join (repoPath , "src" , "generated" )
223- err := filepath .Walk (generatedPath , func (path string , info os.FileInfo , err error ) error {
234+ err := filepath .WalkDir (path , func (path string , d os.DirEntry , err error ) error {
224235 if err != nil {
225- return errSrcNotFound
236+ return err
226237 }
227- if ! info .IsDir () && info .Name () == sidekickFile {
238+ if ! d .IsDir () && d .Name () == sidekickFile {
228239 files = append (files , path )
229240 }
230241 return nil
@@ -241,8 +252,7 @@ func findSidekickFiles(repoPath string) ([]string, error) {
241252 return files , nil
242253}
243254
244- // readSidekickFiles reads all sidekick.toml files and extracts library information.
245- func readSidekickFiles (files []string , repoPath string ) (map [string ]* config.Library , error ) {
255+ func buildGAPIC (files []string , repoPath string ) (map [string ]* config.Library , error ) {
246256 libraries := make (map [string ]* config.Library )
247257
248258 for _ , file := range files {
@@ -271,15 +281,9 @@ func readSidekickFiles(files []string, repoPath string) (map[string]*config.Libr
271281
272282 // Read Cargo.toml in the same directory to get the actual library name
273283 dir := filepath .Dir (file )
274- cargoPath := filepath .Join (dir , "Cargo.toml" )
275- cargoData , err := os .ReadFile (cargoPath )
284+ cargo , err := readTOML [CargoConfig ](filepath .Join (dir , cargoFile ))
276285 if err != nil {
277- return nil , fmt .Errorf ("failed to read %s: %w" , cargoPath , err )
278- }
279-
280- var cargo CargoConfig
281- if err := toml .Unmarshal (cargoData , & cargo ); err != nil {
282- return nil , fmt .Errorf ("failed to unmarshal %s: %w" , cargoPath , err )
286+ return nil , fmt .Errorf ("failed to read cargo: %w" , err )
283287 }
284288
285289 libraryName := cargo .Package .Name
@@ -424,6 +428,116 @@ func deriveLibraryName(apiPath string) string {
424428 return "google-cloud-" + strings .ReplaceAll (trimmedPath , "/" , "-" )
425429}
426430
431+ // findCargos returns all Cargo.toml files within the given path.
432+ //
433+ // A file is filtered if the file lives in a path that contains src/generated.
434+ func findCargos (path string ) ([]string , error ) {
435+ var files []string
436+ err := filepath .WalkDir (path , func (path string , d os.DirEntry , err error ) error {
437+ if err != nil {
438+ return err
439+ }
440+
441+ if d .IsDir () && strings .Contains (path , "src/generated" ) {
442+ return filepath .SkipDir
443+ }
444+
445+ if d .IsDir () || d .Name () != cargoFile {
446+ return nil
447+ }
448+
449+ files = append (files , path )
450+
451+ return nil
452+ })
453+ return files , err
454+ }
455+
456+ func buildVeneer (files []string ) (map [string ]* config.Library , error ) {
457+ veneers := make (map [string ]* config.Library )
458+ for _ , file := range files {
459+ cargo , err := readTOML [CargoConfig ](file )
460+ if err != nil {
461+ return nil , err
462+ }
463+ dir := filepath .Dir (file )
464+ rustModules , err := buildModules (dir )
465+ if err != nil {
466+ return nil , err
467+ }
468+ name := cargo .Package .Name
469+ veneers [name ] = & config.Library {
470+ Name : name ,
471+ Veneer : true ,
472+ Output : dir ,
473+ Version : cargo .Package .Version ,
474+ CopyrightYear : "2025" ,
475+ }
476+ if rustModules != nil {
477+ veneers [name ].Rust = & config.RustCrate {
478+ Modules : rustModules ,
479+ }
480+ }
481+ }
482+
483+ return veneers , nil
484+ }
485+
486+ func buildModules (path string ) ([]* config.RustModule , error ) {
487+ var modules []* config.RustModule
488+ err := filepath .WalkDir (path , func (path string , d os.DirEntry , err error ) error {
489+ if err != nil {
490+ return err
491+ }
492+
493+ if d .IsDir () || d .Name () != sidekickFile {
494+ return nil
495+ }
496+
497+ sidekick , err := readTOML [SidekickConfig ](path )
498+ if err != nil {
499+ return err
500+ }
501+
502+ includedIds , _ := sidekick .Source ["included-ids" ].(string )
503+ includeList , _ := sidekick .Source ["include-list" ].(string )
504+ skippedIds , _ := sidekick .Source ["skipped-ids" ].(string )
505+
506+ hasVeneer , _ := sidekick .Codec ["has-veneer" ].(string )
507+ includeGrpcOnlyMethods , _ := sidekick .Codec ["include-grpc-only-methods" ].(string )
508+ routingRequired , _ := sidekick .Codec ["routing-required" ].(string )
509+ modulePath , _ := sidekick .Codec ["module-path" ].(string )
510+ nameOverrides , _ := sidekick .Codec ["name-overrides" ].(string )
511+ postProcessProtos , _ := sidekick .Codec ["post-process-protos" ].(string )
512+ templateOverride , _ := sidekick .Codec ["template-override" ].(string )
513+ generateSetterSamples , ok := sidekick .Codec ["generate-setter-samples" ].(string )
514+ if ! ok {
515+ generateSetterSamples = "true"
516+ }
517+
518+ modules = append (modules , & config.RustModule {
519+ GenerateSetterSamples : strToBool (generateSetterSamples ),
520+ HasVeneer : strToBool (hasVeneer ),
521+ IncludedIds : strToSlice (includedIds ),
522+ IncludeGrpcOnlyMethods : strToBool (includeGrpcOnlyMethods ),
523+ IncludeList : includeList ,
524+ ModulePath : modulePath ,
525+ NameOverrides : nameOverrides ,
526+ Output : filepath .Dir (path ),
527+ PostProcessProtos : postProcessProtos ,
528+ RoutingRequired : strToBool (routingRequired ),
529+ ServiceConfig : sidekick .General .ServiceConfig ,
530+ SkippedIds : strToSlice (skippedIds ),
531+ Source : sidekick .General .SpecificationSource ,
532+ Template : strings .TrimPrefix (templateOverride , "templates/" ),
533+ })
534+
535+ return nil
536+ })
537+
538+ return modules , err
539+ }
540+
427541// buildConfig builds the complete config from libraries.
428542func buildConfig (libraries map [string ]* config.Library , defaults * config.Config ) * config.Config {
429543 cfg := defaults
@@ -506,3 +620,17 @@ func strToSlice(s string) []string {
506620func isEmptyRustCrate (r * config.RustCrate ) bool {
507621 return reflect .DeepEqual (r , & config.RustCrate {})
508622}
623+
624+ func readTOML [T any ](file string ) (* T , error ) {
625+ data , err := os .ReadFile (file )
626+ if err != nil {
627+ return nil , fmt .Errorf ("failed to read %s: %w" , file , err )
628+ }
629+
630+ var tomlData T
631+ if err := toml .Unmarshal (data , & tomlData ); err != nil {
632+ return nil , fmt .Errorf ("failed to unmarshal %s: %w" , file , err )
633+ }
634+
635+ return & tomlData , nil
636+ }
0 commit comments