Skip to content

Commit f1c2bb6

Browse files
DCodeBotdector
andcommitted
feat(cli): add custom and compat mode router
Co-authored-by: D <code@dector.space>
1 parent 6d0e0a6 commit f1c2bb6

5 files changed

Lines changed: 217 additions & 14 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package compat
2+
3+
import (
4+
"fmt"
5+
"io"
6+
)
7+
8+
// Run is a temporary placeholder for adb-compatible mode. The router can select
9+
// compat mode before the adb-shaped command implementation exists, which lets
10+
// tests and users verify mode detection independently from the upcoming compat
11+
// command skeleton.
12+
func Run(args []string, stdout, stderr io.Writer) int {
13+
fmt.Fprintln(stderr, "adb-go compat mode is not implemented yet")
14+
return 1
15+
}

cmd/adb-go/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"os"
55

6+
"github.com/dector/adb-go/cmd/adb-go/internal/compat"
67
"github.com/dector/adb-go/cmd/adb-go/internal/custom"
78
)
89

@@ -13,5 +14,5 @@ var version = "dev"
1314

1415
func main() {
1516
custom.Version = version
16-
os.Exit(custom.Run(os.Args[1:], os.Stdout, os.Stderr))
17+
os.Exit(runCLI(os.Args[1:], os.Stdout, os.Stderr, os.Args[0], os.Getenv("ADB_GO_MODE"), custom.Run, compat.Run))
1718
}

cmd/adb-go/router.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"path/filepath"
7+
)
8+
9+
type cliMode int
10+
11+
const (
12+
cliModeCustom cliMode = iota
13+
cliModeCompat
14+
)
15+
16+
type cliRunner func(args []string, stdout, stderr io.Writer) int
17+
18+
func runCLI(args []string, stdout, stderr io.Writer, executablePath, envMode string, customRun, compatRun cliRunner) int {
19+
mode, err := selectCLIMode(executablePath, envMode)
20+
if err != nil {
21+
fmt.Fprintln(stderr, err)
22+
return 2
23+
}
24+
25+
switch mode {
26+
case cliModeCompat:
27+
return compatRun(args, stdout, stderr)
28+
default:
29+
return customRun(args, stdout, stderr)
30+
}
31+
}
32+
33+
func selectCLIMode(executablePath, envMode string) (cliMode, error) {
34+
switch envMode {
35+
case "":
36+
// Fall through to executable-name detection.
37+
case "custom":
38+
return cliModeCustom, nil
39+
case "compat":
40+
return cliModeCompat, nil
41+
default:
42+
return cliModeCustom, fmt.Errorf("invalid ADB_GO_MODE %q: expected \"custom\" or \"compat\"", envMode)
43+
}
44+
45+
switch filepath.Base(executablePath) {
46+
case "adb", "adb.exe":
47+
return cliModeCompat, nil
48+
default:
49+
return cliModeCustom, nil
50+
}
51+
}

