Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt at extra args #491

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 28 additions & 2 deletions mage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ type Invocation struct {
GoCmd string // the go binary command to run
CacheDir string // the directory where we should store compiled binaries
HashFast bool // don't rely on GOCACHE, just hash the magefiles

// all the arguments that came after --, we do not do any kind of parsing on them as
// they are intended for a subprocess and we know nothing about them
ExtraArgs []string
}

// MagefilesDirName is the name of the default folder to look for if no directory was specified,
Expand Down Expand Up @@ -296,7 +300,23 @@ Options:
return inv, cmd, errors.New("-goos and -goarch only apply when running with -compile")
}

inv.Args = fs.Args()
var extraArgs []string
extraArgsFound := false
for _, arg := range fs.Args() {
// we do not really care about args before this point, this is where extra args begin
if arg == "--" {
extraArgsFound = true
continue
}
// all args before -- are the positional args that go to mage
if !extraArgsFound {
inv.Args = append(inv.Args, arg)
continue
}
// now we parse all that comes after --
extraArgs = append(extraArgs, arg)
}
inv.ExtraArgs = extraArgs
if inv.Help && len(inv.Args) > 1 {
return inv, cmd, errors.New("-h can only show help for a single target")
}
Expand Down Expand Up @@ -704,7 +724,13 @@ func generateInit(dir string) error {
// RunCompiled runs an already-compiled mage command with the given args,
func RunCompiled(inv Invocation, exePath string, errlog *log.Logger) int {
debug.Println("running binary", exePath)
c := exec.Command(exePath, inv.Args...)
args := make([]string, len(inv.Args), len(inv.Args)+len(inv.ExtraArgs)+1)
copy(args, inv.Args)
if len(inv.ExtraArgs) > 0 {
args = append(append(args, "--"), inv.ExtraArgs...)
}

c := exec.Command(exePath, args...)
c.Stderr = inv.Stderr
c.Stdout = inv.Stdout
c.Stdin = inv.Stdin
Expand Down
262 changes: 262 additions & 0 deletions mage/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1877,6 +1877,268 @@ func TestWrongDependency(t *testing.T) {
}
}

func TestExtraArgs(t *testing.T) {
stdout := &bytes.Buffer{}
inv := Invocation{
Dir: "./testdata/extra_args",
Stderr: ioutil.Discard,
Stdout: stdout,
Args: []string{"targetOne"},
ExtraArgs: []string{"--", "-baz", "foo", "bar"},
}

code := Invoke(inv)
if code != 0 {
t.Fatalf("expected 0, but got %v", code)
}
expected := "mg.ExtraArgs{\"-baz\", \"foo\", \"bar\"}\n"
if stdout.String() != expected {
t.Fatalf("expected %q, but got %q", expected, stdout.String())
}
}

func TestExtraArgsParsing(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := ParseAndRun(stderr, stdout, nil,
[]string{"-d", "./testdata/extra_args", "targetOne", "--", "-baz", "foo", "bar"})
if code != 0 {
t.Fatal("unexpected code", code)
}

targetOneExpectedOut := "Running target: TargetOne\n"
if stdout.String() != targetOneExpectedOut {
t.Fatalf("expected output %q, but got %q", targetOneExpectedOut, stdout.String())
}
targetOneExpectedErr := `mg.ExtraArgs{"-baz", "foo", "bar"}
`
if stderr.String() != targetOneExpectedErr {
t.Fatalf("expected stderr output %q, but got %q", targetOneExpectedErr, stderr.String())
}
}

func TestExtraArgsWithContext(t *testing.T) {
stdout := &bytes.Buffer{}
inv := Invocation{
Dir: "./testdata/extra_args",
Stderr: ioutil.Discard,
Stdout: stdout,
Args: []string{"targetTwo"},
ExtraArgs: []string{"--", "-baz", "foo", "bar"},
}

code := Invoke(inv)
if code != 0 {
t.Fatalf("expected 0, but got %v", code)
}
expected := "Context is nil: false\nmg.ExtraArgs{\"-baz\", \"foo\", \"bar\"}\n"
if stdout.String() != expected {
t.Fatalf("expected %q, but got %q", expected, stdout.String())
}
}

func TestExtraArgsWithContextParsing(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := ParseAndRun(stderr, stdout, nil,
[]string{"-d", "./testdata/extra_args", "targetTwo", "--", "-baz", "foo", "bar"})
if code != 0 {
t.Fatal("unexpected code", code)
}

targetOneExpectedOut := "Running target: TargetTwo\n"
if stdout.String() != targetOneExpectedOut {
t.Fatalf("expected output %q, but got %q", targetOneExpectedOut, stdout.String())
}
targetOneExpectedErr := `Context is nil: false
mg.ExtraArgs{"-baz", "foo", "bar"}
`
if stderr.String() != targetOneExpectedErr {
t.Fatalf("expected stderr output %q, but got %q", targetOneExpectedErr, stderr.String())
}
}

func TestExtraArgsWithContextAndString(t *testing.T) {
stdout := &bytes.Buffer{}
inv := Invocation{
Dir: "./testdata/extra_args",
Stderr: ioutil.Discard,
Stdout: stdout,
Args: []string{"targetThree", "stringvalue"},
ExtraArgs: []string{"--", "-baz", "foo", "bar"},
}

code := Invoke(inv)
if code != 0 {
t.Fatalf("expected 0, but got %v", code)
}
expected := "Context is nil: false\nmg.ExtraArgs{\"-baz\", \"foo\", \"bar\"}\nstringvalue\n"
if stdout.String() != expected {
t.Fatalf("expected %q, but got %q", expected, stdout.String())
}
}

func TestExtraArgsWithContextAndStringParsing(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := ParseAndRun(stderr, stdout, nil,
[]string{"-d", "./testdata/extra_args", "targetThree", "stringvalue", "--", "-baz", "foo", "bar"})
if code != 0 {
t.Fatal("unexpected code", code)
}

targetOneExpectedOut := "Running target: TargetThree\n"
if stdout.String() != targetOneExpectedOut {
t.Fatalf("expected output %q, but got %q", targetOneExpectedOut, stdout.String())
}
targetOneExpectedErr := `Context is nil: false
mg.ExtraArgs{"-baz", "foo", "bar"}
stringvalue
`
if stderr.String() != targetOneExpectedErr {
t.Fatalf("expected stderr output %q, but got %q", targetOneExpectedErr, stderr.String())
}
}

func TestExtraArgsWithContextAndStringAndInt(t *testing.T) {
stdout := &bytes.Buffer{}
inv := Invocation{
Dir: "./testdata/extra_args",
Stderr: ioutil.Discard,
Stdout: stdout,
Args: []string{"targetFour", "stringvalue", "2"},
ExtraArgs: []string{"--", "-baz", "foo", "bar"},
}

code := Invoke(inv)
if code != 0 {
t.Fatalf("expected 0, but got %v", code)
}
expected := "Context is nil: false\nmg.ExtraArgs{\"-baz\", \"foo\", \"bar\"}\nstringvalue\n2\n"
if stdout.String() != expected {
t.Fatalf("expected %q, but got %q", expected, stdout.String())
}
}

func TestExtraArgsWithContextAndStringAndIntParsing(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := ParseAndRun(stderr, stdout, nil,
[]string{"-d", "./testdata/extra_args", "targetFour", "stringvalue", "2", "--", "-baz", "foo", "bar"})
if code != 0 {
t.Fatal("unexpected code", code)
}

targetOneExpectedOut := "Running target: TargetFour\n"
if stdout.String() != targetOneExpectedOut {
t.Fatalf("expected output %q, but got %q", targetOneExpectedOut, stdout.String())
}
targetOneExpectedErr := `Context is nil: false
mg.ExtraArgs{"-baz", "foo", "bar"}
stringvalue
2
`
if stderr.String() != targetOneExpectedErr {
t.Fatalf("expected stderr output %q, but got %q", targetOneExpectedErr, stderr.String())
}
}

func TestExtraArgsWithoutContextAndStringAndInt(t *testing.T) {
stdout := &bytes.Buffer{}
inv := Invocation{
Dir: "./testdata/extra_args",
Stderr: ioutil.Discard,
Stdout: stdout,
Args: []string{"targetFive", "stringvalue", "2"},
ExtraArgs: []string{"--", "-baz", "foo", "bar"},
}

code := Invoke(inv)
if code != 0 {
t.Fatalf("expected 0, but got %v", code)
}
expected := "mg.ExtraArgs{\"-baz\", \"foo\", \"bar\"}\nstringvalue\n2\n"
if stdout.String() != expected {
t.Fatalf("expected %q, but got %q", expected, stdout.String())
}
}

func TestExtraArgsWithoutContextAndStringAndIntParsing(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := ParseAndRun(stderr, stdout, nil,
[]string{"-d", "./testdata/extra_args", "targetFive", "stringvalue", "2", "--", "-baz", "foo", "bar"})
if code != 0 {
t.Fatal("unexpected code", code)
}

targetOneExpectedOut := "Running target: TargetFive\n"
if stdout.String() != targetOneExpectedOut {
t.Fatalf("expected output %q, but got %q", targetOneExpectedOut, stdout.String())
}
targetOneExpectedErr := `mg.ExtraArgs{"-baz", "foo", "bar"}
stringvalue
2
`
if stderr.String() != targetOneExpectedErr {
t.Fatalf("expected stderr output %q, but got %q", targetOneExpectedErr, stderr.String())
}
}

func TestExtraArgsWithoutContextAndStringAndIntNonOrdered(t *testing.T) {
stdout := &bytes.Buffer{}
inv := Invocation{
Dir: "./testdata/extra_args",
Stderr: ioutil.Discard,
Stdout: stdout,
Args: []string{"targetSix", "stringvalue", "2"},
ExtraArgs: []string{"--", "-baz", "foo", "bar"},
}

code := Invoke(inv)
if code != 0 {
t.Fatalf("expected 0, but got %v", code)
}
expected := "mg.ExtraArgs{\"-baz\", \"foo\", \"bar\"}\nstringvalue\n2\n"
if stdout.String() != expected {
t.Fatalf("expected %q, but got %q", expected, stdout.String())
}
}

func TestExtraArgsWithoutContextAndStringAndIntNonOrderedParsing(t *testing.T) {
os.Setenv("MAGEFILE_VERBOSE", "1")
defer os.Unsetenv("MAGEFILE_VERBOSE")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := ParseAndRun(stderr, stdout, nil,
[]string{"-d", "./testdata/extra_args", "targetSix", "stringvalue", "2", "--", "-baz", "foo", "bar"})
if code != 0 {
t.Fatal("unexpected code", code)
}

targetOneExpectedOut := "Running target: TargetSix\n"
if stdout.String() != targetOneExpectedOut {
t.Fatalf("expected output %q, but got %q", targetOneExpectedOut, stdout.String())
}
targetOneExpectedErr := `mg.ExtraArgs{"-baz", "foo", "bar"}
stringvalue
2
`
if stderr.String() != targetOneExpectedErr {
t.Fatalf("expected stderr output %q, but got %q", targetOneExpectedErr, stderr.String())
}
}

// / This code liberally borrowed from https://github.com/rsc/goversion/blob/master/version/exe.go

type (
Expand Down
23 changes: 20 additions & 3 deletions mage/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func main() {
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
ExtraArgs []string // extraArgs contain the command-line arguments after the "--" separator
}

parseBool := func(env string) bool {
Expand Down Expand Up @@ -90,7 +91,23 @@ Options:
// flag will have printed out an error already.
return
}
args.Args = fs.Args()
var extraArgs []string
extraArgsFound := false
for _, arg := range fs.Args() {
// we do not really care about args before this point, this is where extra args begin
if arg == "--" {
extraArgsFound = true
continue
}
// all args before -- are the positional args that go to mage
if !extraArgsFound {
args.Args = append(args.Args, arg)
continue
}
// now we parse all that comes after --
extraArgs = append(extraArgs, arg)
}
args.ExtraArgs = extraArgs
if args.Help && len(args.Args) == 0 {
fs.Usage()
return
Expand Down Expand Up @@ -445,7 +462,7 @@ Options:
switch _strings.ToLower(target) {
{{range .Funcs }}
case "{{lower .TargetName}}":
expected := x + {{len .Args}}
expected := x + {{len .Args}} - {{.ExtraArgsPresent}}
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.
Expand All @@ -462,7 +479,7 @@ Options:
{{$imp := .}}
{{range .Info.Funcs }}
case "{{lower .TargetName}}":
expected := x + {{len .Args}}
expected := x + {{len .Args}} - {{.ExtraArgsPresent}}
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.
Expand Down