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." }