blob: ad896e74e0d091d8513195f0382b4bead39541d5 [file] [log] [blame] [edit]
/* Copyright 2017 The Bazel Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/bazelbuild/bazel-gazelle/internal/merger"
"github.com/bazelbuild/bazel-gazelle/internal/repos"
"github.com/bazelbuild/bazel-gazelle/internal/wspace"
bf "github.com/bazelbuild/buildtools/build"
)
type updateReposFn func(c *updateReposConfiguration, oldFile *bf.File) (*bf.File, error)
type updateReposConfiguration struct {
fn updateReposFn
repoRoot string
lockFilename string
importPaths []string
}
func updateRepos(args []string) error {
c, err := newUpdateReposConfiguration(args)
if err != nil {
return err
}
workspacePath := filepath.Join(c.repoRoot, "WORKSPACE")
content, err := ioutil.ReadFile(workspacePath)
if err != nil {
return fmt.Errorf("error reading %q: %v", workspacePath, err)
}
oldFile, err := bf.Parse(workspacePath, content)
if err != nil {
return fmt.Errorf("error parsing %q: %v", workspacePath, err)
}
mergedFile, err := c.fn(c, oldFile)
if err != nil {
return err
}
mergedFile = merger.FixLoads(mergedFile)
if err := ioutil.WriteFile(mergedFile.Path, bf.Format(mergedFile), 0666); err != nil {
return fmt.Errorf("error writing %q: %v", mergedFile.Path, err)
}
return nil
}
func newUpdateReposConfiguration(args []string) (*updateReposConfiguration, error) {
c := new(updateReposConfiguration)
fs := flag.NewFlagSet("gazelle", flag.ContinueOnError)
// Flag will call this on any parse error. Don't print usage unless
// -h or -help were passed explicitly.
fs.Usage = func() {}
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.")
repoRootFlag := fs.String("repo_root", "", "path to the root directory of the repository. If unspecified, this is assumed to be the directory containing WORKSPACE.")
if err := fs.Parse(args); err != nil {
if err == flag.ErrHelp {
updateReposUsage(fs)
os.Exit(0)
}
// flag already prints the error; don't print it again.
return nil, errors.New("Try -help for more information")
}
// Handle general flags that apply to all subcommands.
c.repoRoot = *repoRootFlag
if c.repoRoot == "" {
if repoRoot, err := wspace.Find("."); err != nil {
return nil, err
} else {
c.repoRoot = repoRoot
}
}
// Handle flags specific to each subcommand.
switch {
case *fromFileFlag != "":
if len(fs.Args()) != 0 {
return nil, fmt.Errorf("Got %d positional arguments with -from_file; wanted 0.\nTry -help for more information.", len(fs.Args()))
}
c.fn = importFromLockFile
c.lockFilename = *fromFileFlag
default:
if len(fs.Args()) == 0 {
return nil, fmt.Errorf("No repositories specified\nTry -help for more information.")
}
c.fn = updateImportPaths
c.importPaths = fs.Args()
}
return c, nil
}
func updateReposUsage(fs *flag.FlagSet) {
fmt.Fprintln(os.Stderr, `usage:
# Add/update repositories by import path
gazelle update-repos example.com/repo1 example.com/repo2
# Import repositories from lock file
gazelle update-repos -from_file=file
The update-repos command updates repository rules in the WORKSPACE file.
update-repos can add or update repositories explicitly by import path.
update-repos can also import repository rules from a vendoring tool's lock
file (currently only deps' Gopkg.lock is supported).
FLAGS:
`)
}
func updateImportPaths(c *updateReposConfiguration, oldFile *bf.File) (*bf.File, error) {
rs := repos.ListRepositories(oldFile)
rc := repos.NewRemoteCache(rs)
genRules := make([]bf.Expr, len(c.importPaths))
errs := make([]error, len(c.importPaths))
var wg sync.WaitGroup
wg.Add(len(c.importPaths))
for i, imp := range c.importPaths {
go func(i int) {
defer wg.Done()
repo, err := repos.UpdateRepo(rc, imp)
if err != nil {
errs[i] = err
return
}
repo.Remote = "" // don't set these explicitly
repo.VCS = ""
rule := repos.GenerateRule(repo)
genRules[i] = rule
}(i)
}
wg.Wait()
for _, err := range errs {
if err != nil {
return nil, err
}
}
mergedFile, _ := merger.MergeFile(genRules, nil, oldFile, merger.RepoAttrs)
return mergedFile, nil
}
func importFromLockFile(c *updateReposConfiguration, oldFile *bf.File) (*bf.File, error) {
genRules, err := repos.ImportRepoRules(c.lockFilename)
if err != nil {
return nil, err
}
mergedFile, _ := merger.MergeFile(genRules, nil, oldFile, merger.RepoAttrs)
return mergedFile, nil
}