github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/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  	"io/ioutil"
    24  	"log"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strings"
    30  
    31  	"github.com/bazelbuild/bazel-gazelle/internal/label"
    32  	"github.com/bazelbuild/bazel-gazelle/internal/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 := ioutil.WriteFile(file.Path, content, 0666); 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 := ioutil.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  func isBuiltinLabel(label string) bool {
   169  	return strings.HasPrefix(label, "//visibility:") || strings.HasPrefix(label, "//conditions:")
   170  }
   171  
   172  type configuration struct {
   173  	// repoRoot is the repository root directory, formatted as an absolute
   174  	// file system path.
   175  	repoRoot string
   176  
   177  	// from is the original location of the build files within their repository,
   178  	// formatted as a slash-separated relative path from the original
   179  	// repository root.
   180  	from string
   181  
   182  	// to is the new location of the build files, formatted as an absolute
   183  	// file system path.
   184  	to string
   185  }
   186  
   187  func newConfiguration(args []string) (*configuration, error) {
   188  	var err error
   189  	c := &configuration{}
   190  	fs := flag.NewFlagSet("move_labels", flag.ContinueOnError)
   191  	fs.Usage = func() {}
   192  	fs.StringVar(&c.repoRoot, "repo_root", "", "repository root directory; inferred to be parent directory containing WORKSPACE file")
   193  	fs.StringVar(&c.from, "from", "", "original location of build files, formatted as a slash-separated relative path from the original repository root")
   194  	fs.StringVar(&c.to, "to", "", "new location of build files, formatted as a file system path")
   195  	if err := fs.Parse(args); err != nil {
   196  		if err == flag.ErrHelp {
   197  			fmt.Fprint(os.Stderr, usageMessage)
   198  			fs.PrintDefaults()
   199  			os.Exit(0)
   200  		}
   201  		// flag already prints an error; don't print again.
   202  		return nil, errors.New("Try -help for more information")
   203  	}
   204  
   205  	if c.repoRoot == "" {
   206  		c.repoRoot, err = findRepoRoot()
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  	}
   211  	c.repoRoot, err = filepath.Abs(c.repoRoot)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	if c.to == "" {
   217  		return nil, errors.New("-to must be specified. Try -help for more information.")
   218  	}
   219  	c.to, err = filepath.Abs(c.to)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	if len(fs.Args()) != 0 {
   225  		return nil, errors.New("No positional arguments expected. Try -help for more information.")
   226  	}
   227  
   228  	return c, nil
   229  }
   230  
   231  func findRepoRoot() (string, error) {
   232  	dir, err := os.Getwd()
   233  	if err != nil {
   234  		return "", err
   235  	}
   236  	for {
   237  		workspacePath := filepath.Join(dir, "WORKSPACE")
   238  		_, err := os.Stat(workspacePath)
   239  		if err == nil {
   240  			return dir, nil
   241  		}
   242  		if strings.HasSuffix(dir, string(os.PathSeparator)) {
   243  			// root directory
   244  			break
   245  		}
   246  		dir = filepath.Dir(dir)
   247  	}
   248  	return "", fmt.Errorf("could not find WORKSPACE file. -repo_root must be set explicitly.")
   249  }
   250  
   251  type errorList []error
   252  
   253  func (e errorList) Error() string {
   254  	buf := new(bytes.Buffer)
   255  	for _, err := range e {
   256  		fmt.Fprintln(buf, err.Error())
   257  	}
   258  	return buf.String()
   259  }