From 3444493cf94a182f2c8bd5901cdad8cb7fed532e Mon Sep 17 00:00:00 2001 From: Viktor Voltaire Date: Mon, 10 Jan 2022 15:17:38 +0100 Subject: [PATCH] feat: make mage-tools easy to understand --- .mage/magefile.go | 11 + .mage/mgmain_gen.go | 12 -- .mage/tools.mk | 51 +---- Makefile | 38 +++- README.md | 43 ++-- example/.mage/magefile.go | 11 + example/.mage/mgmake_gen.go | 8 + example/.mage/tools.mk | 51 +---- example/Makefile | 4 +- main.go | 91 ++++---- mgmake/generate.go | 200 +++++++++++++----- mgpath/path.go | 52 +++++ mgtool/build.go | 3 +- mgtool/configuration.go | 45 ---- mgtool/exec.go | 4 +- targets/mgbuf/targets.go | 3 +- targets/mgcocogitto/targets.go | 3 +- targets/mgcommitlint/targets.go | 6 +- targets/mgconvco/targets.go | 3 +- targets/mggo/targets.go | 4 +- targets/mggolangcilint/targets.go | 7 +- targets/mggooglecloudprotoscrubber/targets.go | 3 +- targets/mggoreview/targets.go | 3 +- targets/mggrpcjava/targets.go | 3 +- targets/mghadolint/targets.go | 3 +- targets/mgko/targets.go | 3 +- targets/mgprettier/targets.go | 12 +- targets/mgprotoc/targets.go | 3 +- targets/mgsemanticrelease/tool.go | 6 +- targets/mgsops/tool.go | 3 +- 30 files changed, 396 insertions(+), 293 deletions(-) delete mode 100644 .mage/mgmain_gen.go create mode 100644 example/.mage/mgmake_gen.go create mode 100644 mgpath/path.go diff --git a/.mage/magefile.go b/.mage/magefile.go index 8ca7a78d..a5a38fb9 100644 --- a/.mage/magefile.go +++ b/.mage/magefile.go @@ -5,6 +5,8 @@ package main import ( "github.com/magefile/mage/mg" + "go.einride.tech/mage-tools/mgmake" + "go.einride.tech/mage-tools/mgpath" // mage:import "go.einride.tech/mage-tools/targets/mgmarkdownfmt" @@ -25,6 +27,15 @@ import ( "go.einride.tech/mage-tools/targets/mggoreview" ) +func init() { + mgmake.GenerateMakefiles( + mgmake.Makefile{ + Path: mgpath.FromGitRoot("Makefile"), + DefaultTarget: All, + }, + ) +} + func All() { mg.Deps( mgcocogitto.CogCheck, diff --git a/.mage/mgmain_gen.go b/.mage/mgmain_gen.go deleted file mode 100644 index 448f4125..00000000 --- a/.mage/mgmain_gen.go +++ /dev/null @@ -1,12 +0,0 @@ -// Code generated by Mage-tools. DO NOT EDIT. -//go:build ignore -// +build ignore - -package main - -import ( - "os" - "github.com/magefile/mage/mage" -) - -func main() { os.Exit(mage.Main()) } diff --git a/.mage/tools.mk b/.mage/tools.mk index 00056cbe..af4f7867 100644 --- a/.mage/tools.mk +++ b/.mage/tools.mk @@ -1,49 +1,10 @@ -mage_folder := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) -mage_generated_path := $(mage_folder)/tools/mgmake -mage_tools_path := $(mage_folder)/tools -mage_targets_file := $(mage_generated_path)/targets.mk -mage := $(mage_generated_path)/local-mage -mgmake := $(mage_folder)/mgmake_gen.go -mgmain := $(mage_folder)/mgmain_gen.go +mage_dir := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +mage_tools_path := $(mage_dir)/tools +mage := $(mage_tools_path)/mgmake/magefile -define mgmake_content -// Code generated by Mage-tools. DO NOT EDIT. -//go:build mage -// +build mage - -package main - -// mage:import -import _ "go.einride.tech/mage-tools/mgmake" -endef - -define mgmain_content -// Code generated by Mage-tools. DO NOT EDIT. -//go:build ignore -// +build ignore - -package main - -import ( - "os" - "github.com/magefile/mage/mage" -) - -func main() { os.Exit(mage.Main()) } -endef - -include $(mage_targets_file) - -$(mage_targets_file): $(mage_folder)/go.mod $(shell find $(mage_folder)/.. -type f -name '*.go') - @git clean -fdx $(mage_generated_path) - @mkdir -p $(mage_generated_path) - $(file > $(mgmake),$(mgmake_content)) - $(file > $(mgmain),$(mgmain_content)) - @cd $(mage_folder) && \ - go mod tidy && \ - go run $(notdir $(mgmain)) -compile $(mage) && \ - $(mage) generateMakefile $(@) +$(mage): $(mage_dir)/go.mod $(shell find $(mage_dir)/.. -type f -name '*.go') + @cd $(mage_dir) && go run ../main.go gen .PHONY: mage-clean mage-clean: - @git clean -fdx $(mage_generated_path) $(mage_tools_path) + @git clean -fdx $(mage_dir) diff --git a/Makefile b/Makefile index 7acf8754..9a786bee 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,37 @@ -.PHONY: all -all: $(mage_targets) +# Code generated by Mage-tools. DO NOT EDIT. + +.DEFAULT_GOAL := all include .mage/tools.mk -include $(mage_targets) + +.PHONY: all +all: $(mage) + @$(mage) all + +.PHONY: cog-check +cog-check: $(mage) + @$(mage) cogCheck + +.PHONY: format-markdown +format-markdown: $(mage) + @$(mage) formatMarkdown + +.PHONY: git-verify-no-diff +git-verify-no-diff: $(mage) + @$(mage) gitVerifyNoDiff + +.PHONY: go-mod-tidy +go-mod-tidy: $(mage) + @$(mage) goModTidy + +.PHONY: go-test +go-test: $(mage) + @$(mage) goTest + +.PHONY: golangci-lint +golangci-lint: $(mage) + @$(mage) golangciLint + +.PHONY: goreview +goreview: $(mage) + @$(mage) goreview diff --git a/README.md b/README.md index 5c33d48e..7f6b4916 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Run `make` Usage ----- -All mage imports, and targets within the magefiles, gets written to a default Makefile which is always imported. It can also generate specific makefiles to only be imported in certain places like in a Makefile that should only have terraform targets. +Mage imports, and targets within the magefiles, can be written to Makefiles, you can generate as many Makefiles as you want, see more at [Makefiles / Mage namespaces](https://github.com/einride/mage-tools#makefiles--mage-namespaces). ### Magefiles @@ -64,15 +64,37 @@ func All() { } ``` -#### Specific makefiles / Mage namespaces +#### Makefiles / Mage namespaces -We utilize mage namespaces to group targets and write them to unique Makefiles. This can be done via import or for local targets. The below would generate a `semantic-release.mk` and a `terraform.mk` +To generate makefiles, an `init` method needs to exist in one of the magefiles where we call the `mgmake.GenerateMakefiles` method. + +```golang +func init() { + mgmake.GenerateMakefiles( + mgmake.Makefile{ + Path: mgpath.FromGitRoot("Makefile"), + DefaultTarget: All, + }, + ) +} +``` + +If another makefile is desired, lets say one that only includes Terraform targets, we utilize the `mg.Namespace` type and just add another `Makefile` to the `GenerateMakefiles` method and specify the namespace, path and default target. ```golang -import ( - // mage:import semantic-release - _ "go.einride.tech/mage-tools/semantic-release" -) + +func init() { + mgmake.GenerateMakefiles( + mgmake.Makefile{ + Path: mgpath.FromGitRoot("Makefile"), + DefaultTarget: All, + }, + mgmake.Makefile{ + Path: mgpath.FromGitRoot("terraform/Makefile"), + Namespace: Terraform{}, + }, + ) +} type Terraform mg.Namespace @@ -84,13 +106,6 @@ func (Terraform) TerraformInitDev() { } ``` -Which we can then import like below, where the `$(mage_terraform)` variable gets generated by the tooling - -```Makefile -include ./.mage/tools.mk -include $(mage_terraform) -``` - #### Dependencies Dependencies can be defined just by specificing the function, or with `mg.F` if the function takes arguments. `Deps` runs in parallel while `Serial` runs serially diff --git a/example/.mage/magefile.go b/example/.mage/magefile.go index ce171fd4..066c71a9 100644 --- a/example/.mage/magefile.go +++ b/example/.mage/magefile.go @@ -5,6 +5,8 @@ package main import ( "github.com/magefile/mage/mg" + "go.einride.tech/mage-tools/mgmake" + "go.einride.tech/mage-tools/mgpath" // mage:import "go.einride.tech/mage-tools/targets/mgcocogitto" @@ -13,6 +15,15 @@ import ( "go.einride.tech/mage-tools/targets/mggitverifynodiff" ) +func init() { + mgmake.GenerateMakefiles( + mgmake.Makefile{ + Path: mgpath.FromGitRoot("Makefile"), + DefaultTarget: All, + }, + ) +} + func All() { mg.Deps( mgcocogitto.CogCheck, diff --git a/example/.mage/mgmake_gen.go b/example/.mage/mgmake_gen.go new file mode 100644 index 00000000..4efa6669 --- /dev/null +++ b/example/.mage/mgmake_gen.go @@ -0,0 +1,8 @@ +// Code generated by Mage-tools. DO NOT EDIT. +//go:build mage +// +build mage + +package main + +// mage:import +import _ "go.einride.tech/mage-tools/mgmake" diff --git a/example/.mage/tools.mk b/example/.mage/tools.mk index aab4de0b..62da3d2b 100644 --- a/example/.mage/tools.mk +++ b/example/.mage/tools.mk @@ -1,49 +1,10 @@ -mage_folder := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) -mage_generated_path := $(mage_folder)/tools/mgmake -mage_tools_path := $(mage_folder)/tools -mage_targets_file := $(mage_generated_path)/targets.mk -mage := $(mage_generated_path)/local-mage -mgmake := $(mage_folder)/mgmake_gen.go -mgmain := $(mage_folder)/mgmain_gen.go +mage_dir := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +mage_tools_path := $(mage_dir)/tools +mage := $(mage_tools_path)/mgmake/magefile -define mgmake_content -// Code generated by Mage-tools. DO NOT EDIT. -//go:build mage -// +build mage - -package main - -// mage:import -import _ "go.einride.tech/mage-tools/mgmake" -endef - -define mgmain_content -// Code generated by Mage-tools. DO NOT EDIT. -//go:build ignore -// +build ignore - -package main - -import ( - "os" - "github.com/magefile/mage/mage" -) - -func main() { os.Exit(mage.Main()) } -endef - -include $(mage_targets_file) - -$(mage_targets_file): $(mage_folder)/go.mod $(mage_folder)/*.go - @git clean -fdx $(mage_generated_path) - @mkdir -p $(mage_generated_path) - $(file > $(mgmake),$(mgmake_content)) - $(file > $(mgmain),$(mgmain_content)) - @cd $(mage_folder) && \ - go mod tidy && \ - go run $(notdir $(mgmain)) -compile $(mage) && \ - $(mage) generateMakefile $(@) +$(mage): $(mage_dir)/go.mod $(mage_dir)/*.go + @cd $(mage_dir) && go run go.einride.tech/mage-tools gen .PHONY: mage-clean mage-clean: - @git clean -fdx $(mage_generated_path) $(mage_tools_path) + @git clean -fdx $(mage_dir) diff --git a/example/Makefile b/example/Makefile index 7acf8754..9204bf6f 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,5 +1,3 @@ -.PHONY: all -all: $(mage_targets) +# Code generated by Mage-tools. DO NOT EDIT. include .mage/tools.mk -include $(mage_targets) diff --git a/main.go b/main.go index d4415db4..9a008c4a 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,13 @@ package main import ( _ "embed" "fmt" + "log" "os" "path/filepath" + "github.com/magefile/mage/mage" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -17,11 +20,15 @@ var ( magefile string //go:embed example/Makefile makefile string + //go:embed example/.mage/mgmake_gen.go + mgmake string + // nolint: gochecknoglobals + mageDir = mgpath.FromGitRoot(mgpath.MageDir) ) func main() { + logger := mglog.Logger("mage-tools") usage := func() { - logger := mglog.Logger("main") logger.Info(`Usage: init to initialize mage-tools`) os.Exit(0) @@ -29,78 +36,86 @@ func main() { if len(os.Args) <= 1 { usage() } - switch os.Args[1] { case "init": - initMageTools() + if err := initMageTools(); err != nil { + log.Fatalf(err.Error()) + } + case "gen": + if err := gen(); err != nil { + log.Fatalf(err.Error()) + } default: usage() } } -func initMageTools() { +func gen() error { + mglog.Logger("gen").Info("generating makefiles...") + executable := filepath.Join(mgpath.Tools(), "mgmake", "magefile") + if err := mgtool.RunInDir("git", mageDir, "clean", "-fdx", filepath.Dir(executable)); err != nil { + return err + } + if err := os.WriteFile(filepath.Join(mageDir, mgpath.MakeGenGo), []byte(mgmake), 0o600); err != nil { + return err + } + if err := mgtool.RunInDir("go", mageDir, "mod", "tidy"); err != nil { + return err + } + if exit := mage.ParseAndRun(os.Stdout, os.Stderr, os.Stdin, []string{"-compile", executable}); exit != 0 { + return fmt.Errorf("faild to compile magefile binary") + } + return mgtool.RunInDir(executable, mageDir, mgpath.GenMakefilesTarget, executable) +} + +func initMageTools() error { logger := mglog.Logger("init") logger.Info("initializing mage-tools...") - defer func() { - if err := recover(); err != nil { - logger.Error(err.(error), err.(error).Error()) - } - }() - if mgtool.GetCWDPath("") != mgtool.GetGitRootPath("") { - panic(fmt.Errorf("can only be generated in git root directory")) + if mgpath.FromWorkDir(".") != mgpath.FromGitRoot(".") { + return fmt.Errorf("can only be generated in git root directory") } - mageDir := filepath.Join(mgtool.GetGitRootPath(""), ".mage") - err := os.Mkdir(mageDir, 0o755) - if err != nil { - panic(err) + if err := os.Mkdir(mageDir, 0o755); err != nil { + return err } - // Write tools.mk - err = os.WriteFile(filepath.Join(mageDir, "tools.mk"), []byte(toolsMk), 0o600) - if err != nil { - panic(err) + if err := os.WriteFile(filepath.Join(mageDir, mgpath.ToolsMk), []byte(toolsMk), 0o600); err != nil { + return err } - // Write magefile.go - err = os.WriteFile(filepath.Join(mageDir, "magefile.go"), []byte(magefile), 0o600) - if err != nil { - panic(err) + if err := os.WriteFile(filepath.Join(mageDir, "magefile.go"), []byte(magefile), 0o600); err != nil { + return err } - _, err = os.Stat("Makefile") + _, err := os.Stat("Makefile") if err != nil { // Write Makefile - err = os.WriteFile("Makefile", []byte(makefile), 0o600) - if err != nil { - panic(err) + if err := os.WriteFile("Makefile", []byte(makefile), 0o600); err != nil { + return err } } else { const mm = "Makefile.MAGE" logger.Info(fmt.Sprintf("Makefile already exist, writing to %s", mm)) - err = os.WriteFile(mm, []byte(makefile), 0o600) - if err != nil { - panic(err) + if err := os.WriteFile(mm, []byte(makefile), 0o600); err != nil { + return err } } if err := mgtool.RunInDir("go", mageDir, []string{"mod", "init", "mage-tools"}...); err != nil { - panic(err) - } - if err != nil { - panic(err) + return err } if err := mgtool.RunInDir("go", mageDir, []string{"mod", "tidy"}...); err != nil { - panic(err) + return err } gitIgnore, err := os.OpenFile(".gitignore", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) if err != nil { - panic(err) + return err } defer gitIgnore.Close() - if _, err := gitIgnore.WriteString(".mage/tools/"); err != nil { - panic(err) + if _, err := gitIgnore.WriteString(mgpath.Tools()); err != nil { + return err } // TODO: Output some documentation, next steps after init, and useful links. logger.Info("mage-tools initialized!") + return nil } diff --git a/mgmake/generate.go b/mgmake/generate.go index 8b99f05e..2a848f1f 100644 --- a/mgmake/generate.go +++ b/mgmake/generate.go @@ -1,94 +1,183 @@ package mgmake import ( + "bytes" "fmt" "os" "path/filepath" + "reflect" + "runtime" + "sort" "strings" "text/template" + "unicode" "github.com/iancoleman/strcase" - "github.com/magefile/mage/sh" - "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" + "go.einride.tech/mage-tools/mgtool" ) -type templateTargets struct { +const defaultNamespace = "default" + +// nolint: gochecknoglobals +var ( + executable string + makefiles = make(map[string]makefile) +) + +type Makefile struct { + Namespace interface{} + Path string + DefaultTarget interface{} +} + +type makefile struct { + Path string + DefaultTarget string +} + +type templateTarget struct { MakeTarget string MageTarget string Args []string } -// GenerateMakefile is a mage target that ... -func GenerateMakefile(makefile string) error { - const defaultTargets = "mage_targets" - genDir := filepath.Dir(makefile) - mgDefaultTargets := fmt.Sprintf("%s.mk", filepath.Join(genDir, strcase.ToKebab(defaultTargets))) - if makefile == mgDefaultTargets { - return fmt.Errorf("%s has the same name as the default %s makefile", makefile, mgDefaultTargets) +func GenerateMakefiles(mks ...Makefile) { + for _, i := range mks { + if i.Path == "" { + panic("Path needs to be defined") + } + namespace := defaultNamespace + if i.Namespace != nil { + namespace = reflect.TypeOf(i.Namespace).Name() + } + var defaultTarget string + if i.DefaultTarget != nil { + defaultTarget = runtime.FuncForPC(reflect.ValueOf(i.DefaultTarget).Pointer()).Name() + defaultTarget = strings.TrimPrefix(defaultTarget, "main.") + defaultTarget = strings.TrimPrefix(defaultTarget, namespace+".") + defaultTarget = strings.Split(defaultTarget, "-")[0] + for _, r := range defaultTarget { + if !unicode.IsLetter(r) { + panic(fmt.Sprintf("Invalid default target %s", defaultTarget)) + } + } + } + makefiles[toMakeTarget(namespace)] = makefile{Path: i.Path, DefaultTarget: defaultTarget} + } +} + +func GenMakefiles(exec string) error { + if len(makefiles) == 0 { + return fmt.Errorf("no makefiles to generate, see https://github.com/einride/mage-tools#readme for more info") } - mglog.Logger("generate-makefile").Info("generating Makefile...") + executable = exec targets, err := listTargets() if err != nil { return err } + buffers, err := generateMakeTargets(targets) + if err != nil { + return err + } - // Create map which holds variables for each makefile being generated - mgMakefiles := make(map[string]string) + namespaces := make([]string, 0, len(makefiles)) + for k := range makefiles { + namespaces = append(namespaces, k) + } + sort.Strings(namespaces) - for _, target := range targets { - var f *os.File - args, _ := getTargetArguments(target) - if strings.Contains(target, ":") { - // Create unique makefile if target is namespaced - name := "mage_" + strcase.ToSnake(strings.Split(target, ":")[0]) - filename := fmt.Sprintf("%s.mk", filepath.Join(genDir, strcase.ToKebab(name))) - f, err = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) - if err != nil { - return err - } - if _, ok := mgMakefiles[name]; !ok { - mgMakefiles[name] = filename + // Add target for non-root makefile to default makefile + for _, ns := range namespaces { + if ns != defaultNamespace { + mk := makefiles[ns] + if defaultBuf, ok := buffers[defaultNamespace]; ok { + if strings.Contains(defaultBuf.String(), fmt.Sprintf(".PHONY: %s", ns)) { + return fmt.Errorf("can't create target for makefile, %s already exist", ns) + } + mkPath, err := filepath.Rel(mgpath.FromGitRoot("."), filepath.Dir(mk.Path)) + if err != nil { + return err + } + mkTarget := []byte(fmt.Sprintf(`.PHONY: %s +%s: + make -C %s + +`, ns, ns, mkPath)) + buffers[defaultNamespace] = bytes.NewBuffer(append(defaultBuf.Bytes(), mkTarget...)) } - } else { - f, err = os.OpenFile(mgDefaultTargets, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) - if err != nil { + } + } + // Write non-root makefiles + for _, ns := range namespaces { + if buf, ok := buffers[ns]; ok { + mk := makefiles[ns] + if err := createMakefile(mk.Path, mk.DefaultTarget, buf.Bytes()); err != nil { return err } - if _, ok := mgMakefiles[defaultTargets]; !ok { - mgMakefiles[defaultTargets] = mgDefaultTargets - } } - templateTarget := templateTargets{ + } + return nil +} + +func createMakefile(makefilePath, target string, data []byte) error { + includePath, err := filepath.Rel(filepath.Dir(makefilePath), mgpath.FromWorkDir(".")) + if err != nil { + return err + } + if target != "" { + target = fmt.Sprintf("\n\n.DEFAULT_GOAL := %s", toMakeTarget(target)) + } + codegen := []byte(fmt.Sprintf(`# Code generated by Mage-tools. DO NOT EDIT.%s + +include %s/%s + +`, target, includePath, mgpath.ToolsMk)) + // Removes trailing empty line + data = data[:len(data)-1] + err = os.WriteFile(makefilePath, append(codegen, data...), 0o600) + if err != nil { + return err + } + return nil +} + +func generateMakeTargets(targets []string) (map[string]*bytes.Buffer, error) { + buffers := make(map[string]*bytes.Buffer) + for _, target := range targets { + var b *bytes.Buffer + var ns string + if strings.Contains(target, ":") { + ns = toMakeTarget(strings.Split(target, ":")[0]) + } else { + ns = defaultNamespace + } + if _, ok := buffers[ns]; ok { + b = buffers[ns] + } else { + b = bytes.NewBuffer(make([]byte, 0)) + } + args, _ := getTargetArguments(target) + templateTarget := templateTarget{ MakeTarget: toMakeTarget(target), MageTarget: toMageTarget(target, toMakeVars(args)), Args: toMakeVars(args), } - t, _ := template.New("dynamic").Parse(` -.PHONY: {{.MakeTarget}} -{{.MakeTarget}}:{{range .Args}} + t, _ := template.New("dynamic").Parse(`.PHONY: {{.MakeTarget}} +{{.MakeTarget}}: $(mage){{range .Args}} ifndef {{.}} {{"\t"}}$(error missing argument {{.}}="...") endif{{end}} {{"\t"}}@$(mage) {{.MageTarget}} + `) - err = t.Execute(f, templateTarget) + err := t.Execute(b, templateTarget) if err != nil { - return err + return nil, err } + buffers[ns] = b } - err = os.WriteFile(makefile, createMakefileVariablesFromMap(mgMakefiles), 0o600) - if err != nil { - return err - } - return nil -} - -func createMakefileVariablesFromMap(m map[string]string) []byte { - makefileVariables := make([]string, 0, len(m)) - for key, value := range m { - makefileVariables = append(makefileVariables, fmt.Sprintf("%s := %s", key, value)) - } - return []byte(strings.Join(makefileVariables, "\n")) + return buffers, nil } // toMakeVars converts input to make vars. @@ -150,10 +239,9 @@ func listTargets() ([]string, error) { parts[0] = strings.TrimRight(strings.TrimSpace(parts[0]), "*") // Remove this mage target from the output - if strings.Contains(parts[0], "generateMakefile") { + if strings.Contains(parts[0], mgpath.GenMakefilesTarget) { continue } - targets = append(targets, parts[0]) } } @@ -177,11 +265,7 @@ func getTargetArguments(name string) ([]string, error) { } func invokeMage(args []string) (string, error) { - binary, err := os.Executable() - if err != nil { - return "", err - } - out, err := sh.Output(binary, args...) + out, err := mgtool.OutputRunInDir(executable, ".", args...) if err != nil { return "", err } diff --git a/mgpath/path.go b/mgpath/path.go new file mode 100644 index 00000000..7cd738fd --- /dev/null +++ b/mgpath/path.go @@ -0,0 +1,52 @@ +package mgpath + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "strings" +) + +const ( + ToolsMk = "tools.mk" + MakeGenGo = "mgmake_gen.go" + GenMakefilesTarget = "genMakefiles" + MageDir = ".mage" + ToolsDir = MageDir + "/tools" +) + +// nolint: gochecknoglobals +var mgToolsPath = FromGitRoot(ToolsDir) + +func FromWorkDir(path string) string { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + return filepath.Join(cwd, path) +} + +func FromGitRoot(path string) string { + // We use exec.command here because this command runs in a global, + // which is set up before mage has configured logging resulting in unwanted log prints + output := &bytes.Buffer{} + c := exec.Command("git", []string{"rev-parse", "--show-toplevel"}...) + c.Env = os.Environ() + c.Stderr = os.Stderr + c.Stdout = output + c.Stdin = os.Stdin + + if err := c.Run(); err != nil { + panic(err) + } + return filepath.Join(strings.TrimSpace(output.String()), path) +} + +// Tools returns the base to where tools are downloaded and installed. +func Tools() string { + if mgToolsPath == "" { + panic("No tools path set") + } + return mgToolsPath +} diff --git a/mgtool/build.go b/mgtool/build.go index 7cae3b37..9397c389 100644 --- a/mgtool/build.go +++ b/mgtool/build.go @@ -8,10 +8,11 @@ import ( "github.com/go-logr/logr" "github.com/magefile/mage/sh" + "go.einride.tech/mage-tools/mgpath" ) func GoInstall(ctx context.Context, goPkg, version string) (string, error) { - toolDir, err := filepath.Abs(GetPath()) + toolDir, err := filepath.Abs(mgpath.Tools()) if err != nil { return "", err } diff --git a/mgtool/configuration.go b/mgtool/configuration.go index 72718a7f..20b76ae1 100644 --- a/mgtool/configuration.go +++ b/mgtool/configuration.go @@ -1,11 +1,7 @@ package mgtool import ( - "bytes" "fmt" - "os" - "os/exec" - "path/filepath" "strings" ) @@ -14,47 +10,6 @@ const ( X8664 = "x86_64" ) -// Path This should only be used to set a custom value. -// Targets should use path() instead which performs -// validation on whether a path is set. -// nolint: gochecknoglobals -var mgToolPath = GetGitRootPath(".mage/tools") - -func GetCWDPath(path string) string { - cwd, err := os.Getwd() - if err != nil { - panic(err) - } - return filepath.Join(cwd, path) -} - -func GetGitRootPath(path string) string { - // We use exec.command here because this command runs in a global, - // which is set up before mage has configured logging resulting in unwanted log prints - output := &bytes.Buffer{} - c := exec.Command("git", []string{"rev-parse", "--show-toplevel"}...) - c.Env = os.Environ() - c.Stderr = os.Stderr - c.Stdout = output - c.Stdin = os.Stdin - - if err := c.Run(); err != nil { - panic(err) - } - return filepath.Join(strings.TrimSpace(output.String()), path) -} - -func GetPath() string { - if mgToolPath == "" { - panic("No tools path set") - } - return mgToolPath -} - -func SetPath(p string) { - mgToolPath = p -} - func IsSupportedVersion(versions []string, version, name string) error { for _, a := range versions { if a == version { diff --git a/mgtool/exec.go b/mgtool/exec.go index c158558b..127fe3d5 100644 --- a/mgtool/exec.go +++ b/mgtool/exec.go @@ -30,8 +30,8 @@ func OutputRunInDir(cmd, dir string, args ...string) (string, error) { func run(cmd, dir string, stdout, stderr io.Writer, args ...string) error { c := exec.Command(cmd, args...) c.Env = os.Environ() - c.Stderr = stdout - c.Stdout = stderr + c.Stderr = stderr + c.Stdout = stdout c.Stdin = os.Stdin c.Dir = dir diff --git a/targets/mgbuf/targets.go b/targets/mgbuf/targets.go index 45576a66..cde619d8 100644 --- a/targets/mgbuf/targets.go +++ b/targets/mgbuf/targets.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -40,7 +41,7 @@ func Buf(ctx context.Context, args ...string) error { func prepare(ctx context.Context) error { const binaryName = "buf" - binDir := filepath.Join(mgtool.GetPath(), binaryName, version, "bin") + binDir := filepath.Join(mgpath.Tools(), binaryName, version, "bin") binary := filepath.Join(binDir, binaryName) hostOS := runtime.GOOS hostArch := runtime.GOARCH diff --git a/targets/mgcocogitto/targets.go b/targets/mgcocogitto/targets.go index e6f0f085..86638182 100644 --- a/targets/mgcocogitto/targets.go +++ b/targets/mgcocogitto/targets.go @@ -12,6 +12,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -30,7 +31,7 @@ func CogCheck(ctx context.Context) error { func prepare(ctx context.Context) error { const toolName = "cocogitto" - binDir := filepath.Join(mgtool.GetPath(), toolName, version) + binDir := filepath.Join(mgpath.Tools(), toolName, version) toolPath := filepath.Join(binDir, "cog") var archiveName string switch strings.Split(runtime.GOOS, "/")[0] { diff --git a/targets/mgcommitlint/targets.go b/targets/mgcommitlint/targets.go index 2246eab8..56a3722d 100644 --- a/targets/mgcommitlint/targets.go +++ b/targets/mgcommitlint/targets.go @@ -9,7 +9,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" - "go.einride.tech/mage-tools/mgtool" + "go.einride.tech/mage-tools/mgpath" ) const packageJSONContent = `{ @@ -33,7 +33,7 @@ var executable string func Commitlint(ctx context.Context, branch string) error { logger := mglog.Logger("commitlint") ctx = logr.NewContext(ctx, logger) - commitlintrc := filepath.Join(mgtool.GetPath(), "commitlint", ".commitlintrc.js") + commitlintrc := filepath.Join(mgpath.Tools(), "commitlint", ".commitlintrc.js") mg.CtxDeps(ctx, mg.F(prepare, commitlintrc)) args := []string{ "--config", @@ -56,7 +56,7 @@ func prepare(ctx context.Context, commitlintrc string) error { return err } - toolDir := filepath.Join(mgtool.GetPath(), "commitlint") + toolDir := filepath.Join(mgpath.Tools(), "commitlint") binary := filepath.Join(toolDir, "node_modules", ".bin", "commitlint") packageJSON := filepath.Join(toolDir, "package.json") diff --git a/targets/mgconvco/targets.go b/targets/mgconvco/targets.go index 5a9daa61..d1e7bec9 100644 --- a/targets/mgconvco/targets.go +++ b/targets/mgconvco/targets.go @@ -12,6 +12,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -30,7 +31,7 @@ func ConvcoCheck(ctx context.Context, rev string) error { func prepare(ctx context.Context) error { const toolName = "convco" - binDir := filepath.Join(mgtool.GetPath(), toolName, version) + binDir := filepath.Join(mgpath.Tools(), toolName, version) toolPath := filepath.Join(binDir, toolName) var hostOS string switch strings.Split(runtime.GOOS, "/")[0] { diff --git a/targets/mggo/targets.go b/targets/mggo/targets.go index 4898c6b3..cb86581e 100644 --- a/targets/mggo/targets.go +++ b/targets/mggo/targets.go @@ -6,13 +6,13 @@ import ( "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" - "go.einride.tech/mage-tools/mgtool" + "go.einride.tech/mage-tools/mgpath" ) func GoTest() error { const toolName = "go" mglog.Logger("go-test").Info("running Go unit tests..") - coverFile := filepath.Join(mgtool.GetPath(), toolName, "coverage", "go-test.txt") + coverFile := filepath.Join(mgpath.Tools(), toolName, "coverage", "go-test.txt") if err := os.MkdirAll(filepath.Dir(coverFile), 0o755); err != nil { return err } diff --git a/targets/mggolangcilint/targets.go b/targets/mggolangcilint/targets.go index a9d92290..04ade10b 100644 --- a/targets/mggolangcilint/targets.go +++ b/targets/mggolangcilint/targets.go @@ -13,6 +13,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -28,9 +29,9 @@ func GolangciLint(ctx context.Context) error { ctx = logr.NewContext(ctx, mglog.Logger("golangci-lint")) mg.CtxDeps(ctx, mg.F(prepare)) logr.FromContextOrDiscard(ctx).Info("running...") - configPath := mgtool.GetCWDPath(".golangci.yml") + configPath := mgpath.FromWorkDir(".golangci.yml") if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) { - configPath = filepath.Join(mgtool.GetPath(), "golangci-lint", ".golangci.yml") + configPath = filepath.Join(mgpath.Tools(), "golangci-lint", ".golangci.yml") if err := os.WriteFile(configPath, []byte(defaultConfig), 0o600); err != nil { return err } @@ -40,7 +41,7 @@ func GolangciLint(ctx context.Context) error { func prepare(ctx context.Context) error { const binaryName = "golangci-lint" - toolDir := filepath.Join(mgtool.GetPath(), binaryName) + toolDir := filepath.Join(mgpath.Tools(), binaryName) binDir := filepath.Join(toolDir, version, "bin") binary := filepath.Join(binDir, binaryName) hostOS := runtime.GOOS diff --git a/targets/mggooglecloudprotoscrubber/targets.go b/targets/mggooglecloudprotoscrubber/targets.go index 1c19c3d5..3a6f6788 100644 --- a/targets/mggooglecloudprotoscrubber/targets.go +++ b/targets/mggooglecloudprotoscrubber/targets.go @@ -11,6 +11,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -28,7 +29,7 @@ func GoogleCloudProtoScrubber(ctx context.Context, fileDescriptorPath string) er func prepare(ctx context.Context) error { const binaryName = "google-cloud-proto-scrubber" - binDir := filepath.Join(mgtool.GetPath(), binaryName, version) + binDir := filepath.Join(mgpath.Tools(), binaryName, version) binary := filepath.Join(binDir, binaryName) hostOS := runtime.GOOS hostArch := runtime.GOARCH diff --git a/targets/mggoreview/targets.go b/targets/mggoreview/targets.go index 88c973da..dcf6fd78 100644 --- a/targets/mggoreview/targets.go +++ b/targets/mggoreview/targets.go @@ -11,6 +11,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -29,7 +30,7 @@ func Goreview(ctx context.Context) error { func prepare(ctx context.Context) error { const toolName = "goreview" - toolDir := filepath.Join(mgtool.GetPath(), toolName) + toolDir := filepath.Join(mgpath.Tools(), toolName) binDir := filepath.Join(toolDir, version, "bin") binary := filepath.Join(binDir, toolName) hostOS := strings.Title(runtime.GOOS) diff --git a/targets/mggrpcjava/targets.go b/targets/mggrpcjava/targets.go index 03dc61b3..d76f71dd 100644 --- a/targets/mggrpcjava/targets.go +++ b/targets/mggrpcjava/targets.go @@ -12,6 +12,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -30,7 +31,7 @@ func ProtocGenGrpcJava(ctx context.Context) error { func prepare(ctx context.Context) error { const binaryName = "protoc-gen-grpc-java" - binDir := filepath.Join(mgtool.GetPath(), "grpc-java", version, "bin") + binDir := filepath.Join(mgpath.Tools(), "grpc-java", version, "bin") binary := filepath.Join(binDir, binaryName) // read the whole pom at once diff --git a/targets/mghadolint/targets.go b/targets/mghadolint/targets.go index bbf13339..9260697b 100644 --- a/targets/mghadolint/targets.go +++ b/targets/mghadolint/targets.go @@ -13,6 +13,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -40,7 +41,7 @@ func Hadolint(ctx context.Context) error { func prepare(ctx context.Context) error { const binaryName = "hadolint" - toolDir := filepath.Join(mgtool.GetPath(), binaryName) + toolDir := filepath.Join(mgpath.Tools(), binaryName) binDir := filepath.Join(toolDir, version, "bin") binary := filepath.Join(binDir, binaryName) hostOS := runtime.GOOS diff --git a/targets/mgko/targets.go b/targets/mgko/targets.go index b5cea1df..909bf11e 100644 --- a/targets/mgko/targets.go +++ b/targets/mgko/targets.go @@ -11,6 +11,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -84,7 +85,7 @@ func prepare(ctx context.Context) error { hostOS := runtime.GOOS - binDir := filepath.Join(mgtool.GetPath(), binaryName, version, "bin") + binDir := filepath.Join(mgpath.Tools(), binaryName, version, "bin") binary := filepath.Join(binDir, binaryName) binURL := fmt.Sprintf( diff --git a/targets/mgprettier/targets.go b/targets/mgprettier/targets.go index a6ceb152..daf836d4 100644 --- a/targets/mgprettier/targets.go +++ b/targets/mgprettier/targets.go @@ -9,7 +9,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" - "go.einride.tech/mage-tools/mgtool" + "go.einride.tech/mage-tools/mgpath" ) const packageJSONContent = `{ @@ -29,14 +29,14 @@ var executable string func FormatMarkdown(ctx context.Context) error { logger := mglog.Logger("prettier") ctx = logr.NewContext(ctx, logger) - prettierrc := filepath.Join(mgtool.GetPath(), "prettier", ".prettierrc.js") + prettierrc := filepath.Join(mgpath.Tools(), "prettier", ".prettierrc.js") mg.CtxDeps(ctx, mg.F(prepare, prettierrc)) args := []string{ "--config", prettierrc, "--write", "**/*.md", - "!.mage", + "!" + mgpath.MageDir, } logger.Info("formatting Markdown files...") return sh.RunV(executable, args...) @@ -45,14 +45,14 @@ func FormatMarkdown(ctx context.Context) error { func FormatYAML(ctx context.Context) error { logger := mglog.Logger("prettier") ctx = logr.NewContext(ctx, logger) - prettierrc := filepath.Join(mgtool.GetPath(), "prettier", ".prettierrc.js") + prettierrc := filepath.Join(mgpath.Tools(), "prettier", ".prettierrc.js") mg.CtxDeps(ctx, mg.F(prepare, prettierrc)) args := []string{ "--config", prettierrc, "--write", "**/*.y*ml", - "!.mage", + "!" + mgpath.MageDir, } logger.Info("formatting YAML files...") return sh.RunV(executable, args...) @@ -64,7 +64,7 @@ func prepare(ctx context.Context, prettierrc string) error { return err } - toolDir := filepath.Join(mgtool.GetPath(), "prettier") + toolDir := filepath.Join(mgpath.Tools(), "prettier") binary := filepath.Join(toolDir, "node_modules", ".bin", "prettier") packageJSON := filepath.Join(toolDir, "package.json") diff --git a/targets/mgprotoc/targets.go b/targets/mgprotoc/targets.go index 7cf8eb2c..72b21078 100644 --- a/targets/mgprotoc/targets.go +++ b/targets/mgprotoc/targets.go @@ -11,6 +11,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -30,7 +31,7 @@ func Protoc(ctx context.Context, args ...string) error { func prepare(ctx context.Context) error { const binaryName = "protoc" - binDir := filepath.Join(mgtool.GetPath(), binaryName, version) + binDir := filepath.Join(mgpath.Tools(), binaryName, version) binary := filepath.Join(binDir, "bin", binaryName) hostOS := runtime.GOOS diff --git a/targets/mgsemanticrelease/tool.go b/targets/mgsemanticrelease/tool.go index 736652ff..969f60b4 100644 --- a/targets/mgsemanticrelease/tool.go +++ b/targets/mgsemanticrelease/tool.go @@ -10,7 +10,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" - "go.einride.tech/mage-tools/mgtool" + "go.einride.tech/mage-tools/mgpath" ) const packageJSONContent = `{ @@ -29,7 +29,7 @@ func SemanticRelease(ctx context.Context, branch string, ci bool) error { logger := mglog.Logger("semantic-release") ctx = logr.NewContext(ctx, logger) mg.CtxDeps(ctx, mg.F(prepare, branch)) - releaserc := filepath.Join(mgtool.GetPath(), "semantic-release", ".releaserc.json") + releaserc := filepath.Join(mgpath.Tools(), "semantic-release", ".releaserc.json") args := []string{ "--extends", releaserc, @@ -47,7 +47,7 @@ func prepare(ctx context.Context, branch string) error { return err } - toolDir := filepath.Join(mgtool.GetPath(), "semantic-release") + toolDir := filepath.Join(mgpath.Tools(), "semantic-release") binary := filepath.Join(toolDir, "node_modules", ".bin", "semantic-release") releasercJSON := filepath.Join(toolDir, ".releaserc.json") packageJSON := filepath.Join(toolDir, "package.json") diff --git a/targets/mgsops/tool.go b/targets/mgsops/tool.go index 4a6dfb9f..aaa4e95a 100644 --- a/targets/mgsops/tool.go +++ b/targets/mgsops/tool.go @@ -10,6 +10,7 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "go.einride.tech/mage-tools/mglog" + "go.einride.tech/mage-tools/mgpath" "go.einride.tech/mage-tools/mgtool" ) @@ -28,7 +29,7 @@ func Sops(ctx context.Context, file string) error { func sops(ctx context.Context) error { const binaryName = "sops" - binDir := filepath.Join(mgtool.GetPath(), binaryName, version) + binDir := filepath.Join(mgpath.Tools(), binaryName, version) binary := filepath.Join(binDir, binaryName) executable = binary