plugins: the Bun the plugin host runs on is kept as magpie-bun, so a proxy app's PROCESS-NAME rule can route the requests plugins (Cursor and the other moved subscriptions) make (#1048)
magpie downloads its own Bun into ~/.cache/magpie/bun/<version>/. It is now written there as magpie-bun (magpie-bun.exe on Windows), and a Bun downloaded before is renamed in place the first time it is looked for, so nothing is downloaded again and no second copy takes disk. The host, bun add and bun update all run it, so all of their traffic carries the one name. A $MAGPIE_BUN the user set is run as it is. Verified: TestBunDownloadedAsMagpieBun, TestBunDownloadedBeforeTakesTheName and TestHostRunsAsMagpieBun fail without the change (the Bun is .../bun, the host runs .../bun). TestHostRunsAsMagpieBun starts the real host on a copy of Bun 1.4.2 and `ps -o comm=` names it magpie-bun on macOS. On the Windows box the three tests pass with the Windows build of Bun 1.4.2, a running bun.exe renames to magpie-bun.exe, and Get-Process/Win32_Process name it magpie-bun. Linux not run here (CI's ubuntu job runs the test with Bun on PATH). go test -tags nogui ./... green; -race -count=10 on the new tests green.
This commit is contained in:
1 parent
de2c84ace7
commit
05683637d5
4 files changed
+140
-4
No files matched your search
+3
-1
@@ -1674,7 +1674,9 @@ MAGPIE_GITHUB_MIRROR=https://gh.example magpie serve
|
||||
- `~/.config/magpie/stash.json` — values magpie replaced, restored on switch-back
|
||||
- `~/.config/magpie/plugins.json`, `plugins/` — the plugins added, and their packages
|
||||
- `~/.config/magpie/plugin-auth.json` — the plugins' sign-ins (0600)
|
||||
- `~/.cache/magpie/bun/` — the Bun plugins run on
|
||||
- `~/.cache/magpie/bun/<version>/magpie-bun` — the Bun plugins run on, named
|
||||
so that a proxy app's PROCESS-NAME rule can match it (`magpie-bun.exe` on
|
||||
Windows)
|
||||
- `~/.cache/magpie/models.json` — models.dev catalog (OpenCode's cache at
|
||||
`~/.cache/opencode/models.json` is used when present)
|
||||
- `~/.cache/magpie/models/<provider>.json` — model lists fetched from vendors
|
||||
|
||||
@@ -100,7 +100,7 @@ func Bun(ctx context.Context) (string, error) {
|
||||
defer bunMu.Unlock()
|
||||
v := inUseLocked()
|
||||
exe := bunExeOf(v)
|
||||
if _, err := os.Stat(exe); err == nil {
|
||||
if haveBun(v) {
|
||||
return exe, nil
|
||||
}
|
||||
if err := downloadBun(ctx, v, exe); err != nil {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The Bun magpie downloads is kept as magpie-bun, the name a proxy app's
|
||||
// PROCESS-NAME rule sees (#1048).
|
||||
func TestBunDownloadedAsMagpieBun(t *testing.T) {
|
||||
bunHome(t, BunVersion, time.Now())
|
||||
asked := bunReleases(t, []byte("bun"))
|
||||
exe, err := Bun(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if filepath.Base(exe) != magpieBun() || asked.Load() != 1 {
|
||||
t.Fatalf("Bun = %s after %d downloads, want one named %s", exe, asked.Load(), magpieBun())
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(filepath.Dir(exe), bunExe())); !os.IsNotExist(err) {
|
||||
t.Fatalf("a bun beside magpie-bun: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A Bun downloaded before is renamed magpie-bun, not downloaded again.
|
||||
func TestBunDownloadedBeforeTakesTheName(t *testing.T) {
|
||||
bunHome(t, BunVersion, time.Now())
|
||||
asked := bunReleases(t, []byte("new download"))
|
||||
old := filepath.Join(bunDirOf(BunVersion), bunExe())
|
||||
if err := os.MkdirAll(filepath.Dir(old), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(old, []byte("downloaded before"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !HasBun() {
|
||||
t.Fatal("HasBun is false with a bun downloaded before")
|
||||
}
|
||||
exe, err := Bun(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b, _ := os.ReadFile(exe); filepath.Base(exe) != magpieBun() || string(b) != "downloaded before" || asked.Load() != 0 {
|
||||
t.Fatalf("Bun = %s holding %q after %d downloads", exe, b, asked.Load())
|
||||
}
|
||||
if _, err := os.Stat(old); !os.IsNotExist(err) {
|
||||
t.Fatalf("the old bun is still there: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// The plugin host runs as magpie-bun: the process name the system reports
|
||||
// for it is magpie-bun, not bun.
|
||||
func TestHostRunsAsMagpieBun(t *testing.T) {
|
||||
real, err := exec.LookPath("bun")
|
||||
if err != nil {
|
||||
t.Skip("no bun on PATH")
|
||||
}
|
||||
bunHome(t, BunVersion, time.Now())
|
||||
t.Cleanup(Settle)
|
||||
// a real Bun as magpie downloaded it before: a copy (not a link, whose
|
||||
// process would take the target's name) at the old name
|
||||
old := filepath.Join(bunDirOf(BunVersion), bunExe())
|
||||
if err := os.MkdirAll(filepath.Dir(old), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
copyExe(t, real, old)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
abs, _ := filepath.Abs("testdata/fake/index.js")
|
||||
if _, err := Add(ctx, abs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ps, err := Providers(ctx); err != nil || len(ps) != 1 {
|
||||
t.Fatalf("Providers = %+v, %v", ps, err)
|
||||
}
|
||||
hostMu.Lock()
|
||||
h := current
|
||||
hostMu.Unlock()
|
||||
if h == nil || h.cmd.Process == nil {
|
||||
t.Fatal("no host running")
|
||||
}
|
||||
if got := filepath.Base(h.cmd.Path); got != magpieBun() {
|
||||
t.Fatalf("the host runs %s", h.cmd.Path)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return
|
||||
}
|
||||
out, err := exec.Command("ps", "-o", "comm=", "-p", strconv.Itoa(h.cmd.Process.Pid)).Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := filepath.Base(strings.TrimSpace(string(out))); got != "magpie-bun" {
|
||||
t.Fatalf("ps says the host is %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func copyExe(t *testing.T, from, to string) {
|
||||
t.Helper()
|
||||
in, err := os.Open(from)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer in.Close()
|
||||
out, err := os.OpenFile(to, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := out.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,12 @@ func bunRoot() string { return filepath.Join(filepath.Dir(catalog.CachePath()),
|
||||
|
||||
func bunDirOf(v string) string { return filepath.Join(bunRoot(), v) }
|
||||
|
||||
func bunExeOf(v string) string { return filepath.Join(bunDirOf(v), bunExe()) }
|
||||
// bunExeOf is where magpie keeps Bun v: named magpie-bun, so a proxy app's
|
||||
// PROCESS-NAME rule can tell the requests plugins make (and their installs)
|
||||
// from any other Bun's (#1048).
|
||||
func bunExeOf(v string) string { return filepath.Join(bunDirOf(v), magpieBun()) }
|
||||
|
||||
func magpieBun() string { return "magpie-" + bunExe() }
|
||||
|
||||
func bunStatePath() string { return filepath.Join(bunRoot(), "state.json") }
|
||||
|
||||
@@ -70,7 +75,13 @@ func writeBunState(s bunState) error {
|
||||
}
|
||||
|
||||
func haveBun(v string) bool {
|
||||
_, err := os.Stat(bunExeOf(v))
|
||||
exe := bunExeOf(v)
|
||||
if _, err := os.Stat(exe); err == nil {
|
||||
return true
|
||||
}
|
||||
// a Bun downloaded before it was named magpie-bun takes the name
|
||||
_ = os.Rename(filepath.Join(bunDirOf(v), bunExe()), exe)
|
||||
_, err := os.Stat(exe)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user