blob: 2f383748d214f28cd671f956c2941d5b3ac07f38 [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"
"os"
"path/filepath"
"sync"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/merger"
"github.com/bazelbuild/bazel-gazelle/internal/repos"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
)
type updateReposFn func(c *updateReposConfig, oldFile *rule.File, kinds map[string]rule.KindInfo) error
type updateReposConfig struct {
fn updateReposFn
lockFilename string
importPaths []string
}
const updateReposName = "_update-repos"
func getUpdateReposConfig(c *config.Config) *updateReposConfig {
return c.Exts[updateReposName].(*updateReposConfig)
}
type updateReposConfigurer struct{}
func (_ *updateReposConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
uc := &updateReposConfig{}
c.Exts[updateReposName] = uc
fs.StringVar(&uc.lockFilename, "from_file", "", "Gazelle will translate repositories listed in this file into repository rules in WORKSPACE. Currently only dep's Gopkg.lock is supported.")
}
func (_ *updateReposConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
uc := getUpdateReposConfig(c)
switch {
case uc.lockFilename != "":
if len(fs.Args()) != 0 {
return fmt.Errorf("Got %d positional arguments with -from_file; wanted 0.\nTry -help for more information.", len(fs.Args()))
}
uc.fn = importFromLockFile
default:
if len(fs.Args()) == 0 {
return fmt.Errorf("No repositories specified\nTry -help for more information.")
}
uc.fn = updateImportPaths
uc.importPaths = fs.Args()
}
return nil
}
func (_ *updateReposConfigurer) KnownDirectives() []string { return nil }
func (_ *updateReposConfigurer) Configure(c *config.Config, rel string, f *rule.File) {}
func updateRepos(args []string) error {
cexts := make([]config.Configurer, 0, len(languages)+2)
cexts = append(cexts, &config.CommonConfigurer{}, &updateReposConfigurer{})
kinds := make(map[string]rule.KindInfo)
loads := []rule.LoadInfo{}
for _, lang := range languages {
cexts = append(cexts, lang)
loads = append(loads, lang.Loads()...)
for kind, info := range lang.Kinds() {
kinds[kind] = info
}
}
c, err := newUpdateReposConfiguration(args, cexts)
if err != nil {
return err
}
uc := getUpdateReposConfig(c)
workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE")
f, err := rule.LoadFile(workspacePath, "")
if err != nil {
return fmt.Errorf("error loading %q: %v", workspacePath, err)
}
merger.FixWorkspace(f)
if err := uc.fn(uc, f, kinds); err != nil {
return err
}
merger.FixLoads(f, loads)
if err := merger.CheckGazelleLoaded(f); err != nil {
return err
}
if err := f.Save(f.Path); err != nil {
return fmt.Errorf("error writing %q: %v", f.Path, err)
}
return nil
}
func newUpdateReposConfiguration(args []string, cexts []config.Configurer) (*config.Config, error) {
c := config.New()
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() {}
for _, cext := range cexts {
cext.RegisterFlags(fs, "update-repos", c)
}
if err := fs.Parse(args); err != nil {
if err == flag.ErrHelp {
updateReposUsage(fs)
return nil, err
}
// flag already prints the error; don't print it again.
return nil, errors.New("Try -help for more information")
}
for _, cext := range cexts {
if err := cext.CheckFlags(fs, c); err != nil {
return nil, err
}
}
return c, nil
}
func updateReposUsage(fs *flag.FlagSet) {
fmt.Fprint(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 *updateReposConfig, f *rule.File, kinds map[string]rule.KindInfo) error {
rs := repos.ListRepositories(f)
rc := repos.NewRemoteCache(rs)
genRules := make([]*rule.Rule, 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, imp string) {
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, imp)
}
wg.Wait()
for _, err := range errs {
if err != nil {
return err
}
}
merger.MergeFile(f, nil, genRules, merger.PreResolve, kinds)
return nil
}
func importFromLockFile(c *updateReposConfig, f *rule.File, kinds map[string]rule.KindInfo) error {
genRules, err := repos.ImportRepoRules(c.lockFilename)
if err != nil {
return err
}
merger.MergeFile(f, nil, genRules, merger.PreResolve, kinds)
return nil
}