Skip to content

Commit ed3408d

Browse files
bug: fix more path issues on windows
Stop evaluating env vars locally, but instead use the shell/cmd.exe.
1 parent 2bbe7bc commit ed3408d

File tree

4 files changed

+21
-293
lines changed

4 files changed

+21
-293
lines changed

pkg/engine/cmd.go

Lines changed: 21 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@ import (
1111
"os"
1212
"os/exec"
1313
"path"
14-
"path/filepath"
1514
"runtime"
1615
"sort"
1716
"strings"
1817
"sync"
1918

20-
"github.com/google/shlex"
2119
"github.com/gptscript-ai/gptscript/pkg/counter"
2220
"github.com/gptscript-ai/gptscript/pkg/env"
2321
"github.com/gptscript-ai/gptscript/pkg/types"
@@ -254,28 +252,14 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
254252
interpreter, rest, _ := strings.Cut(tool.Instructions, "\n")
255253
interpreter = strings.TrimSpace(interpreter)[2:]
256254

257-
args, err := shlex.Split(interpreter)
258-
if err != nil {
259-
return nil, nil, err
260-
}
255+
args := strings.Fields(interpreter)
261256

262-
envvars, err = e.getRuntimeEnv(ctx, tool, args, envvars)
257+
envvars, err := e.getRuntimeEnv(ctx, tool, args, envvars)
263258
if err != nil {
264259
return nil, nil, err
265260
}
266261

267262
envvars, envMap := envAsMapAndDeDup(envvars)
268-
for i, arg := range args {
269-
args[i] = os.Expand(arg, func(s string) string {
270-
return envMap[s]
271-
})
272-
}
273-
274-
// After we determined the interpreter we again interpret the args by env vars
275-
args, err = replaceVariablesForInterpreter(interpreter, envMap)
276-
if err != nil {
277-
return nil, nil, err
278-
}
279263

280264
if runtime.GOOS == "windows" && (args[0] == "/bin/bash" || args[0] == "/bin/sh") {
281265
args[0] = path.Base(args[0])
@@ -286,8 +270,7 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
286270
}
287271

288272
var (
289-
cmdArgs = args[1:]
290-
stop = func() {}
273+
stop = func() {}
291274
)
292275

293276
if strings.TrimSpace(rest) != "" {
@@ -305,105 +288,30 @@ func (e *Engine) newCommand(ctx context.Context, extraEnv []string, tool types.T
305288
stop()
306289
return nil, nil, err
307290
}
308-
cmdArgs = append(cmdArgs, f.Name())
291+
args = append(args, f.Name())
309292
}
310293

311-
// This is a workaround for Windows, where the command interpreter is constructed with unix style paths
312-
// It converts unix style paths to windows style paths
313-
if runtime.GOOS == "windows" {
314-
parts := strings.Split(args[0], "/")
315-
if parts[len(parts)-1] == "gptscript-go-tool" {
316-
parts[len(parts)-1] = "gptscript-go-tool.exe"
317-
}
318-
319-
args[0] = filepath.Join(parts...)
320-
}
321-
322-
cmd := exec.CommandContext(ctx, env.Lookup(envvars, args[0]), cmdArgs...)
323-
cmd.Env = compressEnv(envvars)
324-
return cmd, stop, nil
325-
}
326-
327-
func replaceVariablesForInterpreter(interpreter string, envMap map[string]string) ([]string, error) {
328-
var parts []string
329-
for i, part := range splitByQuotes(interpreter) {
330-
if i%2 == 0 {
331-
part = os.Expand(part, func(s string) string {
332-
return envMap[s]
333-
})
334-
// We protect newly resolved env vars from getting replaced when we do the second Expand
335-
// after shlex. Yeah, crazy. I'm guessing this isn't secure, but just trying to avoid a foot gun.
336-
part = os.Expand(part, func(s string) string {
337-
return "${__" + s + "}"
338-
})
339-
}
340-
parts = append(parts, part)
341-
}
342-
343-
parts, err := shlex.Split(strings.Join(parts, ""))
344-
if err != nil {
345-
return nil, err
346-
}
347-
348-
for i, part := range parts {
349-
parts[i] = os.Expand(part, func(s string) string {
350-
if strings.HasPrefix(s, "__") {
351-
return "${" + s[2:] + "}"
352-
}
353-
return envMap[s]
354-
})
355-
}
356-
357-
return parts, nil
358-
}
359-
360-
// splitByQuotes will split a string by parsing matching double quotes (with \ as the escape character).
361-
// The return value conforms to the following properties
362-
// 1. s == strings.Join(result, "")
363-
// 2. Even indexes are strings that were not in quotes.
364-
// 3. Odd indexes are strings that were quoted.
365-
//
366-
// Example: s = `In a "quoted string" quotes can be escaped with \"`
367-
//
368-
// result = [`In a `, `"quoted string"`, ` quotes can be escaped with \"`]
369-
func splitByQuotes(s string) (result []string) {
370-
var (
371-
buf strings.Builder
372-
inEscape, inQuote bool
373-
)
374-
375-
for _, c := range s {
376-
if inEscape {
377-
buf.WriteRune(c)
378-
inEscape = false
379-
continue
380-
}
381-
382-
switch c {
383-
case '"':
384-
if inQuote {
385-
buf.WriteRune(c)
294+
// translate UNIX style to Windows
295+
for i, arg := range args {
296+
args[i] = os.Expand(arg, func(s string) string {
297+
if strings.HasPrefix(s, "!") {
298+
return envMap[s[1:]]
386299
}
387-
result = append(result, buf.String())
388-
buf.Reset()
389-
if !inQuote {
390-
buf.WriteRune(c)
300+
if runtime.GOOS == "windows" {
301+
return "%" + s + "%"
391302
}
392-
inQuote = !inQuote
393-
case '\\':
394-
inEscape = true
395-
buf.WriteRune(c)
396-
default:
397-
buf.WriteRune(c)
398-
}
303+
return "${" + s + "}"
304+
})
399305
}
400306

401-
if buf.Len() > 0 {
402-
if inQuote {
403-
result = append(result, "")
404-
}
405-
result = append(result, buf.String())
307+
if runtime.GOOS == "windows" {
308+
args[0] = strings.ReplaceAll(args[0], "/", "\\")
309+
args = append([]string{"cmd.exe", "/c"}, strings.Join(args, " "))
310+
} else {
311+
args = append([]string{"/bin/sh", "-c"}, strings.Join(args, " "))
406312
}
407313

408-
return
314+
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
315+
cmd.Env = compressEnv(envvars)
316+
return cmd, stop, nil
409317
}

pkg/engine/cmd_test.go

Lines changed: 0 additions & 135 deletions
This file was deleted.

pkg/env/env.go

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package env
33
import (
44
"fmt"
55
"os"
6-
"path/filepath"
76
"strings"
87
)
98

@@ -65,42 +64,3 @@ func AppendPath(env []string, binPath string) []string {
6564
}
6665
return newEnv
6766
}
68-
69-
// Lookup will try to find bin in the PATH in env. It will refer to PATHEXT for Windows support.
70-
// If bin can not be resolved to anything the original bin string is returned.
71-
func Lookup(env []string, bin string) string {
72-
if strings.Contains(bin, string(filepath.Separator)) {
73-
return bin
74-
}
75-
76-
for _, env := range env {
77-
for _, prefix := range []string{"PATH=", "Path="} {
78-
suffix, ok := strings.CutPrefix(env, prefix)
79-
if !ok {
80-
continue
81-
}
82-
log.Debugf("Looking for %s in %s", bin, suffix)
83-
for _, path := range strings.Split(suffix, string(os.PathListSeparator)) {
84-
testPath := filepath.Join(path, bin)
85-
86-
if stat, err := os.Stat(testPath); err == nil && !stat.IsDir() {
87-
log.Debugf("Found %s for %s in %s", testPath, bin, suffix)
88-
return testPath
89-
}
90-
91-
for _, ext := range strings.Split(os.Getenv("PATHEXT"), string(os.PathListSeparator)) {
92-
if ext == "" {
93-
continue
94-
}
95-
96-
if stat, err := os.Stat(testPath + ext); err == nil && !stat.IsDir() {
97-
log.Debugf("Found %s for %s in %s", testPath+ext, bin, suffix)
98-
return testPath + ext
99-
}
100-
}
101-
}
102-
}
103-
}
104-
105-
return bin
106-
}

pkg/env/log.go

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)