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

     1  /* Copyright 2018 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  package main
    17  
    18  import (
    19  	"bytes"
    20  	"errors"
    21  	"flag"
    22  	"fmt"
    23  	"log"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  
    30  	"github.com/bazelbuild/bazel-gazelle/internal/wspace"
    31  	"github.com/bazelbuild/bazel-gazelle/label"
    32  	"github.com/bazelbuild/bazel-gazelle/pathtools"
    33  	"github.com/bazelbuild/buildtools/build"
    34  )
    35  
    36  const usageMessage = `usage: move_labels [-repo_root=root] [-from=dir] -to=dir
    37  
    38  move_labels updates Bazel labels in a tree containing build files after the
    39  tree has been moved to a new location. This is useful for vendoring
    40  repositories that already have Bazel build files.
    41  
    42  `
    43  
    44  func main() {
    45  	log.SetPrefix("move_labels: ")
    46  	log.SetFlags(0)
    47  	if err := run(os.Args[1:]); err != nil {
    48  		log.Fatal(err)
    49  	}
    50  }
    51  
    52  func run(args []string) error {
    53  	c, err := newConfiguration(args)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	files, err := moveLabelsInDir(c)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	var errs errorList
    64  	for _, file := range files {
    65  		content := build.Format(file)
    66  		if err := os.WriteFile(file.Path, content, 0o666); err != nil {
    67  			errs = append(errs, err)
    68  		}
    69  	}
    70  	if len(errs) > 0 {
    71  		return errs
    72  	}
    73  	return nil
    74  }
    75  
    76  func moveLabelsInDir(c *configuration) ([]*build.File, error) {
    77  	toRel, err := filepath.Rel(c.repoRoot, c.to)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	toRel = filepath.ToSlash(toRel)
    82  
    83  	var files []*build.File
    84  	var errors errorList
    85  	err = filepath.Walk(c.to, func(path string, info os.FileInfo, err error) error {
    86  		if err != nil {
    87  			return err
    88  		}
    89  		if name := info.Name(); name != "BUILD" && name != "BUILD.bazel" {
    90  			return nil
    91  		}
    92  		content, err := os.ReadFile(path)
    93  		if err != nil {
    94  			errors = append(errors, err)
    95  			return nil
    96  		}
    97  		file, err := build.Parse(path, content)
    98  		if err != nil {
    99  			errors = append(errors, err)
   100  			return nil
   101  		}
   102  		moveLabelsInFile(file, c.from, toRel)
   103  		files = append(files, file)
   104  		return nil
   105  	})
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	if len(errors) > 0 {
   110  		return nil, errors
   111  	}
   112  	return files, nil
   113  }
   114  
   115  func moveLabelsInFile(file *build.File, from, to string) {
   116  	build.Edit(file, func(x build.Expr, _ []build.Expr) build.Expr {
   117  		str, ok := x.(*build.StringExpr)
   118  		if !ok {
   119  			return nil
   120  		}
   121  		label := str.Value
   122  		var moved string
   123  		if strings.Contains(label, "$(location") {
   124  			moved = moveLocations(from, to, label)
   125  		} else {
   126  			moved = moveLabel(from, to, label)
   127  		}
   128  		if moved == label {
   129  			return nil
   130  		}
   131  		return &build.StringExpr{Value: moved}
   132  	})
   133  }
   134  
   135  func moveLabel(from, to, str string) string {
   136  	l, err := label.Parse(str)
   137  	if err != nil {
   138  		return str
   139  	}
   140  	if l.Relative || l.Repo != "" ||
   141  		l.Pkg == "visibility" || l.Pkg == "conditions" ||
   142  		pathtools.HasPrefix(l.Pkg, to) || !pathtools.HasPrefix(l.Pkg, from) {
   143  		return str
   144  	}
   145  	l.Pkg = path.Join(to, pathtools.TrimPrefix(l.Pkg, from))
   146  	return l.String()
   147  }
   148  
   149  var locationsRegexp = regexp.MustCompile(`\$\(locations?\s*([^)]*)\)`)
   150  
   151  // moveLocations fixes labels within $(location) and $(locations) expansions.
   152  func moveLocations(from, to, str string) string {
   153  	matches := locationsRegexp.FindAllStringSubmatchIndex(str, -1)
   154  	buf := new(bytes.Buffer)
   155  	pos := 0
   156  	for _, match := range matches {
   157  		buf.WriteString(str[pos:match[2]])
   158  		label := str[match[2]:match[3]]
   159  		moved := moveLabel(from, to, label)
   160  		buf.WriteString(moved)
   161  		buf.WriteString(str[match[3]:match[1]])
   162  		pos = match[1]
   163  	}
   164  	buf.WriteString(str[pos:])
   165  	return buf.String()
   166  }
   167  
   168  type configuration struct {
   169  	// repoRoot is the repository root directory, formatted as an absolute
   170  	// file system path.
   171  	repoRoot string
   172  
   173  	// from is the original location of the build files within their repository,
   174  	// formatted as a slash-separated relative path from the original
   175  	// repository root.
   176  	from string
   177  
   178  	// to is the new location of the build files, formatted as an absolute
   179  	// file system path.
   180  	to string
   181  }
   182  
   183  func newConfiguration(args []string) (*configuration, error) {
   184  	var err error
   185  	c := &configuration{}
   186  	fs := flag.NewFlagSet("move_labels", flag.ContinueOnError)
   187  	fs.Usage = func() {}
   188  	fs.StringVar(&c.repoRoot, "repo_root", "", "repository root directory; inferred to be parent directory containing WORKSPACE file")
   189  	fs.StringVar(&c.from, "from", "", "original location of build files, formatted as a slash-separated relative path from the original repository root")
   190  	fs.StringVar(&c.to, "to", "", "new location of build files, formatted as a file system path")
   191  	if err := fs.Parse(args); err != nil {
   192  		if err == flag.ErrHelp {
   193  			fmt.Fprint(os.Stderr, usageMessage)
   194  			fs.PrintDefaults()
   195  			os.Exit(0)
   196  		}
   197  		// flag already prints an error; don't print again.
   198  		return nil, errors.New("Try -help for more information")
   199  	}
   200  
   201  	if c.repoRoot == "" {
   202  		c.repoRoot, err = findRepoRoot()
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  	}
   207  	c.repoRoot, err = filepath.Abs(c.repoRoot)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	if c.to == "" {
   213  		return nil, errors.New("-to must be specified. Try -help for more information.")
   214  	}
   215  	c.to, err = filepath.Abs(c.to)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	if len(fs.Args()) != 0 {
   221  		return nil, errors.New("No positional arguments expected. Try -help for more information.")
   222  	}
   223  
   224  	return c, nil
   225  }
   226  
   227  func findRepoRoot() (string, error) {
   228  	dir, err := os.Getwd()
   229  	if err != nil {
   230  		return "", err
   231  	}
   232  	root, err := wspace.FindRepoRoot(dir)
   233  	if err != nil {
   234  		return "", fmt.Errorf("could not find WORKSPACE file. -repo_root must be set explicitly")
   235  	}
   236  	return root, nil
   237  }
   238  
   239  type errorList []error
   240  
   241  func (e errorList) Error() string {
   242  	buf := new(bytes.Buffer)
   243  	for _, err := range e {
   244  		fmt.Fprintln(buf, err.Error())
   245  	}
   246  	return buf.String()
   247  }