github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/cmd/gazelle/update-repos.go (about)

     1  /* Copyright 2017 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  	"errors"
    20  	"flag"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"sync"
    26  
    27  	"github.com/bazelbuild/bazel-gazelle/internal/merger"
    28  	"github.com/bazelbuild/bazel-gazelle/internal/repos"
    29  	"github.com/bazelbuild/bazel-gazelle/internal/wspace"
    30  	bf "github.com/bazelbuild/buildtools/build"
    31  )
    32  
    33  type updateReposFn func(c *updateReposConfiguration, oldFile *bf.File) (*bf.File, error)
    34  
    35  type updateReposConfiguration struct {
    36  	fn           updateReposFn
    37  	repoRoot     string
    38  	lockFilename string
    39  	importPaths  []string
    40  }
    41  
    42  func updateRepos(args []string) error {
    43  	c, err := newUpdateReposConfiguration(args)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	workspacePath := filepath.Join(c.repoRoot, "WORKSPACE")
    49  	content, err := ioutil.ReadFile(workspacePath)
    50  	if err != nil {
    51  		return fmt.Errorf("error reading %q: %v", workspacePath, err)
    52  	}
    53  	oldFile, err := bf.Parse(workspacePath, content)
    54  	if err != nil {
    55  		return fmt.Errorf("error parsing %q: %v", workspacePath, err)
    56  	}
    57  
    58  	mergedFile, err := c.fn(c, oldFile)
    59  	if err != nil {
    60  		return err
    61  	}
    62  	mergedFile = merger.FixLoads(mergedFile)
    63  	if err := ioutil.WriteFile(mergedFile.Path, bf.Format(mergedFile), 0666); err != nil {
    64  		return fmt.Errorf("error writing %q: %v", mergedFile.Path, err)
    65  	}
    66  	return nil
    67  }
    68  
    69  func newUpdateReposConfiguration(args []string) (*updateReposConfiguration, error) {
    70  	c := new(updateReposConfiguration)
    71  	fs := flag.NewFlagSet("gazelle", flag.ContinueOnError)
    72  	// Flag will call this on any parse error. Don't print usage unless
    73  	// -h or -help were passed explicitly.
    74  	fs.Usage = func() {}
    75  
    76  	fromFileFlag := fs.String("from_file", "", "Gazelle will translate repositories listed in this file into repository rules in WORKSPACE. Currently only dep's Gopkg.lock is supported.")
    77  	repoRootFlag := fs.String("repo_root", "", "path to the root directory of the repository. If unspecified, this is assumed to be the directory containing WORKSPACE.")
    78  	if err := fs.Parse(args); err != nil {
    79  		if err == flag.ErrHelp {
    80  			updateReposUsage(fs)
    81  			os.Exit(0)
    82  		}
    83  		// flag already prints the error; don't print it again.
    84  		return nil, errors.New("Try -help for more information")
    85  	}
    86  
    87  	// Handle general flags that apply to all subcommands.
    88  	c.repoRoot = *repoRootFlag
    89  	if c.repoRoot == "" {
    90  		if repoRoot, err := wspace.Find("."); err != nil {
    91  			return nil, err
    92  		} else {
    93  			c.repoRoot = repoRoot
    94  		}
    95  	}
    96  
    97  	// Handle flags specific to each subcommand.
    98  	switch {
    99  	case *fromFileFlag != "":
   100  		if len(fs.Args()) != 0 {
   101  			return nil, fmt.Errorf("Got %d positional arguments with -from_file; wanted 0.\nTry -help for more information.", len(fs.Args()))
   102  		}
   103  		c.fn = importFromLockFile
   104  		c.lockFilename = *fromFileFlag
   105  
   106  	default:
   107  		if len(fs.Args()) == 0 {
   108  			return nil, fmt.Errorf("No repositories specified\nTry -help for more information.")
   109  		}
   110  		c.fn = updateImportPaths
   111  		c.importPaths = fs.Args()
   112  	}
   113  
   114  	return c, nil
   115  }
   116  
   117  func updateReposUsage(fs *flag.FlagSet) {
   118  	fmt.Fprintln(os.Stderr, `usage:
   119  
   120  # Add/update repositories by import path
   121  gazelle update-repos example.com/repo1 example.com/repo2
   122  
   123  # Import repositories from lock file
   124  gazelle update-repos -from_file=file
   125  
   126  The update-repos command updates repository rules in the WORKSPACE file.
   127  update-repos can add or update repositories explicitly by import path.
   128  update-repos can also import repository rules from a vendoring tool's lock
   129  file (currently only deps' Gopkg.lock is supported).
   130  
   131  FLAGS:
   132  `)
   133  }
   134  
   135  func updateImportPaths(c *updateReposConfiguration, oldFile *bf.File) (*bf.File, error) {
   136  	rs := repos.ListRepositories(oldFile)
   137  	rc := repos.NewRemoteCache(rs)
   138  
   139  	genRules := make([]bf.Expr, len(c.importPaths))
   140  	errs := make([]error, len(c.importPaths))
   141  	var wg sync.WaitGroup
   142  	wg.Add(len(c.importPaths))
   143  	for i, imp := range c.importPaths {
   144  		go func(i int) {
   145  			defer wg.Done()
   146  			repo, err := repos.UpdateRepo(rc, imp)
   147  			if err != nil {
   148  				errs[i] = err
   149  				return
   150  			}
   151  			repo.Remote = "" // don't set these explicitly
   152  			repo.VCS = ""
   153  			rule := repos.GenerateRule(repo)
   154  			genRules[i] = rule
   155  		}(i)
   156  	}
   157  	wg.Wait()
   158  
   159  	for _, err := range errs {
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  	}
   164  	mergedFile, _ := merger.MergeFile(genRules, nil, oldFile, merger.RepoAttrs)
   165  	return mergedFile, nil
   166  }
   167  
   168  func importFromLockFile(c *updateReposConfiguration, oldFile *bf.File) (*bf.File, error) {
   169  	genRules, err := repos.ImportRepoRules(c.lockFilename)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	mergedFile, _ := merger.MergeFile(genRules, nil, oldFile, merger.RepoAttrs)
   175  	return mergedFile, nil
   176  }