From 107e91e959426ea88638112be3a307192aab61ea Mon Sep 17 00:00:00 2001 From: skiffer-git <44203734@qq.com> Date: Tue, 22 Apr 2025 14:48:28 +0800 Subject: [PATCH] update --- mage_output_file.go | 498 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 mage_output_file.go diff --git a/mage_output_file.go b/mage_output_file.go new file mode 100644 index 000000000..4fef9e262 --- /dev/null +++ b/mage_output_file.go @@ -0,0 +1,498 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "context" + _flag "flag" + _fmt "fmt" + _ioutil "io/ioutil" + _log "log" + "os" + "os/signal" + _filepath "path/filepath" + _sort "sort" + "strconv" + _strings "strings" + "syscall" + _tabwriter "text/tabwriter" + "time" +) + +func main() { + // Use local types and functions in order to avoid name conflicts with additional magefiles. + type arguments struct { + Verbose bool // print out log statements + List bool // print out a list of targets + Help bool // print out help for a specific target + Timeout time.Duration // set a timeout to running the targets + Args []string // args contain the non-flag command-line arguments + } + + parseBool := func(env string) bool { + val := os.Getenv(env) + if val == "" { + return false + } + b, err := strconv.ParseBool(val) + if err != nil { + _log.Printf("warning: environment variable %s is not a valid bool value: %v", env, val) + return false + } + return b + } + + parseDuration := func(env string) time.Duration { + val := os.Getenv(env) + if val == "" { + return 0 + } + d, err := time.ParseDuration(val) + if err != nil { + _log.Printf("warning: environment variable %s is not a valid duration value: %v", env, val) + return 0 + } + return d + } + args := arguments{} + fs := _flag.FlagSet{} + fs.SetOutput(os.Stdout) + + // default flag set with ExitOnError and auto generated PrintDefaults should be sufficient + fs.BoolVar(&args.Verbose, "v", parseBool("MAGEFILE_VERBOSE"), "show verbose output when running targets") + fs.BoolVar(&args.List, "l", parseBool("MAGEFILE_LIST"), "list targets for this binary") + fs.BoolVar(&args.Help, "h", parseBool("MAGEFILE_HELP"), "print out help for a specific target") + fs.DurationVar(&args.Timeout, "t", parseDuration("MAGEFILE_TIMEOUT"), "timeout in duration parsable format (e.g. 5m30s)") + fs.Usage = func() { + _fmt.Fprintf(os.Stdout, ` +%s [options] [target] + +Commands: + -l list targets in this binary + -h show this help + +Options: + -h show description of a target + -t + timeout in duration parsable format (e.g. 5m30s) + -v show verbose output when running targets + `[1:], _filepath.Base(os.Args[0])) + } + if err := fs.Parse(os.Args[1:]); err != nil { + // flag will have printed out an error already. + return + } + args.Args = fs.Args() + if args.Help && len(args.Args) == 0 { + fs.Usage() + return + } + + // color is ANSI color type + type color int + + // If you add/change/remove any items in this constant, + // you will need to run "stringer -type=color" in this directory again. + // NOTE: Please keep the list in an alphabetical order. + const ( + black color = iota + red + green + yellow + blue + magenta + cyan + white + brightblack + brightred + brightgreen + brightyellow + brightblue + brightmagenta + brightcyan + brightwhite + ) + + // AnsiColor are ANSI color codes for supported terminal colors. + var ansiColor = map[color]string{ + black: "\u001b[30m", + red: "\u001b[31m", + green: "\u001b[32m", + yellow: "\u001b[33m", + blue: "\u001b[34m", + magenta: "\u001b[35m", + cyan: "\u001b[36m", + white: "\u001b[37m", + brightblack: "\u001b[30;1m", + brightred: "\u001b[31;1m", + brightgreen: "\u001b[32;1m", + brightyellow: "\u001b[33;1m", + brightblue: "\u001b[34;1m", + brightmagenta: "\u001b[35;1m", + brightcyan: "\u001b[36;1m", + brightwhite: "\u001b[37;1m", + } + + const _color_name = "blackredgreenyellowbluemagentacyanwhitebrightblackbrightredbrightgreenbrightyellowbrightbluebrightmagentabrightcyanbrightwhite" + + var _color_index = [...]uint8{0, 5, 8, 13, 19, 23, 30, 34, 39, 50, 59, 70, 82, 92, 105, 115, 126} + + colorToLowerString := func(i color) string { + if i < 0 || i >= color(len(_color_index)-1) { + return "color(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _color_name[_color_index[i]:_color_index[i+1]] + } + + // ansiColorReset is an ANSI color code to reset the terminal color. + const ansiColorReset = "\033[0m" + + // defaultTargetAnsiColor is a default ANSI color for colorizing targets. + // It is set to Cyan as an arbitrary color, because it has a neutral meaning + var defaultTargetAnsiColor = ansiColor[cyan] + + getAnsiColor := func(color string) (string, bool) { + colorLower := _strings.ToLower(color) + for k, v := range ansiColor { + colorConstLower := colorToLowerString(k) + if colorConstLower == colorLower { + return v, true + } + } + return "", false + } + + // Terminals which don't support color: + // TERM=vt100 + // TERM=cygwin + // TERM=xterm-mono + var noColorTerms = map[string]bool{ + "vt100": false, + "cygwin": false, + "xterm-mono": false, + } + + // terminalSupportsColor checks if the current console supports color output + // + // Supported: + // linux, mac, or windows's ConEmu, Cmder, putty, git-bash.exe, pwsh.exe + // Not supported: + // windows cmd.exe, powerShell.exe + terminalSupportsColor := func() bool { + envTerm := os.Getenv("TERM") + if _, ok := noColorTerms[envTerm]; ok { + return false + } + return true + } + + // enableColor reports whether the user has requested to enable a color output. + enableColor := func() bool { + b, _ := strconv.ParseBool(os.Getenv("MAGEFILE_ENABLE_COLOR")) + return b + } + + // targetColor returns the ANSI color which should be used to colorize targets. + targetColor := func() string { + s, exists := os.LookupEnv("MAGEFILE_TARGET_COLOR") + if exists == true { + if c, ok := getAnsiColor(s); ok == true { + return c + } + } + return defaultTargetAnsiColor + } + + // store the color terminal variables, so that the detection isn't repeated for each target + var enableColorValue = enableColor() && terminalSupportsColor() + var targetColorValue = targetColor() + + printName := func(str string) string { + if enableColorValue { + return _fmt.Sprintf("%s%s%s", targetColorValue, str, ansiColorReset) + } else { + return str + } + } + + list := func() error { + + targets := map[string]string{ + "build*": "", + "check": "", + "start": "", + "stop": "", + } + + keys := make([]string, 0, len(targets)) + for name := range targets { + keys = append(keys, name) + } + _sort.Strings(keys) + + _fmt.Println("Targets:") + w := _tabwriter.NewWriter(os.Stdout, 0, 4, 4, ' ', 0) + for _, name := range keys { + _fmt.Fprintf(w, " %v\t%v\n", printName(name), targets[name]) + } + err := w.Flush() + if err == nil { + _fmt.Println("\n* default target") + } + return err + } + + var ctx context.Context + ctxCancel := func() {} + + // by deferring in a closure, we let the cancel function get replaced + // by the getContext function. + defer func() { + ctxCancel() + }() + + getContext := func() (context.Context, func()) { + if ctx == nil { + if args.Timeout != 0 { + ctx, ctxCancel = context.WithTimeout(context.Background(), args.Timeout) + } else { + ctx, ctxCancel = context.WithCancel(context.Background()) + } + } + + return ctx, ctxCancel + } + + runTarget := func(logger *_log.Logger, fn func(context.Context) error) interface{} { + var err interface{} + ctx, cancel := getContext() + d := make(chan interface{}) + go func() { + defer func() { + err := recover() + d <- err + }() + err := fn(ctx) + d <- err + }() + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT) + select { + case <-sigCh: + logger.Println("cancelling mage targets, waiting up to 5 seconds for cleanup...") + cancel() + cleanupCh := time.After(5 * time.Second) + + select { + // target exited by itself + case err = <-d: + return err + // cleanup timeout exceeded + case <-cleanupCh: + return _fmt.Errorf("cleanup timeout exceeded") + // second SIGINT received + case <-sigCh: + logger.Println("exiting mage") + return _fmt.Errorf("exit forced") + } + case <-ctx.Done(): + cancel() + e := ctx.Err() + _fmt.Printf("ctx err: %v\n", e) + return e + case err = <-d: + // we intentionally don't cancel the context here, because + // the next target will need to run with the same context. + return err + } + } + // This is necessary in case there aren't any targets, to avoid an unused + // variable error. + _ = runTarget + + handleError := func(logger *_log.Logger, err interface{}) { + if err != nil { + logger.Printf("Error: %+v\n", err) + type code interface { + ExitStatus() int + } + if c, ok := err.(code); ok { + os.Exit(c.ExitStatus()) + } + os.Exit(1) + } + } + _ = handleError + + // Set MAGEFILE_VERBOSE so mg.Verbose() reflects the flag value. + if args.Verbose { + os.Setenv("MAGEFILE_VERBOSE", "1") + } else { + os.Setenv("MAGEFILE_VERBOSE", "0") + } + + _log.SetFlags(0) + if !args.Verbose { + _log.SetOutput(_ioutil.Discard) + } + logger := _log.New(os.Stderr, "", 0) + if args.List { + if err := list(); err != nil { + _log.Println(err) + os.Exit(1) + } + return + } + + if args.Help { + if len(args.Args) < 1 { + logger.Println("no target specified") + os.Exit(2) + } + switch _strings.ToLower(args.Args[0]) { + case "build": + + _fmt.Print("Usage:\n\n\tmage build\n\n") + var aliases []string + if len(aliases) > 0 { + _fmt.Printf("Aliases: %s\n\n", _strings.Join(aliases, ", ")) + } + return + case "check": + + _fmt.Print("Usage:\n\n\tmage check\n\n") + var aliases []string + if len(aliases) > 0 { + _fmt.Printf("Aliases: %s\n\n", _strings.Join(aliases, ", ")) + } + return + case "start": + + _fmt.Print("Usage:\n\n\tmage start\n\n") + var aliases []string + if len(aliases) > 0 { + _fmt.Printf("Aliases: %s\n\n", _strings.Join(aliases, ", ")) + } + return + case "stop": + + _fmt.Print("Usage:\n\n\tmage stop\n\n") + var aliases []string + if len(aliases) > 0 { + _fmt.Printf("Aliases: %s\n\n", _strings.Join(aliases, ", ")) + } + return + default: + logger.Printf("Unknown target: %q\n", args.Args[0]) + os.Exit(2) + } + } + if len(args.Args) < 1 { + ignoreDefault, _ := strconv.ParseBool(os.Getenv("MAGEFILE_IGNOREDEFAULT")) + if ignoreDefault { + if err := list(); err != nil { + logger.Println("Error:", err) + os.Exit(1) + } + return + } + + wrapFn := func(ctx context.Context) error { + Build() + return nil + } + ret := runTarget(logger, wrapFn) + handleError(logger, ret) + return + } + for x := 0; x < len(args.Args); { + target := args.Args[x] + x++ + + // resolve aliases + switch _strings.ToLower(target) { + + } + + switch _strings.ToLower(target) { + + case "build": + expected := x + 0 + if expected > len(args.Args) { + // note that expected and args at this point include the arg for the target itself + // so we subtract 1 here to show the number of args without the target. + logger.Printf("not enough arguments for target \"Build\", expected %v, got %v\n", expected-1, len(args.Args)-1) + os.Exit(2) + } + if args.Verbose { + logger.Println("Running target:", "Build") + } + + wrapFn := func(ctx context.Context) error { + Build() + return nil + } + ret := runTarget(logger, wrapFn) + handleError(logger, ret) + case "check": + expected := x + 0 + if expected > len(args.Args) { + // note that expected and args at this point include the arg for the target itself + // so we subtract 1 here to show the number of args without the target. + logger.Printf("not enough arguments for target \"Check\", expected %v, got %v\n", expected-1, len(args.Args)-1) + os.Exit(2) + } + if args.Verbose { + logger.Println("Running target:", "Check") + } + + wrapFn := func(ctx context.Context) error { + Check() + return nil + } + ret := runTarget(logger, wrapFn) + handleError(logger, ret) + case "start": + expected := x + 0 + if expected > len(args.Args) { + // note that expected and args at this point include the arg for the target itself + // so we subtract 1 here to show the number of args without the target. + logger.Printf("not enough arguments for target \"Start\", expected %v, got %v\n", expected-1, len(args.Args)-1) + os.Exit(2) + } + if args.Verbose { + logger.Println("Running target:", "Start") + } + + wrapFn := func(ctx context.Context) error { + Start() + return nil + } + ret := runTarget(logger, wrapFn) + handleError(logger, ret) + case "stop": + expected := x + 0 + if expected > len(args.Args) { + // note that expected and args at this point include the arg for the target itself + // so we subtract 1 here to show the number of args without the target. + logger.Printf("not enough arguments for target \"Stop\", expected %v, got %v\n", expected-1, len(args.Args)-1) + os.Exit(2) + } + if args.Verbose { + logger.Println("Running target:", "Stop") + } + + wrapFn := func(ctx context.Context) error { + Stop() + return nil + } + ret := runTarget(logger, wrapFn) + handleError(logger, ret) + + default: + logger.Printf("Unknown target specified: %q\n", target) + os.Exit(2) + } + } +}