diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go index 8c4500050..5d1cbc2a4 100644 --- a/cli/mobycli/exec.go +++ b/cli/mobycli/exec.go @@ -22,6 +22,7 @@ import ( "os" "os/exec" "os/signal" + "runtime" "strings" "github.com/spf13/cobra" @@ -60,7 +61,16 @@ func mustDelegateToMoby(ctxType string) bool { // Exec delegates to com.docker.cli if on moby context func Exec(root *cobra.Command) { - cmd := exec.Command(ComDockerCli, os.Args[1:]...) + execBinary := ComDockerCli + if runtime.GOOS == "windows" { // workaround for windows issue https://github.com/golang/go/issues/38736 + var err error + execBinary, err = LookPath(ComDockerCli) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } + cmd := exec.Command(execBinary, os.Args[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/cli/mobycli/lp_unix.go b/cli/mobycli/lp_unix.go new file mode 100644 index 000000000..56b1eef65 --- /dev/null +++ b/cli/mobycli/lp_unix.go @@ -0,0 +1,28 @@ +// +build !windows + +/* + Copyright 2020 Docker Compose CLI authors + + 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 mobycli + +import ( + "os/exec" +) + +// LookPath simply delegate to os/exec.LookPath if not on windows +func LookPath(file string) (string, error) { + return exec.LookPath(file) +} diff --git a/cli/mobycli/lp_windows.go b/cli/mobycli/lp_windows.go new file mode 100644 index 000000000..3fd1255fb --- /dev/null +++ b/cli/mobycli/lp_windows.go @@ -0,0 +1,111 @@ +/* + Copyright 2020 Docker Compose CLI authors + + 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 mobycli + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// ErrNotFound is the error resulting if a path search failed to find an executable file. +var ErrNotFound = errors.New("executable file not found in %PATH%") + +func chkStat(file string) error { + d, err := os.Stat(file) + if err != nil { + return err + } + if d.IsDir() { + return os.ErrPermission + } + return nil +} + +func hasExt(file string) bool { + i := strings.LastIndex(file, ".") + if i < 0 { + return false + } + return strings.LastIndexAny(file, `:\/`) < i +} + +func findExecutable(file string, exts []string) (string, error) { + if len(exts) == 0 { + return file, chkStat(file) + } + if hasExt(file) { + if chkStat(file) == nil { + return file, nil + } + } + for _, e := range exts { + if f := file + e; chkStat(f) == nil { + return f, nil + } + } + return "", os.ErrNotExist +} + +// LookPath searches for an executable named file in the +// directories named by the PATH environment variable. +// If file contains a slash, it is tried directly and the PATH is not consulted. +// LookPath also uses PATHEXT environment variable to match +// a suitable candidate. +// The result may be an absolute path or a path relative to the current directory. +func LookPath(file string) (string, error) { + var exts []string + x := os.Getenv(`PATHEXT`) + if x != "" { + for _, e := range strings.Split(strings.ToLower(x), `;`) { + if e == "" { + continue + } + if e[0] != '.' { + e = "." + e + } + exts = append(exts, e) + } + } else { + exts = []string{".com", ".exe", ".bat", ".cmd"} + } + + if strings.ContainsAny(file, `:\/`) { + if f, err := findExecutable(file, exts); err == nil { + return f, nil + } else { + return "", &exec.Error{file, err} + } + } + // DO NOT lookup current folder + //if f, err := findExecutable(filepath.Join(".", file), exts); err == nil { + // return f, nil + //} + path := os.Getenv("path") + for _, dir := range filepath.SplitList(path) { + // empty dir means dupicate semicolon in PATH, should not resolve files in current working dir... + if strings.TrimSpace(dir) == "" { + continue + } + if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil { + return f, nil + } + } + return "", &exec.Error{file, ErrNotFound} +}