github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/language/protobuf/generate.go (about)

     1  package protobuf
     2  
     3  import (
     4  	"log"
     5  	"path"
     6  
     7  	"github.com/bazelbuild/bazel-gazelle/config"
     8  	"github.com/bazelbuild/bazel-gazelle/label"
     9  	"github.com/bazelbuild/bazel-gazelle/language"
    10  
    11  	"github.com/stackb/rules_proto/pkg/protoc"
    12  )
    13  
    14  // GenerateRules extracts build metadata from source files in a directory.
    15  // GenerateRules is called in each directory where an update is requested in
    16  // depth-first post-order.
    17  //
    18  // args contains the arguments for GenerateRules. This is passed as a struct to
    19  // avoid breaking implementations in the future when new fields are added.
    20  //
    21  // A GenerateResult struct is returned. Optional fields may be added to this
    22  // type in the future.
    23  //
    24  // Any non-fatal errors this function encounters should be logged using
    25  // log.Print.
    26  func (pl *protobufLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
    27  	cfg := pl.getOrCreatePackageConfig(args.Config)
    28  
    29  	files := make(map[string]*protoc.File)
    30  	for _, f := range args.RegularFiles {
    31  		if !protoc.IsProtoFile(f) {
    32  			continue
    33  		}
    34  		file := protoc.NewFile(args.Rel, f)
    35  		if err := file.Parse(); err != nil {
    36  			log.Printf("warning: unparseable proto file dir=%s, file=%s: %v", args.Dir, file.Basename, err)
    37  			continue
    38  		}
    39  		files[f] = file
    40  
    41  		// Record the list of dependencies for this proto file.  Dependents are
    42  		// encoded as labels as a matter of practicality given the API of the
    43  		// resolver.
    44  		for _, imp := range file.Imports() {
    45  			dir := path.Dir(imp.Filename)
    46  			if dir == "." {
    47  				dir = ""
    48  			}
    49  			pl.resolver.Provide(
    50  				"proto",
    51  				"depends",
    52  				path.Join(file.Dir, file.Basename),
    53  				label.New("", dir, path.Base(imp.Filename)),
    54  			)
    55  		}
    56  	}
    57  
    58  	protoLibraries := make([]protoc.ProtoLibrary, 0)
    59  	for _, r := range args.OtherGen {
    60  		internalLabel := label.New("", args.Rel, r.Name())
    61  		protoc.GlobalRuleIndex().Put(internalLabel, r)
    62  
    63  		if r.Kind() != "proto_library" {
    64  			continue
    65  		}
    66  
    67  		srcs := r.AttrStrings("srcs")
    68  		srcLabels := make([]label.Label, len(srcs))
    69  		for i, src := range srcs {
    70  			srcLabel, err := label.Parse(src)
    71  			if err != nil {
    72  				log.Fatalf("%s %q: unparseable source label %q: %v", r.Kind(), r.Name(), src, err)
    73  			}
    74  			srcLabels[i] = srcLabel
    75  
    76  			// record the label that "provides" each proto file.
    77  			pl.resolver.Provide(
    78  				"proto",
    79  				"proto",
    80  				path.Join(args.Rel, src),
    81  				internalLabel,
    82  			)
    83  		}
    84  
    85  		lib := protoc.NewOtherProtoLibrary(args.File, r, matchingFiles(files, srcLabels)...)
    86  		protoLibraries = append(protoLibraries, lib)
    87  	}
    88  
    89  	pkg := protoc.NewPackage(args.Rel, cfg, protoLibraries...)
    90  	pl.packages[args.Rel] = pkg
    91  
    92  	rules := pkg.Rules()
    93  
    94  	// special case if we want to override com_google_protobuf or go_googleapis
    95  	// deps.
    96  	if pl.reresolveKnownProtoImports && len(protoLibraries) > 0 {
    97  		rules = append(rules, makeProtoOverrideRule(protoLibraries))
    98  	}
    99  
   100  	imports := make([]interface{}, len(rules))
   101  	for i, r := range rules {
   102  		imports[i] = r.PrivateAttr(config.GazelleImportsKey)
   103  		internalLabel := label.New("", args.Rel, r.Name())
   104  		protoc.GlobalRuleIndex().Put(internalLabel, r)
   105  	}
   106  
   107  	// special case if this is the root BUILD file and the user requested to
   108  	// write the imports file.
   109  	if args.Rel == "" && pl.importsOutFile != "" {
   110  		if err := protoc.GlobalResolver().SaveFile(pl.importsOutFile, pl.repoName); err != nil {
   111  			log.Printf("error saving import file: %s: %v", pl.importsOutFile, err)
   112  		}
   113  	}
   114  
   115  	return language.GenerateResult{
   116  		Gen:     rules,
   117  		Imports: imports,
   118  		Empty:   pkg.Empty(),
   119  	}
   120  }
   121  
   122  func matchingFiles(files map[string]*protoc.File, srcs []label.Label) []*protoc.File {
   123  	matching := make([]*protoc.File, 0)
   124  	for _, src := range srcs {
   125  		if file, ok := files[src.Name]; ok {
   126  			matching = append(matching, file)
   127  		}
   128  	}
   129  	return matching
   130  }