cmd/adb-go/router_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"strings"
8+
"testing"
9+
10+
"github.com/dector/adb-go/cmd/adb-go/internal/compat"
11+
)
12+
13+
func TestRunCLIDefaultsToCustomMode(t *testing.T) {
14+
calls := recordingRunners()
15+
var stdout, stderr bytes.Buffer
16+
17+
code := runCLI([]string{"version"}, &stdout, &stderr, "adb-go", "", calls.custom, calls.compat)
18+
19+
if code != 10 {
20+
t.Fatalf("runCLI exit code = %d, want custom runner code 10", code)
21+
}
22+
if calls.customCalls != 1 || calls.compatCalls != 0 {
23+
t.Fatalf("custom calls = %d, compat calls = %d; want custom only", calls.customCalls, calls.compatCalls)
24+
}
25+
if got := stdout.String(); !strings.Contains(got, "custom:[version]") {
26+
t.Fatalf("stdout = %q, want custom runner output", got)
27+
}
28+
if stderr.Len() != 0 {
29+
t.Fatalf("stderr = %q, want empty", stderr.String())
30+
}
31+
}
32+
33+
func TestRunCLIUsesCustomModeFromEnv(t *testing.T) {
34+
calls := recordingRunners()
35+
var stdout, stderr bytes.Buffer
36+
37+
code := runCLI([]string{"help"}, &stdout, &stderr, "adb", "custom", calls.custom, calls.compat)
38+
39+
if code != 10 {
40+
t.Fatalf("runCLI exit code = %d, want custom runner code 10", code)
41+
}
42+
if calls.customCalls != 1 || calls.compatCalls != 0 {
43+
t.Fatalf("custom calls = %d, compat calls = %d; want custom env override", calls.customCalls, calls.compatCalls)
44+
}
45+
}
46+
47+
func TestRunCLIUsesCompatModeFromEnv(t *testing.T) {
48+
calls := recordingRunners()
49+
var stdout, stderr bytes.Buffer
50+
51+
code := runCLI([]string{"version"}, &stdout, &stderr, "adb-go", "compat", calls.custom, calls.compat)
52+
53+
if code != 20 {
54+
t.Fatalf("runCLI exit code = %d, want compat runner code 20", code)
55+
}
56+
if calls.customCalls != 0 || calls.compatCalls != 1 {
57+
t.Fatalf("custom calls = %d, compat calls = %d; want compat only", calls.customCalls, calls.compatCalls)
58+
}
59+
if got := stdout.String(); !strings.Contains(got, "compat:[version]") {
60+
t.Fatalf("stdout = %q, want compat runner output", got)
61+
}
62+
}
63+
64+
func TestRunCLIRejectsInvalidEnvMode(t *testing.T) {
65+
calls := recordingRunners()
66+
var stdout, stderr bytes.Buffer
67+
68+
code := runCLI([]string{"version"}, &stdout, &stderr, "adb-go", "invalid", calls.custom, calls.compat)
69+
70+
if code != 2 {
71+
t.Fatalf("runCLI exit code = %d, want 2", code)
72+
}
73+
if calls.customCalls != 0 || calls.compatCalls != 0 {
74+
t.Fatalf("custom calls = %d, compat calls = %d; want no runner calls", calls.customCalls, calls.compatCalls)
75+
}
76+
if stdout.Len() != 0 {
77+
t.Fatalf("stdout = %q, want empty", stdout.String())
78+
}
79+
if got := stderr.String(); !strings.Contains(got, "invalid ADB_GO_MODE") || !strings.Contains(got, `"custom"`) || !strings.Contains(got, `"compat"`) {
80+
t.Fatalf("stderr = %q, want invalid mode guidance", got)
81+
}
82+
}
83+
84+
func TestRunCLIUsesCompatModeForADBExecutableBasenames(t *testing.T) {
85+
for _, executablePath := range []string{"adb", "/usr/local/bin/adb", "adb.exe", `/opt/android/adb.exe`} {
86+
t.Run(executablePath, func(t *testing.T) {
87+
calls := recordingRunners()
88+
var stdout, stderr bytes.Buffer
89+
90+
code := runCLI([]string{"devices"}, &stdout, &stderr, executablePath, "", calls.custom, calls.compat)
91+
92+
if code != 20 {
93+
t.Fatalf("runCLI exit code = %d, want compat runner code 20", code)
94+
}
95+
if calls.customCalls != 0 || calls.compatCalls != 1 {
96+
t.Fatalf("custom calls = %d, compat calls = %d; want compat only", calls.customCalls, calls.compatCalls)
97+
}
98+
})
99+
}
100+
}
101+
102+
func TestCompatPlaceholderReportsNotImplemented(t *testing.T) {
103+
var stdout, stderr bytes.Buffer
104+
calls := recordingRunners()
105+
106+
code := runCLI([]string{"devices"}, &stdout, &stderr, "adb", "", calls.custom, compat.Run)
107+
108+
if code != 1 {
109+
t.Fatalf("runCLI compat placeholder exit code = %d, want 1", code)
110+
}
111+
if stdout.Len() != 0 {
112+
t.Fatalf("stdout = %q, want empty", stdout.String())
113+
}
114+
if got := stderr.String(); !strings.Contains(got, "compat mode is not implemented yet") {
115+
t.Fatalf("stderr = %q, want compat placeholder message", got)
116+
}
117+
}
118+
119+
type runnerCalls struct {
120+
customCalls int
121+
compatCalls int
122+
}
123+
124+
func recordingRunners() *runnerCalls { return &runnerCalls{} }
125+
126+
func (r *runnerCalls) custom(args []string, stdout, stderr io.Writer) int {
127+
r.customCalls++
128+
fmt.Fprintf(stdout, "custom:%v\n", args)
129+
return 10
130+
}
131+
132+
func (r *runnerCalls) compat(args []string, stdout, stderr io.Writer) int {
133+
r.compatCalls++
134+
fmt.Fprintf(stdout, "compat:%v\n", args)
135+
return 20
136+
}

docs/init/PLAN.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ This plan is organized as small milestones. Each milestone should be implemented
44

55
## Progress
66

