1
1
///<reference path="../.d.ts"/>
2
2
"use strict" ;
3
- import * as helpers from "../common/helpers" ;
4
3
import * as path from "path" ;
5
- import * as util from "util" ;
4
+ import * as net from "net" ;
5
+ import Future = require( "fibers/future" ) ;
6
6
7
7
class AndroidDebugService implements IDebugService {
8
- private static ENV_DEBUG_IN_FILENAME = "envDebug.in" ;
9
- private static ENV_DEBUG_OUT_FILENAME = "envDebug.out" ;
10
- private static DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug" ;
11
- private static PACKAGE_EXTERNAL_DIR_TEMPLATE = "/sdcard/Android/data/%s/files/" ;
8
+ private static DEFAULT_NODE_INSPECTOR_URL = "http://127.0.0.1:8080/debug" ;
12
9
13
10
private _device : Mobile . IAndroidDevice = null ;
14
11
@@ -19,13 +16,10 @@ class AndroidDebugService implements IDebugService {
19
16
private $logger : ILogger ,
20
17
private $options : IOptions ,
21
18
private $childProcess : IChildProcess ,
22
- private $mobileHelper : Mobile . IMobileHelper ,
23
- private $hostInfo : IHostInfo ,
24
- private $errors : IErrors ,
25
- private $opener : IOpener ,
26
- private $staticConfig : IStaticConfig ,
27
- private $utils : IUtils ,
28
- private $config : IConfiguration ) { }
19
+ private $hostInfo : IHostInfo ,
20
+ private $errors : IErrors ,
21
+ private $opener : IOpener ,
22
+ private $config : IConfiguration ) { }
29
23
30
24
private get platform ( ) { return "android" ; }
31
25
@@ -43,14 +37,60 @@ class AndroidDebugService implements IDebugService {
43
37
: this . debugOnDevice ( ) ;
44
38
}
45
39
46
- public debugOnEmulator ( ) : IFuture < void > {
40
+ private debugOnEmulator ( ) : IFuture < void > {
47
41
return ( ( ) => {
48
42
this . $platformService . deployOnEmulator ( this . platform ) . wait ( ) ;
49
43
this . debugOnDevice ( ) . wait ( ) ;
50
44
} ) . future < void > ( ) ( ) ;
51
45
}
52
46
53
- public debugOnDevice ( ) : IFuture < void > {
47
+ private isPortAvailable ( candidatePort : number ) : IFuture < boolean > {
48
+ let future = new Future < boolean > ( ) ;
49
+ let server = net . createServer ( ) ;
50
+ server . on ( "error" , ( err : Error ) => {
51
+ future . return ( false ) ;
52
+ } ) ;
53
+ server . listen ( candidatePort , ( err : Error ) => {
54
+ server . once ( "close" , ( ) => {
55
+ future . return ( true ) ;
56
+ } ) ;
57
+ server . close ( ) ;
58
+ } ) ;
59
+
60
+ return future ;
61
+ }
62
+
63
+ private getForwardedLocalDebugPortForPackageName ( deviceId : string , packageName : string ) : IFuture < number > {
64
+ return ( ( ) => {
65
+ let port = - 1 ;
66
+ let forwardsResult = this . device . adb . executeCommand ( [ "forward" , "--list" ] ) . wait ( ) ;
67
+
68
+ //matches 123a188909e6czzc tcp:40001 localabstract:org.nativescript.testUnixSockets-debug
69
+ let regexp = new RegExp ( `(?:${ deviceId } tcp:)([\\d]+)(?= localabstract:${ packageName } -debug)` , "g" ) ;
70
+ let match = regexp . exec ( forwardsResult ) ;
71
+ if ( match ) {
72
+ port = parseInt ( match [ 1 ] ) ;
73
+ } else {
74
+ let candidatePort = 40000 ;
75
+ while ( ! this . isPortAvailable ( candidatePort ++ ) . wait ( ) ) {
76
+ if ( candidatePort > 65534 ) {
77
+ this . $errors . failWithoutHelp ( "Unable to find free local port." ) ;
78
+ }
79
+ }
80
+ port = candidatePort ;
81
+
82
+ this . unixSocketForward ( port , packageName + "-debug" ) . wait ( ) ;
83
+ }
84
+
85
+ return port ;
86
+ } ) . future < number > ( ) ( ) ;
87
+ }
88
+
89
+ private unixSocketForward ( local : number , remote : string ) : IFuture < void > {
90
+ return this . device . adb . executeCommand ( [ "forward" , `tcp:${ local . toString ( ) } ` , `localabstract:${ remote } ` ] ) ;
91
+ }
92
+
93
+ private debugOnDevice ( ) : IFuture < void > {
54
94
return ( ( ) => {
55
95
let packageFile = "" ;
56
96
@@ -78,183 +118,102 @@ class AndroidDebugService implements IDebugService {
78
118
}
79
119
80
120
private debugCore ( device : Mobile . IAndroidDevice , packageFile : string , packageName : string ) : IFuture < void > {
81
- return ( ( ) => {
121
+ return ( ( ) => {
82
122
this . device = device ;
83
123
84
- if ( this . $options . getPort ) {
85
- this . printDebugPort ( packageName ) . wait ( ) ;
86
- } else if ( this . $options . start ) {
87
- this . attachDebugger ( packageName ) ;
88
- } else if ( this . $options . stop ) {
89
- this . detachDebugger ( packageName ) . wait ( ) ;
90
- } else if ( this . $options . debugBrk ) {
91
- this . startAppWithDebugger ( packageFile , packageName ) . wait ( ) ;
92
- }
93
- } ) . future < void > ( ) ( ) ;
94
- }
95
-
96
- private printDebugPort ( packageName : string ) : IFuture < void > {
97
- return ( ( ) => {
98
- let res = this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , packageName + "-GetDbgPort" ] ) . wait ( ) ;
99
- this . $logger . info ( res ) ;
100
- } ) . future < void > ( ) ( ) ;
101
- }
102
-
103
- private attachDebugger ( packageName : string ) : void {
104
- let startDebuggerCommand = [ "am" , "broadcast" , "-a" , '\"${packageName}-Debug\"' , "--ez" , "enable" , "true" ] ;
105
- let port = this . $options . debugPort ;
106
-
107
- if ( port > 0 ) {
108
- startDebuggerCommand . push ( "--ei" , "debuggerPort" , port . toString ( ) ) ;
109
- this . device . adb . executeShellCommand ( startDebuggerCommand ) . wait ( ) ;
110
- } else {
111
- let res = this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , packageName + "-Debug" , "--ez" , "enable" , "true" ] ) . wait ( ) ;
112
- let match = res . match ( / r e s u l t = ( \d ) + / ) ;
113
- if ( match ) {
114
- port = match [ 0 ] . substring ( 7 ) ;
115
- } else {
116
- port = 0 ;
117
- }
118
- }
119
- if ( ( 0 < port ) && ( port < 65536 ) ) {
120
- this . tcpForward ( port , port ) . wait ( ) ;
121
- this . startDebuggerClient ( port ) . wait ( ) ;
122
- this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + port ) ;
123
- } else {
124
- this . $logger . info ( "Cannot detect debug port." ) ;
125
- }
126
- }
127
-
128
- private detachDebugger ( packageName : string ) : IFuture < void > {
129
- return this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , `${ packageName } -Debug` , "--ez" , "enable" , "false" ] ) ;
130
- }
131
-
132
- private startAppWithDebugger ( packageFile : string , packageName : string ) : IFuture < void > {
133
- return ( ( ) => {
134
- if ( ! this . $options . emulator ) {
135
- this . device . applicationManager . uninstallApplication ( packageName ) . wait ( ) ;
136
- this . device . applicationManager . installApplication ( packageFile ) . wait ( ) ;
137
- }
138
- this . debugStartCore ( ) . wait ( ) ;
139
- } ) . future < void > ( ) ( ) ;
140
- }
141
-
142
- public debugStart ( ) : IFuture < void > {
143
- return ( ( ) => {
144
- this . $devicesService . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
145
- let action = ( device : Mobile . IAndroidDevice ) : IFuture < void > => {
146
- this . device = device ;
147
- return this . debugStartCore ( ) ;
148
- } ;
149
- this . $devicesService . execute ( action ) . wait ( ) ;
150
- } ) . future < void > ( ) ( ) ;
151
- }
152
-
153
- private debugStartCore ( ) : IFuture < void > {
154
- return ( ( ) => {
155
- let packageName = this . $projectData . projectId ;
156
- let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
157
- let envDebugOutFullpath = this . $mobileHelper . buildDevicePath ( packageDir , AndroidDebugService . ENV_DEBUG_OUT_FILENAME ) ;
158
-
159
- this . device . adb . executeShellCommand ( [ "rm" , `${ envDebugOutFullpath } ` ] ) . wait ( ) ;
160
- this . device . adb . executeShellCommand ( [ "mkdir" , "-p" , `${ packageDir } ` ] ) . wait ( ) ;
161
-
162
- let debugBreakPath = this . $mobileHelper . buildDevicePath ( packageDir , "debugbreak" ) ;
163
- this . device . adb . executeShellCommand ( [ `cat /dev/null > ${ debugBreakPath } ` ] ) . wait ( ) ;
164
-
165
- this . device . applicationManager . stopApplication ( packageName ) . wait ( ) ;
166
- this . device . applicationManager . startApplication ( packageName ) . wait ( ) ;
167
-
168
- let dbgPort = this . startAndGetPort ( packageName ) . wait ( ) ;
169
- if ( dbgPort > 0 ) {
170
- this . tcpForward ( dbgPort , dbgPort ) . wait ( ) ;
171
- this . startDebuggerClient ( dbgPort ) . wait ( ) ;
172
- this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + dbgPort ) ;
173
- }
174
- } ) . future < void > ( ) ( ) ;
175
- }
176
-
177
- private tcpForward ( src : Number , dest : Number ) : IFuture < void > {
178
- return this . device . adb . executeCommand ( [ "forward" , `tcp:${ src . toString ( ) } ` , `tcp:${ dest . toString ( ) } ` ] ) ;
179
- }
180
-
181
- private startDebuggerClient ( port : Number ) : IFuture < void > {
182
- return ( ( ) => {
183
- let nodeInspectorModuleFilePath = require . resolve ( "node-inspector" ) ;
184
- let nodeInspectorModuleDir = path . dirname ( nodeInspectorModuleFilePath ) ;
185
- let nodeInspectorFullPath = path . join ( nodeInspectorModuleDir , "bin" , "inspector" ) ;
186
- this . $childProcess . spawn ( process . argv [ 0 ] , [ nodeInspectorFullPath , "--debug-port" , port . toString ( ) ] , { stdio : "ignore" , detached : true } ) ;
187
- } ) . future < void > ( ) ( ) ;
188
- }
189
-
190
- private openDebuggerClient ( url : string ) : void {
191
- let defaultDebugUI = "chrome" ;
192
- if ( this . $hostInfo . isDarwin ) {
193
- defaultDebugUI = "Google Chrome" ;
194
- }
195
- if ( this . $hostInfo . isLinux ) {
196
- defaultDebugUI = "google-chrome" ;
197
- }
124
+ if ( this . $options . getPort ) {
125
+ this . printDebugPort ( device . deviceInfo . identifier , packageName ) . wait ( ) ;
126
+ } else if ( this . $options . start ) {
127
+ this . attachDebugger ( device . deviceInfo . identifier , packageName ) . wait ( ) ;
128
+ } else if ( this . $options . stop ) {
129
+ this . detachDebugger ( packageName ) . wait ( ) ;
130
+ } else if ( this . $options . debugBrk ) {
131
+ this . startAppWithDebugger ( packageFile , packageName ) . wait ( ) ;
132
+ }
133
+ } ) . future < void > ( ) ( ) ;
134
+ }
135
+
136
+ private printDebugPort ( deviceId : string , packageName : string ) : IFuture < void > {
137
+ return ( ( ) => {
138
+ let port = this . getForwardedLocalDebugPortForPackageName ( deviceId , packageName ) . wait ( ) ;
139
+ this . $logger . info ( port ) ;
140
+ } ) . future < void > ( ) ( ) ;
141
+ }
142
+
143
+ private attachDebugger ( deviceId : string , packageName : string ) : IFuture < void > {
144
+ return ( ( ) => {
145
+ let startDebuggerCommand = [ "am" , "broadcast" , "-a" , '\"${packageName}-debug\"' , "--ez" , "enable" , "true" ] ;
146
+ this . device . adb . executeShellCommand ( startDebuggerCommand ) . wait ( ) ;
147
+
148
+ let port = this . getForwardedLocalDebugPortForPackageName ( deviceId , packageName ) . wait ( ) ;
149
+ this . startDebuggerClient ( port ) . wait ( ) ;
150
+ this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + port ) ;
151
+ } ) . future < void > ( ) ( ) ;
152
+ }
153
+
154
+ private detachDebugger ( packageName : string ) : IFuture < void > {
155
+ return this . device . adb . executeShellCommand ( [ "am" , "broadcast" , "-a" , `${ packageName } -debug` , "--ez" , "enable" , "false" ] ) ;
156
+ }
157
+
158
+ private startAppWithDebugger ( packageFile : string , packageName : string ) : IFuture < void > {
159
+ return ( ( ) => {
160
+ if ( ! this . $options . emulator ) {
161
+ this . device . applicationManager . uninstallApplication ( packageName ) . wait ( ) ;
162
+ this . device . applicationManager . installApplication ( packageFile ) . wait ( ) ;
163
+ }
164
+ this . debugStartCore ( ) . wait ( ) ;
165
+ } ) . future < void > ( ) ( ) ;
166
+ }
167
+
168
+ public debugStart ( ) : IFuture < void > {
169
+ return ( ( ) => {
170
+ this . $devicesService . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
171
+ let action = ( device : Mobile . IAndroidDevice ) : IFuture < void > => {
172
+ this . device = device ;
173
+ return this . debugStartCore ( ) ;
174
+ } ;
175
+ this . $devicesService . execute ( action ) . wait ( ) ;
176
+ } ) . future < void > ( ) ( ) ;
177
+ }
178
+
179
+ private debugStartCore ( ) : IFuture < void > {
180
+ return ( ( ) => {
181
+ let packageName = this . $projectData . projectId ;
182
+
183
+ this . device . adb . executeShellCommand ( [ `cat /dev/null > /data/local/tmp/${ packageName } -debugbreak` ] ) . wait ( ) ;
184
+
185
+ this . device . applicationManager . stopApplication ( packageName ) . wait ( ) ;
186
+ this . device . applicationManager . startApplication ( packageName ) . wait ( ) ;
187
+
188
+ let localDebugPort = this . getForwardedLocalDebugPortForPackageName ( this . device . deviceInfo . identifier , packageName ) . wait ( ) ;
189
+ this . startDebuggerClient ( localDebugPort ) . wait ( ) ;
190
+ this . openDebuggerClient ( AndroidDebugService . DEFAULT_NODE_INSPECTOR_URL + "?port=" + localDebugPort ) ;
191
+ } ) . future < void > ( ) ( ) ;
192
+ }
193
+
194
+ private startDebuggerClient ( port : Number ) : IFuture < void > {
195
+ return ( ( ) => {
196
+ let nodeInspectorModuleFilePath = require . resolve ( "node-inspector" ) ;
197
+ let nodeInspectorModuleDir = path . dirname ( nodeInspectorModuleFilePath ) ;
198
+ let nodeInspectorFullPath = path . join ( nodeInspectorModuleDir , "bin" , "inspector" ) ;
199
+ this . $childProcess . spawn ( process . argv [ 0 ] , [ nodeInspectorFullPath , "--debug-port" , port . toString ( ) ] , { stdio : "ignore" , detached : true } ) ;
200
+ } ) . future < void > ( ) ( ) ;
201
+ }
202
+
203
+ private openDebuggerClient ( url : string ) : void {
204
+ let defaultDebugUI = "chrome" ;
205
+ if ( this . $hostInfo . isDarwin ) {
206
+ defaultDebugUI = "Google Chrome" ;
207
+ }
208
+ if ( this . $hostInfo . isLinux ) {
209
+ defaultDebugUI = "google-chrome" ;
210
+ }
198
211
199
212
let debugUI = this . $config . ANDROID_DEBUG_UI || defaultDebugUI ;
200
213
let child = this . $opener . open ( url , debugUI ) ;
201
214
if ( ! child ) {
202
215
this . $errors . failWithoutHelp ( `Unable to open ${ debugUI } .` ) ;
203
216
}
204
- }
205
-
206
- private checkIfRunning ( packageName : string ) : boolean {
207
- let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
208
- let envDebugOutFullpath = packageDir + AndroidDebugService . ENV_DEBUG_OUT_FILENAME ;
209
- let isRunning = this . checkIfFileExists ( envDebugOutFullpath ) . wait ( ) ;
210
- return isRunning ;
211
- }
212
-
213
- private checkIfFileExists ( filename : string ) : IFuture < boolean > {
214
- return ( ( ) => {
215
- let res = this . device . adb . executeShellCommand ( [ `test -f ${ filename } && echo 'yes' || echo 'no'` ] ) . wait ( ) ;
216
- let exists = res . indexOf ( 'yes' ) > - 1 ;
217
- return exists ;
218
- } ) . future < boolean > ( ) ( ) ;
219
- }
220
-
221
- private startAndGetPort ( packageName : string ) : IFuture < number > {
222
- return ( ( ) => {
223
- let port = - 1 ;
224
- let timeout = this . $utils . getParsedTimeout ( 90 ) ;
225
-
226
- let packageDir = util . format ( AndroidDebugService . PACKAGE_EXTERNAL_DIR_TEMPLATE , packageName ) ;
227
- let envDebugInFullpath = packageDir + AndroidDebugService . ENV_DEBUG_IN_FILENAME ;
228
- this . device . adb . executeShellCommand ( [ "rm" , `${ envDebugInFullpath } ` ] ) . wait ( ) ;
229
-
230
- let isRunning = false ;
231
- for ( let i = 0 ; i < timeout ; i ++ ) {
232
- helpers . sleep ( 1000 /* ms */ ) ;
233
- isRunning = this . checkIfRunning ( packageName ) ;
234
- if ( isRunning ) {
235
- break ;
236
- }
237
- }
238
-
239
- if ( isRunning ) {
240
- this . device . adb . executeShellCommand ( [ `cat /dev/null > ${ envDebugInFullpath } ` ] ) . wait ( ) ;
241
-
242
- for ( let i = 0 ; i < timeout ; i ++ ) {
243
- helpers . sleep ( 1000 /* ms */ ) ;
244
- let envDebugOutFullpath = packageDir + AndroidDebugService . ENV_DEBUG_OUT_FILENAME ;
245
- let exists = this . checkIfFileExists ( envDebugOutFullpath ) . wait ( ) ;
246
- if ( exists ) {
247
- let res = this . device . adb . executeShellCommand ( [ "cat" , envDebugOutFullpath ] ) . wait ( ) ;
248
- let match = res . match ( / P O R T = ( \d ) + / ) ;
249
- if ( match ) {
250
- port = parseInt ( match [ 0 ] . substring ( 5 ) , 10 ) ;
251
- break ;
252
- }
253
- }
254
- }
255
- }
256
- return port ;
257
- } ) . future < number > ( ) ( ) ;
258
- }
217
+ }
259
218
}
260
219
$injector . register ( "androidDebugService" , AndroidDebugService ) ;
0 commit comments