github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/cmd/generate_repo_config/generate_repo_config.go (about)

     1  /* Copyright 2019 The Bazel Authors. All rights reserved.
     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  
    16  // Command generate_repo_config takes in a build config file such as
    17  // WORKSPACE and generates a stripped version of the file. The generated
    18  // config file should contain only the information relevant to gazelle for
    19  // dependency resolution, so go_repository rules with importpath
    20  // and name defined, plus any directives.
    21  //
    22  // This command is used by the go_repository_config rule to generate a repo
    23  // config file used by all go_repository rules. A list of macro files is
    24  // printed to stdout to be read by the go_repository_config rule.
    25  package main
    26  
    27  import (
    28  	"bytes"
    29  	"flag"
    30  	"fmt"
    31  	"log"
    32  	"os"
    33  	"path/filepath"
    34  	"sort"
    35  	"strings"
    36  
    37  	"github.com/bazelbuild/bazel-gazelle/repo"
    38  	"github.com/bazelbuild/bazel-gazelle/rule"
    39  )
    40  
    41  const (
    42  	goRepoRuleKind      = "go_repository"
    43  	httpArchiveRuleKind = "http_archive"
    44  )
    45  
    46  var (
    47  	configSource = flag.String("config_source", "", "a file that is read to learn about external repositories")
    48  	configDest   = flag.String("config_dest", "", "destination file for the generated repo config")
    49  )
    50  
    51  type byName []*rule.Rule
    52  
    53  func (s byName) Len() int           { return len(s) }
    54  func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
    55  func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    56  
    57  func main() {
    58  	log.SetFlags(0)
    59  	log.SetPrefix("generate_repo_config: ")
    60  
    61  	flag.Parse()
    62  	if *configDest == "" {
    63  		log.Fatal("-config_dest must be set")
    64  	}
    65  	if *configSource == "" {
    66  		log.Fatal("-config_source must be set")
    67  	}
    68  	if flag.NArg() != 0 {
    69  		log.Fatal("generate_repo_config does not accept positional arguments")
    70  	}
    71  	files, err := generateRepoConfig(*configDest, *configSource)
    72  	if err != nil {
    73  		log.Fatal(err)
    74  	}
    75  	for _, f := range files {
    76  		fmt.Fprintln(os.Stdout, f)
    77  	}
    78  }
    79  
    80  func generateRepoConfig(configDest, configSource string) ([]string, error) {
    81  	var buf bytes.Buffer
    82  	buf.WriteString("# Code generated by generate_repo_config.go; DO NOT EDIT.\n")
    83  
    84  	sourceFile, err := rule.LoadWorkspaceFile(configSource, "")
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	repos, repoFileMap, err := repo.ListRepositories(sourceFile)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	sort.Stable(byName(repos))
    93  
    94  	seenFile := make(map[*rule.File]bool)
    95  	var sortedFiles []*rule.File
    96  	for _, f := range repoFileMap {
    97  		if !seenFile[f] {
    98  			seenFile[f] = true
    99  			sortedFiles = append(sortedFiles, f)
   100  		}
   101  	}
   102  	sort.SliceStable(sortedFiles, func(i, j int) bool {
   103  		if cmp := strings.Compare(sortedFiles[i].Path, sortedFiles[j].Path); cmp != 0 {
   104  			return cmp < 0
   105  		}
   106  		return sortedFiles[i].DefName < sortedFiles[j].DefName
   107  	})
   108  
   109  	destFile := rule.EmptyFile(configDest, "")
   110  	for _, rsrc := range repos {
   111  		var rdst *rule.Rule
   112  		if rsrc.Kind() == goRepoRuleKind {
   113  			rdst = rule.NewRule(goRepoRuleKind, rsrc.Name())
   114  			rdst.SetAttr("importpath", rsrc.AttrString("importpath"))
   115  			if namingConvention := rsrc.AttrString("build_naming_convention"); namingConvention != "" {
   116  				rdst.SetAttr("build_naming_convention", namingConvention)
   117  			}
   118  		} else if rsrc.Kind() == httpArchiveRuleKind && rsrc.Name() == "io_bazel_rules_go" {
   119  			rdst = rule.NewRule(httpArchiveRuleKind, "io_bazel_rules_go")
   120  			rdst.SetAttr("urls", rsrc.AttrStrings("urls"))
   121  		}
   122  		if rdst != nil {
   123  			rdst.Insert(destFile)
   124  		}
   125  	}
   126  
   127  	buf.WriteString("\n")
   128  	buf.Write(destFile.Format())
   129  	if err := os.WriteFile(configDest, buf.Bytes(), 0o666); err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	files := make([]string, 0, len(sortedFiles))
   134  	for _, m := range sortedFiles {
   135  		// We have to trim the configSource file path from the repo files returned.
   136  		// This is safe/required because repo.ListRepositories(sourceFile) is called
   137  		// with the sourcefile as the workspace, so the source file location is always
   138  		// prepended to the macro file paths.
   139  		// TODO: https://github.com/bazelbuild/bazel-gazelle/issues/1068
   140  		f, err := filepath.Rel(filepath.Dir(configSource), m.Path)
   141  		if err != nil {
   142  			return nil, err
   143  		}
   144  		files = append(files, f)
   145  	}
   146  
   147  	return files, nil
   148  }