7-
- Current milestone: M63 — Add CLI mode router.
8-
- Completed milestone range: Milestones 17–62 completed the initial CLI shell/push/pull work, CLI documentation, Linux USB transport design, transport abstraction, Linux USB discovery, Linux usbfs bulk transport, high-level USB connection API, CLI USB connection option, USB documentation, adb-go-specific target listing, explicit ADB authentication support, the client APK install helper, the CLI `install-apk` command, APK install documentation, logcat library/CLI/documentation support, property helpers, screencap support, reboot library/CLI/documentation support, foreground port forwarding support, the minimal adb-god daemon foundation, Linux systemd user-service install/lifecycle controls, Linux systemd user-service status reporting, daemon diagnostics, daemon service logs, daemon service reinstall, CLI version reporting, CLI error-message improvements, integration-test documentation, exported package documentation/examples, the daemon-backed persistent forwarding design, the daemon forwarding protocol model, daemon-owned forwarding listener registration, TCP target bridging for persistent forwards, CLI persistent forwarding controls, persistent forwarding diagnostics, and the extracted custom CLI mode package.
7+
- Current milestone: M64 — Add adb-compatible CLI skeleton.
8+
- Completed milestone range: Milestones 17–63 completed the initial CLI shell/push/pull work, CLI documentation, Linux USB transport design, transport abstraction, Linux USB discovery, Linux usbfs bulk transport, high-level USB connection API, CLI USB connection option, USB documentation, adb-go-specific target listing, explicit ADB authentication support, the client APK install helper, the CLI `install-apk` command, APK install documentation, logcat library/CLI/documentation support, property helpers, screencap support, reboot library/CLI/documentation support, foreground port forwarding support, the minimal adb-god daemon foundation, Linux systemd user-service install/lifecycle controls, Linux systemd user-service status reporting, daemon diagnostics, daemon service logs, daemon service reinstall, CLI version reporting, CLI error-message improvements, integration-test documentation, exported package documentation/examples, the daemon-backed persistent forwarding design, the daemon forwarding protocol model, daemon-owned forwarding listener registration, TCP target bridging for persistent forwards, CLI persistent forwarding controls, persistent forwarding diagnostics, the extracted custom CLI mode package, and the CLI custom/compat mode router.
99
- Active focus: split the CLI into clearly separated custom and adb-compatibility modes. Custom mode preserves the current adb-go UX. Compat mode will target current Android SDK Platform-Tools `adb` CLI behavior closely enough that a future `adb` symlink can use adb-go as a drop-in replacement for supported workflows.
1010
- Completed USB direction: Linux-only first, using the kernel usbfs interface under `/dev/bus/usb` behind build tags. This remains pure Go because it talks to device files and ioctls directly instead of linking native USB libraries.
1111

@@ -70,28 +70,28 @@ Done when:
7070

7171
## M63 — Add CLI mode router
7272

73-
Status: Not started
73+
Status: Done
7474

7575
Commit: `feat(cli): add custom and compat mode router`
7676

7777
Tasks:
7878

79-
- [ ] Replace `cmd/adb-go/main.go` with a tiny router that only detects mode and dispatches to the selected implementation.
80-
- [ ] Select compat mode when `ADB_GO_MODE=compat` or the executable basename is exactly `adb` or `adb.exe`.
81-
- [ ] Select custom mode when `ADB_GO_MODE=custom` or when no compat signal is present.
82-
- [ ] Reject any other non-empty `ADB_GO_MODE` value with exit code `2`.
83-
- [ ] Do not support bootstrap flags such as `--compat` or `--custom`.
84-
- [ ] Add a temporary compat placeholder that returns a clear “compat mode is not implemented yet” error with exit code `1`.
79+
- [x] Replace `cmd/adb-go/main.go` with a tiny router that only detects mode and dispatches to the selected implementation.
80+
- [x] Select compat mode when `ADB_GO_MODE=compat` or the executable basename is exactly `adb` or `adb.exe`.
81+
- [x] Select custom mode when `ADB_GO_MODE=custom` or when no compat signal is present.
82+
- [x] Reject any other non-empty `ADB_GO_MODE` value with exit code `2`.
83+
- [x] Do not support bootstrap flags such as `--compat` or `--custom`.
84+
- [x] Add a temporary compat placeholder that returns a clear “compat mode is not implemented yet” error with exit code `1`.
8585

8686
Tests:
8787

88-
- [ ] Router tests cover default custom mode, `ADB_GO_MODE=custom`, `ADB_GO_MODE=compat`, invalid `ADB_GO_MODE`, and `adb`/`adb.exe` basename detection.
89-
- [ ] Existing custom mode CLI tests continue to pass.
90-
- [ ] `go test ./...` passes
88+
- [x] Router tests cover default custom mode, `ADB_GO_MODE=custom`, `ADB_GO_MODE=compat`, invalid `ADB_GO_MODE`, and `adb`/`adb.exe` basename detection.
89+
- [x] Existing custom mode CLI tests continue to pass.
90+
- [x] `go test ./...` passes
9191

9292
Done when:
9393

94-
- [ ] The binary can route cleanly between custom mode and a compat placeholder without changing custom behavior.
94+
- [x] The binary can route cleanly between custom mode and a compat placeholder without changing custom behavior.
9595

9696
## M64 — Add adb-compatible CLI skeleton
9797

0 commit comments

Comments
 (0)