package plan
import (
"os"
"path/filepath"
"runtime"
)
// marker identifies the Python side when walking the tree.
var marker = filepath.Join("updater", "cli.py")
// Discover fills in PYTHONPATH by finding the Python package, so `go run .`
// works from a checkout with no configuration.
//
// Order: an explicit path wins, then $UPDATER_HOME, then an upward search from
// the working directory, the executable, and this source file. The last one is
// what makes `go run` work: runtime.Caller records the path the module was
// compiled from, which is the checkout even though the binary itself is in a
// temporary directory.
func (b Backend) Discover(explicit string) Backend {
if explicit != "" {
return b.withPath(explicit)
}
for _, dir := range searchRoots() {
if _, err := os.Stat(filepath.Join(dir, marker)); err == nil {
return b.withPath(dir)
}
}
return b // fall back to whatever the interpreter already has installed
}
func (b Backend) withPath(dir string) Backend {
abs, err := filepath.Abs(dir)
if err != nil {
abs = dir
}
out := b
out.Env = append(append([]string{}, b.Env...), "PYTHONPATH="+abs)
return out
}
func searchRoots() []string {
var roots []string
add := func(dir string) {
for _, r := range ancestors(dir) {
roots = append(roots, r)
}
}
if v := os.Getenv("UPDATER_HOME"); v != "" {
roots = append(roots, v)
}
if wd, err := os.Getwd(); err == nil {
add(wd)
}
if exe, err := os.Executable(); err == nil {
add(filepath.Dir(exe))
}
if _, file, _, ok := runtime.Caller(0); ok {
add(filepath.Dir(file))
}
seen := map[string]bool{}
out := roots[:0]
for _, r := range roots {
if !seen[r] {
seen[r] = true
out = append(out, r)
}
}
return out
}
// ancestors yields dir and then each parent, stopping after a few levels.
func ancestors(dir string) []string {
var out []string
cur, err := filepath.Abs(dir)
if err != nil {
return nil
}
for i := 0; i < 5; i++ {
out = append(out, cur)
parent := filepath.Dir(cur)
if parent == cur {
break
}
cur = parent
}
return out
}
// Hint is a human-readable explanation of how to fix a missing backend.
func (b Backend) Hint() string {
dir := ""
if len(b.Env) > 0 {
for _, e := range b.Env {
if len(e) > len("PYTHONPATH=") && e[:len("PYTHONPATH=")] == "PYTHONPATH=" {
dir = e[len("PYTHONPATH="):]
}
}
}
if dir == "" {
return "the Python package `updater` was not found.\n" +
"Run from inside the checkout, or set UPDATER_HOME=/path/to/updater,\n" +
"or pass --repo /path/to/updater."
}
return "looked for " + filepath.Join(dir, marker) + "\n" +
"pass --repo /path/to/updater, or set UPDATER_HOME."
}