@@ -63,12 +63,15 @@ pub fn to_trimmed_utf8(v: Vec<u8>) -> String {
63
63
pub struct ACommand ( std:: process:: Command ) ;
64
64
impl ACommand {
65
65
/// `adb` command builder
66
+ #[ must_use]
66
67
pub fn new ( ) -> Self {
67
68
Self ( std:: process:: Command :: new ( "adb" ) )
68
69
}
70
+
69
71
/// `shell` sub-command builder.
70
72
///
71
73
/// If `device_serial` is empty, it lets ADB choose the default device.
74
+ #[ must_use]
72
75
pub fn shell < S : AsRef < str > > ( mut self , device_serial : S ) -> ShellCommand {
73
76
let serial = device_serial. as_ref ( ) ;
74
77
if !serial. is_empty ( ) {
@@ -77,6 +80,7 @@ impl ACommand {
77
80
self . 0 . arg ( "shell" ) ;
78
81
ShellCommand ( self )
79
82
}
83
+
80
84
/// Header-less list of attached devices (as serials) and their statuses:
81
85
/// - USB
82
86
/// - TCP/IP: WIFI, Ethernet, etc...
@@ -107,6 +111,61 @@ impl ACommand {
107
111
} )
108
112
. collect ( ) )
109
113
}
114
+
115
+ /// `version` sub-command, splitted by lines
116
+ ///
117
+ /// ## Format
118
+ /// This is just a sample,
119
+ /// we don't know which guarantees are stable (yet):
120
+ /// ```txt
121
+ /// Android Debug Bridge version 1.0.41
122
+ /// Version 34.0.5-debian
123
+ /// Installed as /usr/lib/android-sdk/platform-tools/adb
124
+ /// Running on Linux 6.12.12-amd64 (x86_64)
125
+ /// ```
126
+ ///
127
+ /// The expected format should be like:
128
+ /// ```txt
129
+ /// Android Debug Bridge version <num>.<num>.<num>
130
+ /// Version <num>.<num>.<num>-<no spaces>
131
+ /// Installed as <ANDROID_SDK_HOME>/platform-tools/adb[.exe]
132
+ /// Running on <OS/kernel version> (<CPU arch>)
133
+ /// ```
134
+ pub fn version ( mut self ) -> Result < Vec < String > , String > {
135
+ #[ cfg( debug_assertions) ]
136
+ static TRIPLE : LazyLock < Regex > = LazyLock :: new ( || {
137
+ Regex :: new ( r"^Android Debug Bridge version \d+.\d+.\d+$" )
138
+ . unwrap_or_else ( |_| unreachable ! ( ) )
139
+ } ) ;
140
+ #[ cfg( debug_assertions) ]
141
+ static DISTRO : LazyLock < Regex > = LazyLock :: new ( || {
142
+ Regex :: new ( r"^Version \d+.\d+.\d+-\S+$" ) . unwrap_or_else ( |_| unreachable ! ( ) )
143
+ } ) ;
144
+
145
+ self . 0 . arg ( "version" ) ;
146
+ Ok ( self
147
+ . run ( ) ?
148
+ . lines ( )
149
+ . enumerate ( )
150
+ // typically 5 allocs.
151
+ // ideally 0, if we didn't use `lines`.
152
+ . map ( |( i, ln) | {
153
+ debug_assert ! ( match i {
154
+ 0 => TRIPLE . is_match( ln) ,
155
+ 1 => DISTRO . is_match( ln) ,
156
+ 2 =>
157
+ // missing test for valid path
158
+ ln. starts_with( "Installed as " )
159
+ && ( ln. ends_with( "adb" ) || ln. ends_with( "adb.exe" ) ) ,
160
+ // missing test for x86/ARM (both 64b)
161
+ 3 => ln. starts_with( "Running on " ) ,
162
+ _ => unreachable!( "Expected < 5 lines" ) ,
163
+ } ) ;
164
+ ln. to_string ( )
165
+ } )
166
+ . collect ( ) )
167
+ }
168
+
110
169
/// General executor
111
170
fn run ( self ) -> Result < String , String > {
112
171
let mut cmd = self . 0 ;
@@ -118,7 +177,7 @@ impl ACommand {
118
177
cmd. get_args( )
119
178
. map( |s| s. to_str( ) . unwrap_or_else( || unreachable!( ) ) )
120
179
. collect:: <Vec <_>>( )
121
- . join( "' ' " )
180
+ . join( " " )
122
181
) ;
123
182
match cmd. output ( ) {
124
183
Err ( e) => {
0 commit comments