2022-05-20 19:40:03 +00:00
|
|
|
package python
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
func PyInline(ctx context.Context, inlinePy string) (string, error) {
|
|
|
|
return Py(ctx, "-c", TrimLeadingWhitespace(inlinePy))
|
|
|
|
}
|
|
|
|
|
|
|
|
func Py(ctx context.Context, script string, args ...string) (string, error) {
|
2023-07-26 10:07:26 +00:00
|
|
|
py, err := DetectExecutable(ctx)
|
2022-05-20 19:40:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
out, err := execAndPassErr(ctx, py, append([]string{script}, args...)...)
|
|
|
|
if err != nil {
|
|
|
|
// current error message chain is longer:
|
|
|
|
// failed to call {pyExec} __non_existing__.py: {pyExec}: can't open
|
|
|
|
// ... file '{pwd}/__non_existing__.py': [Errno 2] No such file or directory"
|
|
|
|
// probably we'll need to make it shorter:
|
|
|
|
// can't open file '$PWD/__non_existing__.py': [Errno 2] No such file or directory
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return trimmedS(out), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createVirtualEnv(ctx context.Context) error {
|
|
|
|
_, err := Py(context.Background(), "-m", "venv", ".venv")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// python3 -m build -w
|
|
|
|
// https://packaging.python.org/en/latest/tutorials/packaging-projects/
|
|
|
|
func detectVirtualEnv() (string, error) {
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
wdf, err := os.Open(wd)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
files, err := wdf.ReadDir(0)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
for _, v := range files {
|
|
|
|
if !v.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
candidate := fmt.Sprintf("%s/%s", wd, v.Name())
|
|
|
|
_, err = os.Stat(fmt.Sprintf("%s/pyvenv.cfg", candidate))
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return candidate, nil
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var pyExec string
|
|
|
|
|
2023-07-26 10:07:26 +00:00
|
|
|
func DetectExecutable(ctx context.Context) (string, error) {
|
2022-05-20 19:40:03 +00:00
|
|
|
if pyExec != "" {
|
|
|
|
return pyExec, nil
|
|
|
|
}
|
|
|
|
detector := "which"
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
detector = "where.exe"
|
|
|
|
}
|
|
|
|
out, err := execAndPassErr(ctx, detector, "python3")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
pyExec = trimmedS(out)
|
|
|
|
return pyExec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func execAndPassErr(ctx context.Context, name string, args ...string) ([]byte, error) {
|
|
|
|
// TODO: move out to a separate package, once we have Maven integration
|
|
|
|
out, err := exec.CommandContext(ctx, name, args...).Output()
|
|
|
|
return out, nicerErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func nicerErr(err error) error {
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if ee, ok := err.(*exec.ExitError); ok {
|
|
|
|
errMsg := trimmedS(ee.Stderr)
|
|
|
|
if errMsg == "" {
|
|
|
|
errMsg = err.Error()
|
|
|
|
}
|
|
|
|
return errors.New(errMsg)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func trimmedS(bytes []byte) string {
|
|
|
|
return strings.Trim(string(bytes), "\n\r")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TrimLeadingWhitespace removes leading whitespace
|
|
|
|
// function copied from Databricks Terraform provider
|
|
|
|
func TrimLeadingWhitespace(commandStr string) (newCommand string) {
|
|
|
|
lines := strings.Split(strings.ReplaceAll(commandStr, "\t", " "), "\n")
|
|
|
|
leadingWhitespace := 1<<31 - 1
|
|
|
|
for _, line := range lines {
|
|
|
|
for pos, char := range line {
|
|
|
|
if char == ' ' || char == '\t' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// first non-whitespace character
|
|
|
|
if pos < leadingWhitespace {
|
|
|
|
leadingWhitespace = pos
|
|
|
|
}
|
|
|
|
// is not needed further
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := 0; i < len(lines); i++ {
|
|
|
|
if lines[i] == "" || strings.Trim(lines[i], " \t") == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if len(lines[i]) < leadingWhitespace {
|
|
|
|
newCommand += lines[i] + "\n" // or not..
|
|
|
|
} else {
|
|
|
|
newCommand += lines[i][leadingWhitespace:] + "\n"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|