diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e03394c..c69b774 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -18,5 +18,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.55.2 + version: v1.57.2 diff --git a/cmd/puku/puku.go b/cmd/puku/puku.go index c1116d4..6e37544 100644 --- a/cmd/puku/puku.go +++ b/cmd/puku/puku.go @@ -30,10 +30,12 @@ var opts = struct { } `positional-args:"true"` } `command:"fmt" description:"Format build files in the provided paths"` Sync struct { - Write bool `short:"w" long:"write" description:"Whether to write the files back or just print them to stdout"` + Format string `short:"f" long:"format" choice:"json" choice:"text" default:"text" description:"output format when outputting to stdout"` //nolint + Write bool `short:"w" long:"write" description:"Whether to write the files back or just print them to stdout"` } `command:"sync" description:"Synchronises the go.mod to the third party build file"` Lint struct { - Args struct { + Format string `short:"f" long:"format" choice:"json" choice:"text" default:"text" description:"output format when outputting to stdout"` //nolint + Args struct { Paths []string `positional-arg-name:"packages" description:"The packages to process"` } `positional-args:"true"` } `command:"lint" description:"Lint build files in the provided paths"` @@ -44,6 +46,7 @@ var opts = struct { } `command:"watch" description:"Watch build files in the provided paths and update them when needed"` Migrate struct { Write bool `short:"w" long:"write" description:"Whether to write the files back or just print them to stdout"` + Format string `short:"f" long:"format" choice:"json" choice:"text" default:"text" description:"output format when outputting to stdout"` //nolint ThirdPartyDirs []string `long:"third_party_dir" description:"Directories to find go_module rules to migrate"` UpdateGoMod bool `short:"g" long:"update_go_mod" description:"Update the go mod with the module(s) being migrated"` Args struct { @@ -52,8 +55,9 @@ var opts = struct { } `command:"migrate" description:"Migrates from go_module to go_repo"` Licenses struct { Update struct { - Write bool `short:"w" long:"write" description:"Whether to write the files back or just print them to stdout"` - Args struct { + Format string `short:"f" long:"format" choice:"json" choice:"text" default:"text" description:"output format when outputting to stdout"` //nolint + Write bool `short:"w" long:"write" description:"Whether to write the files back or just print them to stdout"` + Args struct { Paths []string `positional-arg-name:"packages" description:"The packages to process"` } `positional-args:"true"` } `command:"update" description:"Updates licences in the given paths"` @@ -67,30 +71,37 @@ puku is a tool used to generate and update Go targets in build files var log = logging.GetLogger() var funcs = map[string]func(conf *config.Config, plzConf *please.Config, orignalWD string) int{ - "fmt": func(conf *config.Config, plzConf *please.Config, orignalWD string) int { + "fmt": func(_ *config.Config, plzConf *please.Config, orignalWD string) int { paths := work.MustExpandPaths(orignalWD, opts.Fmt.Args.Paths) - if err := generate.Update(true, plzConf, paths...); err != nil { + if err := generate.Update(plzConf, paths...); err != nil { log.Fatalf("%v", err) } return 0 }, - "sync": func(conf *config.Config, plzConf *please.Config, orignalWD string) int { + "sync": func(_ *config.Config, plzConf *please.Config, _ string) int { g := graph.New(plzConf.BuildFileNames()) - if err := sync.Sync(plzConf, g, opts.Sync.Write); err != nil { - log.Fatalf("%v", err) + if opts.Sync.Write { + if err := sync.Sync(plzConf, g); err != nil { + log.Fatalf("%v", err) + } + } else { + if err := sync.SyncToStdout(opts.Sync.Format, plzConf, g); err != nil { + log.Fatalf("%v", err) + } } return 0 }, - "lint": func(conf *config.Config, plzConf *please.Config, orignalWD string) int { + "lint": func(_ *config.Config, plzConf *please.Config, orignalWD string) int { + paths := work.MustExpandPaths(orignalWD, opts.Lint.Args.Paths) - if err := generate.Update(false, plzConf, paths...); err != nil { + if err := generate.UpdateToStdout(opts.Lint.Format, plzConf, paths...); err != nil { log.Fatalf("%v", err) } return 0 }, - "watch": func(conf *config.Config, plzConf *please.Config, orignalWD string) int { + "watch": func(_ *config.Config, plzConf *please.Config, orignalWD string) int { paths := work.MustExpandPaths(orignalWD, opts.Watch.Args.Paths) - if err := generate.Update(true, plzConf, paths...); err != nil { + if err := generate.Update(plzConf, paths...); err != nil { log.Fatalf("%v", err) } @@ -105,15 +116,28 @@ var funcs = map[string]func(conf *config.Config, plzConf *please.Config, orignal paths = []string{conf.GetThirdPartyDir()} } paths = work.MustExpandPaths(orignalWD, paths) - if err := migrate.Migrate(conf, plzConf, opts.Migrate.Write, opts.Migrate.UpdateGoMod, opts.Migrate.Args.Modules, paths); err != nil { - log.Fatalf("%v", err) + if opts.Migrate.Write { + if err := migrate.Migrate(conf, plzConf, opts.Migrate.UpdateGoMod, opts.Migrate.Args.Modules, paths); err != nil { + log.Fatalf("%v", err) + } + } else { + if err := migrate.MigrateToStdout(opts.Migrate.Format, conf, plzConf, opts.Migrate.UpdateGoMod, opts.Migrate.Args.Modules, paths); err != nil { + log.Fatalf("%v", err) + } } return 0 }, - "update": func(conf *config.Config, plzConf *please.Config, orignalWD string) int { + "update": func(_ *config.Config, plzConf *please.Config, orignalWD string) int { paths := work.MustExpandPaths(orignalWD, opts.Licenses.Update.Args.Paths) - if err := licences.New(proxy.New(proxy.DefaultURL), graph.New(plzConf.BuildFileNames())).Update(paths, opts.Licenses.Update.Write); err != nil { - log.Fatalf("%v", err) + l := licences.New(proxy.New(proxy.DefaultURL), graph.New(plzConf.BuildFileNames())) + if opts.Licenses.Update.Write { + if err := l.Update(paths); err != nil { + log.Fatalf("%v", err) + } + } else { + if err := l.UpdateToStdout(opts.Licenses.Update.Format, paths); err != nil { + log.Fatalf("%v", err) + } } return 0 }, diff --git a/generate/generate.go b/generate/generate.go index b224446..ebcf322 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -70,28 +70,24 @@ func newUpdater(conf *please.Config) *updater { return newUpdaterWithGraph(g, conf) } -func Update(write bool, plzConf *please.Config, paths ...string) error { +func Update(plzConf *please.Config, paths ...string) error { u := newUpdater(plzConf) - - conf, err := config.ReadConfig(".") - if err != nil { + if err := u.update(paths...); err != nil { return err } - u.paths = paths - - if err := u.readAllModules(conf); err != nil { - return fmt.Errorf("failed to read third party rules: %v", err) - } + return u.graph.FormatFiles() +} - if err := u.update(conf); err != nil { +func UpdateToStdout(format string, plzConf *please.Config, paths ...string) error { + u := newUpdater(plzConf) + if err := u.update(paths...); err != nil { return err } - - return u.graph.FormatFiles(write, os.Stdout) + return u.graph.FormatFilesWithWriter(os.Stdout, format) } func (u *updater) readAllModules(conf *config.Config) error { - return filepath.WalkDir(conf.GetThirdPartyDir(), func(path string, info fs.DirEntry, err error) error { + return filepath.WalkDir(conf.GetThirdPartyDir(), func(path string, info fs.DirEntry, _ error) error { for _, buildFileName := range u.plzConf.BuildFileNames() { if info.Name() == buildFileName { file, err := u.graph.LoadFile(filepath.Dir(path)) @@ -144,7 +140,17 @@ func (u *updater) readModules(file *build.File) error { } // update loops through the provided paths, updating and creating any build rules it finds. -func (u *updater) update(conf *config.Config) error { +func (u *updater) update(paths ...string) error { + conf, err := config.ReadConfig(".") + if err != nil { + return err + } + u.paths = paths + + if err := u.readAllModules(conf); err != nil { + return fmt.Errorf("failed to read third party rules: %v", err) + } + for _, path := range u.paths { conf, err := config.ReadConfig(path) if err != nil { diff --git a/graph/graph.go b/graph/graph.go index 5fd6c6a..a8f03f4 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -2,6 +2,7 @@ package graph import ( "bytes" + "encoding/json" "fmt" "io" "os" @@ -122,12 +123,24 @@ func (g *Graph) loadFile(path string) (*build.File, error) { return build.ParseBuild(validFilename, nil) } -func (g *Graph) FormatFiles(write bool, out io.Writer) error { +func (g *Graph) FormatFilesWithWriter(out io.Writer, format string) error { if err := g.ensureVisibilities(); err != nil { return err } for _, file := range g.files { - if err := saveAndFormatBuildFile(file, write, out); err != nil { + if err := writeFormattedBuildFile(file, out, format); err != nil { + return err + } + } + return nil +} + +func (g *Graph) FormatFiles() error { + if err := g.ensureVisibilities(); err != nil { + return err + } + for _, file := range g.files { + if err := saveFormattedBuildFile(file); err != nil { return err } } @@ -218,24 +231,11 @@ func checkVisibility(target labels.Label, visibilities []string) bool { return false } -func saveAndFormatBuildFile(buildFile *build.File, write bool, out io.Writer) error { +func writeFormattedBuildFile(buildFile *build.File, out io.Writer, format string) error { if len(buildFile.Stmt) == 0 { return nil } - - if write { - f, err := os.Create(buildFile.Path) - if err != nil { - return err - } - defer f.Close() - - _, err = f.Write(build.FormatWithoutRewriting(buildFile)) - return err - } - target := build.FormatWithoutRewriting(buildFile) - actual, err := os.ReadFile(buildFile.Path) if err != nil { if !os.IsNotExist(err) { @@ -245,9 +245,29 @@ func saveAndFormatBuildFile(buildFile *build.File, write bool, out io.Writer) er } if !bytes.Equal(target, actual) { - _, err := out.Write(target) + switch format { + case "text": + _, err := out.Write(target) + return err + case "json": + e := json.NewEncoder(out) + return e.Encode(struct{ Path, Content string }{Path: buildFile.Path, Content: string(target)}) + } + } + return nil +} + +func saveFormattedBuildFile(buildFile *build.File) error { + if len(buildFile.Stmt) == 0 { + return nil + } + + f, err := os.Create(buildFile.Path) + if err != nil { return err } + defer f.Close() - return nil + _, err = f.Write(build.FormatWithoutRewriting(buildFile)) + return err } diff --git a/graph/graph_test.go b/graph/graph_test.go index 77d55e2..67156f8 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -78,7 +78,7 @@ go_library( }) bs := new(bytes.Buffer) - err = g.FormatFiles(false, bs) + err = g.FormatFilesWithWriter(bs, "text") require.NoError(t, err) fooT := edit.FindTargetByName(g.files["foo"], "foo") diff --git a/licences/licences.go b/licences/licences.go index 3b5a067..04eb6a2 100644 --- a/licences/licences.go +++ b/licences/licences.go @@ -67,7 +67,21 @@ func getLicences(modPaths []string) (map[string][]string, error) { return ret, nil } -func (l *Licenses) Update(paths []string, write bool) error { +func (l *Licenses) Update(paths []string) error { + if err := l.update(paths); err != nil { + return err + } + return l.graph.FormatFiles() +} + +func (l *Licenses) UpdateToStdout(format string, paths []string) error { + if err := l.update(paths); err != nil { + return err + } + return l.graph.FormatFilesWithWriter(os.Stdout, format) +} + +func (l *Licenses) update(paths []string) error { var mods []string rules := make(map[string]*build.Rule) @@ -115,8 +129,7 @@ func (l *Licenses) Update(paths []string, write bool) error { rules[mod].SetAttr("licences", edit.NewStringList(license)) } } - - return l.graph.FormatFiles(write, os.Stdout) + return nil } func (l *Licenses) Get(mod, ver string) ([]string, error) { diff --git a/licences/licences_test.go b/licences/licences_test.go index 2469178..3056f37 100644 --- a/licences/licences_test.go +++ b/licences/licences_test.go @@ -49,7 +49,7 @@ go_repo( graph: g, } - err = l.Update([]string{"third_party/go"}, false) + err = l.UpdateToStdout("text", []string{"third_party/go"}) require.NoError(t, err) testify := edit.FindTargetByName(thirdPartFile, "testify") diff --git a/migrate/migrate.go b/migrate/migrate.go index c135978..2f85a4a 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -30,9 +30,9 @@ type migrator struct { licences *licences.Licenses } -func Migrate(conf *config.Config, plzConf *please.Config, write, updateGoMod bool, modules, paths []string) error { +func newMigrator(plzConf *please.Config, conf *config.Config) *migrator { g := graph.New(plzConf.BuildFileNames()) - m := &migrator{ + return &migrator{ plzConf: plzConf, graph: g, thirdPartyFolder: conf.GetThirdPartyDir(), @@ -40,8 +40,22 @@ func Migrate(conf *config.Config, plzConf *please.Config, write, updateGoMod boo licences: licences.New(proxy.New(proxy.DefaultURL), g), existingRepoRules: map[string]*build.Rule{}, } +} + +func Migrate(conf *config.Config, plzConf *please.Config, updateGoMod bool, modules, paths []string) error { + m := newMigrator(plzConf, conf) + if err := m.migrate(modules, paths, updateGoMod); err != nil { + return err + } + return m.graph.FormatFiles() +} - return m.migrate(modules, paths, write, updateGoMod) +func MigrateToStdout(format string, conf *config.Config, plzConf *please.Config, updateGoMod bool, modules, paths []string) error { //nolint + m := newMigrator(plzConf, conf) + if err := m.migrate(modules, paths, updateGoMod); err != nil { + return err + } + return m.graph.FormatFilesWithWriter(os.Stdout, format) } // pkgRule represents the rule expr in a pkg @@ -125,7 +139,7 @@ func binaryAlias(module, thirdPartyDir string, part *pkgRule) (*build.Rule, erro return rule, nil } -func (m *migrator) migrate(modules, paths []string, write, updateGoMod bool) error { +func (m *migrator) migrate(modules, paths []string, updateGoMod bool) error { // Read all the BUILD files under the provided paths to find go_module and go_mod_download rules for _, path := range paths { f, err := m.graph.LoadFile(path) @@ -144,7 +158,7 @@ func (m *migrator) migrate(modules, paths []string, write, updateGoMod bool) err if err := m.replaceRulesForModules(updateGoMod, modules); err != nil { return err } - return m.graph.FormatFiles(write, os.Stdout) + return nil } // replaceRulesForModules takes a list of modules and replaces those modules and their dependencies diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index 5c39938..5f05171 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -31,7 +31,7 @@ go_module( } m.graph.SetFile("third_party/go", thirdPartyFile) - err = m.migrate(nil, []string{"third_party/go"}, false, false) + err = m.migrate(nil, []string{"third_party/go"}, false) require.NoError(t, err) rule := edit.FindTargetByName(thirdPartyFile, "test") @@ -97,7 +97,7 @@ go_module( } m.graph.SetFile("third_party/go", thirdPartyFile) - err = m.migrate(nil, []string{"third_party/go"}, false, false) + err = m.migrate(nil, []string{"third_party/go"}, false) require.NoError(t, err) repoRules := thirdPartyFile.Rules("go_repo") @@ -150,7 +150,7 @@ go_module( } m.graph.SetFile("third_party/go", thirdPartyFile) - err = m.migrate(nil, []string{"third_party/go"}, false, false) + err = m.migrate(nil, []string{"third_party/go"}, false) require.NoError(t, err) repoRule := edit.FindTargetByName(thirdPartyFile, "test") @@ -189,7 +189,7 @@ go_module( } m.graph.SetFile("third_party/go", thirdPartyFile) - err = m.migrate(nil, []string{"third_party/go", "third_party/go/kubernetes"}, false, false) + err = m.migrate(nil, []string{"third_party/go", "third_party/go/kubernetes"}, false) require.NoError(t, err) repoRule := edit.FindTargetByName(thirdPartyFile, "k8s.io_api") @@ -238,7 +238,7 @@ go_module( m.graph.SetFile("third_party/go", thirdPartyFile) - err = m.migrate([]string{"k8s.io/api"}, []string{"third_party/go"}, false, false) + err = m.migrate([]string{"k8s.io/api"}, []string{"third_party/go"}, false) require.NoError(t, err) apiRule := edit.FindTargetByName(thirdPartyFile, "api") diff --git a/sync/integration/syncmod/sync_mod_test.go b/sync/integration/syncmod/sync_mod_test.go index 1019b81..3f4983f 100644 --- a/sync/integration/syncmod/sync_mod_test.go +++ b/sync/integration/syncmod/sync_mod_test.go @@ -29,7 +29,7 @@ func TestModSync(t *testing.T) { require.NoError(t, err) g := graph.New(plzConf.BuildFileNames()) - err = sync.Sync(plzConf, g, false) + err = sync.SyncToStdout("text", plzConf, g) require.NoError(t, err) thirdPartyBuildFile, err := g.LoadFile(conf.GetThirdPartyDir()) diff --git a/sync/sync.go b/sync/sync.go index 764e3d9..c3d4126 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -22,22 +22,37 @@ type syncer struct { licences *licences.Licenses } -// Sync constructs the syncer struct and initiates the sync. -// NB. the Graph is to be constructed in the calling code because it's useful -// for it to be available outside the package for testing. -func Sync(plzConf *please.Config, g *graph.Graph, write bool) error { +func newSyncer(plzConf *please.Config, g *graph.Graph) *syncer { p := proxy.New(proxy.DefaultURL) l := licences.New(p, g) - s := &syncer{ + return &syncer{ plzConf: plzConf, graph: g, licences: l, } +} - return s.sync(write) +// Sync constructs the syncer struct and initiates the sync. +// NB. the Graph is to be constructed in the calling code because it's useful +// for it to be available outside the package for testing. +func Sync(plzConf *please.Config, g *graph.Graph) error { + s := newSyncer(plzConf, g) + if err := s.sync(); err != nil { + return err + } + return s.graph.FormatFiles() } -func (s *syncer) sync(write bool) error { +// SyncToStdout constructs the syncer and outputs the synced build file to stdout. +func SyncToStdout(format string, plzConf *please.Config, g *graph.Graph) error { //nolint + s := newSyncer(plzConf, g) + if err := s.sync(); err != nil { + return err + } + return s.graph.FormatFilesWithWriter(os.Stdout, format) +} + +func (s *syncer) sync() error { if s.plzConf.ModFile() == "" { return nil } @@ -60,8 +75,7 @@ func (s *syncer) sync(write bool) error { if err := s.syncModFile(conf, file, existingRules); err != nil { return err } - - return s.graph.FormatFiles(write, os.Stdout) + return nil } func (s *syncer) syncModFile(conf *config.Config, file *build.File, exitingRules map[string]*build.Rule) error { diff --git a/watch/watch.go b/watch/watch.go index 8cfc412..3aa182b 100644 --- a/watch/watch.go +++ b/watch/watch.go @@ -52,7 +52,7 @@ func (d *debouncer) wait() { for p := range d.paths { paths = append(paths, p) } - if err := generate.Update(true, d.config, paths...); err != nil { + if err := generate.Update(d.config, paths...); err != nil { log.Warningf("failed to update: %v", err) } else { log.Infof("Updated paths: %v ", strings.Join(paths, ", "))