@aiot-toolkit/emulator 2.0.2-beta.4 → 2.0.2-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,7 +41,6 @@ const child_process_1 = require("child_process");
41
41
  const fs_extra_1 = __importDefault(require("fs-extra"));
42
42
  const os_1 = __importDefault(require("os"));
43
43
  const path_1 = __importDefault(require("path"));
44
- const portfinder_1 = __importDefault(require("portfinder"));
45
44
  const ws_1 = __importStar(require("ws"));
46
45
  const constants_1 = require("../static/constants");
47
46
  const utils_1 = require("../utils");
@@ -49,7 +48,7 @@ const common_1 = __importDefault(require("./common"));
49
48
  const MAX_RECONNECT_COUNT = 8;
50
49
  /**
51
50
  * MiwearInstance
52
- * 针对 vela4.0 的镜像
51
+ * 针对 Vela正式版(4.0)的镜像
53
52
  */
54
53
  class MiwearInstance extends common_1.default {
55
54
  constructor(params) {
@@ -58,19 +57,26 @@ class MiwearInstance extends common_1.default {
58
57
  this.appPathInEmulator = '/data/quickapp/app';
59
58
  this.reconnectCount = 0;
60
59
  }
61
- /** 在goldfish模拟器中运行快应用 */
60
+ /**
61
+ * 1. 启动模拟器
62
+ * 2. 启动成功后,adb连接模拟器
63
+ * 3. 连接成功后,在模拟器中启动快应用
64
+ */
62
65
  start(options) {
63
66
  return __awaiter(this, void 0, void 0, function* () {
64
67
  this.startOptions = options;
68
+ this.sn = `127.0.0.1:${this.startOptions.adbPort}`;
65
69
  // 启动模拟器
66
70
  yield this.startGoldfish(options);
67
71
  // adb连接模拟器
68
72
  const connected = yield this.connectGoldfish();
69
73
  if (connected) {
70
74
  ColorConsole_1.default.info('### Emulator ### Goldfish emulator connected successfully');
75
+ // 如果是首次启动,创建server端监听事件
71
76
  if (this.isFirstStart && this.startOptions.serverPort) {
72
77
  yield this.createWebsockeServer();
73
78
  }
79
+ // 在模拟器中启动快应用
74
80
  this.startupQuickApp(options);
75
81
  this.isFirstStart = false;
76
82
  }
@@ -79,31 +85,40 @@ class MiwearInstance extends common_1.default {
79
85
  }
80
86
  });
81
87
  }
82
- /** 启动goldfish模拟器 */
88
+ /**
89
+ * 启动模拟器
90
+ * 1. 通过options生成模拟器的启动命令
91
+ * 2. 执行启动命令
92
+ * 3. 判断模拟器是否启动成功
93
+ * 3.1 若disableNSH=true,输出流中匹配到/quickapp_rpk_installer_init|rpk installer init done/,认为模拟器启动成功了
94
+ * 3.2 若disableNSH=false,认为8s过后模拟器启动成功了
95
+ * @param options
96
+ * @returns
97
+ */
83
98
  startGoldfish(options) {
84
99
  var _a;
85
100
  return __awaiter(this, void 0, void 0, function* () {
86
101
  const { avdName, devtool } = options;
102
+ // 获取emulator bin的绝对路径
87
103
  const emulatorBin = this.getEmulatorBinPath();
88
104
  ColorConsole_1.default.log(`### Emulator ### emulator path: ${emulatorBin}`);
105
+ // 获取vela镜像的绝对路径
89
106
  const avdInfo = this.velaAvdCls.getVelaAvdInfo(avdName);
90
107
  const { avdArch, avdImagePath } = avdInfo;
91
- this.adbPort = yield portfinder_1.default.getPortPromise({ port: this.adbPort });
92
- ColorConsole_1.default.log(`### Emulator ### adb port: ${this.adbPort}`);
108
+ ColorConsole_1.default.log(`### Emulator ### adb port: ${options.adbPort}`);
93
109
  if (!avdImagePath) {
94
110
  return ColorConsole_1.default.throw(`### Emulator ### Unable to find vela image via avd`);
95
111
  }
96
112
  const nuttxBinPath = path_1.default.resolve(avdImagePath, 'nuttx');
97
113
  ColorConsole_1.default.log(`### Emulator ### nuttx path: ${nuttxBinPath}`);
98
- // 端口映射
99
- let portMappingStr = `-network-user-mode-options hostfwd=tcp:127.0.0.1:${this.adbPort}-10.0.2.15:5555`;
114
+ // 端口映射,adb端口和debug端口
115
+ let portMappingStr = `-network-user-mode-options hostfwd=tcp:127.0.0.1:${options.adbPort}-10.0.2.15:5555`;
100
116
  if (devtool) {
101
- this.debugPort = yield portfinder_1.default.getPortPromise({ port: this.debugPort });
102
- portMappingStr += `,hostfwd=tcp:127.0.0.1:${this.debugPort}-10.0.2.15:101`;
117
+ portMappingStr += `,hostfwd=tcp:127.0.0.1:${options.debugPort}-10.0.2.15:101`;
103
118
  }
104
119
  // 文件系统配置,第一次使用fatfs镜像挂载,后续使用adb push更新应用
105
- const systemImageBin = path_1.default.resolve(avdImagePath, 'vela_resource.img');
106
- const dataImageBin = path_1.default.resolve(avdImagePath, 'data.img');
120
+ const systemImageBin = path_1.default.resolve(avdImagePath, 'vela_resource.bin');
121
+ const dataImageBin = path_1.default.resolve(avdImagePath, 'vela_data.bin');
107
122
  const coreBin = path_1.default.resolve(avdImagePath, 'coredump.core');
108
123
  const imageMountStr = `-drive index=0,id=system,if=none,format=raw,file=${systemImageBin} \
109
124
  -device virtio-blk-device,bus=virtio-mmio-bus.0,drive=system \
@@ -120,8 +135,9 @@ class MiwearInstance extends common_1.default {
120
135
  const portSuffix = this.startOptions.vncPort - constants_1.defaultVncPort;
121
136
  vncStr = `-vnc :${portSuffix}`;
122
137
  }
138
+ // 根据disableNSH参数配置stdio
123
139
  const stdioType = options.disableNSH ? 'pipe' : 'inherit';
124
- // 启动goldfish的命令和参数
140
+ // 启动模拟器的命令和参数
125
141
  const cmd = `${emulatorBin} -nuttx -avd ${avdName} -avd-arch ${avdArch} -show-kernel -kernel ${nuttxBinPath} ${portMappingStr} ${windowStr} -qemu ${vncStr} ${imageMountStr}`;
126
142
  const spawnArgs = cmd.split(' ');
127
143
  const spawnBin = spawnArgs.shift();
@@ -129,16 +145,19 @@ class MiwearInstance extends common_1.default {
129
145
  return new Promise((resolve) => {
130
146
  var _a, _b, _c;
131
147
  this.goldfishProcess = (0, child_process_1.spawn)(spawnBin, spawnArgs, { stdio: stdioType, shell: true, cwd: this.sdkHome });
148
+ // 监听错误流
132
149
  (_a = this.goldfishProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
133
150
  const stderrCb = options.stderrCallback || console.log;
134
151
  stderrCb(data.toString());
135
152
  });
153
+ // 监听模拟器的退出事件
136
154
  this.goldfishProcess.on('exit', (code) => {
137
155
  ColorConsole_1.default.error(`### Emulator ### Goldfish emulator exited with code ${code}`);
138
156
  if (options.exitCallback) {
139
157
  options.exitCallback(code);
140
158
  }
141
159
  });
160
+ // disableNSH=false时,输出流会直接定向到终端,可在终端输入「回车」进入模拟器的终端,这种情况下无法拿到stdout中的数据
142
161
  if (options.disableNSH) {
143
162
  (_c = (_b = this.goldfishProcess) === null || _b === void 0 ? void 0 : _b.stdout) === null || _c === void 0 ? void 0 : _c.on('data', (data) => {
144
163
  const msg = data.toString();
@@ -151,6 +170,7 @@ class MiwearInstance extends common_1.default {
151
170
  });
152
171
  }
153
172
  else {
173
+ // disableNSH=true时,默认8s后模拟器启动成功
154
174
  setTimeout(() => {
155
175
  ColorConsole_1.default.info(`### Emulator ### Goldfish emulator starts successfully`);
156
176
  resolve();
@@ -159,37 +179,56 @@ class MiwearInstance extends common_1.default {
159
179
  });
160
180
  });
161
181
  }
162
- /** 通过adb连接模拟器 */
182
+ /**
183
+ * 通过adb连接模拟器。
184
+ * 时间限制为2分钟,超时则表示连接失败
185
+ * 注意:两次connect之间不能等待,否则在disableNSH=true且有vncPort的模式下,模拟器启动成功了,但是vnc可能还没连接上
186
+ * 时间限制不能太短,否则模拟器里面的adbd进程还没初始化成功
187
+ * @returns
188
+ */
163
189
  connectGoldfish() {
164
190
  return __awaiter(this, void 0, void 0, function* () {
165
- let adbConnected = false;
191
+ let adbConnected = false; // adb是否连接成功
192
+ let enableLoop = true; // 是否允许循环,用于终止adb的连接
166
193
  const connectFn = () => __awaiter(this, void 0, void 0, function* () {
167
- const sn = `127.0.0.1:${this.adbPort}`;
168
- while (!adbConnected) {
194
+ while (enableLoop && !adbConnected) {
169
195
  const adbKillCmd = `adb kill-server`;
170
196
  ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${adbKillCmd}`);
171
197
  yield adbMiwt.execAdbCmdAsync(adbKillCmd);
172
- const adbConnectCmd = `adb connect ${sn}`;
198
+ const adbConnectCmd = `adb connect ${this.sn}`;
173
199
  ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${adbConnectCmd}`);
174
200
  const str = yield adbMiwt.execAdbCmdAsync(adbConnectCmd);
175
201
  ColorConsole_1.default.log(`### Emulator ### ${str}`);
202
+ // 查询模拟器的状态是否为“device”
176
203
  const devices = yield adbMiwt.getAdbDevices();
177
204
  ColorConsole_1.default.log(`### Emulator ### adb devices: ${JSON.stringify(devices)}`);
178
205
  adbConnected =
179
- devices.filter((item) => item.sn === sn && item.status === 'device').length > 0;
206
+ devices.filter((item) => item.sn === this.sn && item.status === 'device').length > 0;
180
207
  }
181
- Promise.resolve(adbConnected);
208
+ return Promise.resolve(adbConnected);
182
209
  });
183
- yield Promise.race([
210
+ const res = yield Promise.race([
184
211
  connectFn(),
185
212
  new Promise((resolve) => {
186
- setTimeout(() => resolve(false), 600 * 1000);
213
+ setTimeout(() => {
214
+ enableLoop = false;
215
+ // 超时则认为adb没有连接成功
216
+ resolve(false);
217
+ }, 120 * 1000);
187
218
  })
188
219
  ]);
189
- return adbConnected;
220
+ return res;
190
221
  });
191
222
  }
192
- /** 在goldfish中启动快应用 */
223
+ /**
224
+ * 在模拟器中启动快应用
225
+ * 1. 检查release目录是否有打包好的rpk
226
+ * 2. 检查当前是否为调试模式.
227
+ * 若是,将debugger_ip.cfg推到模拟器的/data/目录下
228
+ * 若否,则要删除模拟器中已有的data/debugger_ip.cfg
229
+ * 3. 调用installRpkToAppList将当前快应用安装到模拟器的应用列表中
230
+ * @param options
231
+ */
193
232
  startupQuickApp(options) {
194
233
  var _a;
195
234
  return __awaiter(this, void 0, void 0, function* () {
@@ -205,41 +244,35 @@ class MiwearInstance extends common_1.default {
205
244
  }
206
245
  const rpkPath = path_1.default.resolve(releaseDir, files[0]);
207
246
  // 调试模式需要push一个文件至miwear中
208
- const sn = `127.0.0.1:${this.adbPort}`;
209
247
  if (options.devtool) {
210
248
  const debuggerCfgFile = path_1.default.join(__dirname, '../static/debugger_ip.cfg');
211
- yield adbMiwt.execAdbCmdAsync(`adb -s ${sn} push ${debuggerCfgFile} /data/debugger_ip.cfg`);
249
+ yield adbMiwt.execAdbCmdAsync(`adb -s ${this.sn} push ${debuggerCfgFile} /data/debugger_ip.cfg`);
212
250
  }
213
251
  else {
214
- adbMiwt.execAdbCmdAsync(`adb -s ${sn} shell rm /data/debugger_ip.cfg`);
252
+ adbMiwt.execAdbCmdAsync(`adb -s ${this.sn} shell rm /data/debugger_ip.cfg`);
215
253
  }
216
254
  this.installRpkToAppList(rpkPath, this.appPathInEmulator);
217
255
  });
218
256
  }
219
- /** 将快应用安装到应用列表 */
257
+ /**
258
+ * 将快应用安装到模拟器的应用列表
259
+ * 1. 使用adb push将打包好的rpk推到模拟器的/data/quickapp/app/
260
+ * 2. nsh中调用pm install命令安装应用
261
+ * @param rpkPath rpk的绝对目录
262
+ * @param targetDir 要将rpk放到模拟器的哪个目录下
263
+ */
220
264
  installRpkToAppList(rpkPath, targetDir) {
221
265
  return __awaiter(this, void 0, void 0, function* () {
222
266
  try {
223
- const sn = `127.0.0.1:${this.adbPort}`;
224
267
  const { package: packageName } = this.projectInfo;
225
- // 1. 查询应用是否已经安装,如果安装了,则先卸载
226
- const lsCmd = `adb -s ${sn} shell ls ${this.appPathInEmulator}`;
227
- const res = yield adbMiwt.execAdbCmdAsync(lsCmd);
228
- if (res.includes(packageName)) {
229
- const uninstallCmd = `adb -s ${sn} shell pm uninstall ${packageName}`;
230
- ColorConsole_1.default.info(`### Emulator ### Excuting cmd: ${uninstallCmd}`);
231
- adbMiwt.execAdbCmdAsync(uninstallCmd);
232
- // 这里等待2s的作用是等qemu执行完uninstall操作的一系列工作
233
- yield (0, utils_1.sleep)(2000);
234
- }
235
- // 2. adb push应用的rpk
268
+ // 1. adb push应用的rpk
236
269
  const targetPath = `${targetDir}/${packageName}.rpk`;
237
- const pushCmd = `adb -s ${sn} push "${rpkPath}" ${targetPath}`;
270
+ const pushCmd = `adb -s ${this.sn} push "${rpkPath}" ${targetPath}`;
238
271
  ColorConsole_1.default.info(`### Emulator ### Excuting cmd: ${pushCmd}`);
239
272
  yield adbMiwt.execAdbCmdAsync(pushCmd);
240
- yield (0, utils_1.sleep)(100);
241
- // 3. 安装应用
242
- const installCmd = `adb -s ${sn} shell pm install ${targetPath}`;
273
+ yield (0, utils_1.sleep)(1000);
274
+ // 2. 安装应用(pm install时如何应用重名会覆盖)
275
+ const installCmd = `adb -s ${this.sn} shell pm install ${targetPath}`;
243
276
  ColorConsole_1.default.info(`### Emulator ### Excuting cmd: ${installCmd}`);
244
277
  adbMiwt.execAdbCmdAsync(installCmd);
245
278
  }
@@ -248,12 +281,17 @@ class MiwearInstance extends common_1.default {
248
281
  }
249
282
  });
250
283
  }
284
+ /** 连接模拟器中的调试服务,创建debugSocket
285
+ * 主要用于热更新时,通过debugSocket通知调试服务更新页面
286
+ * 设置了重连机制,会重复连接8次
287
+ */
251
288
  initDebugSocket() {
252
289
  return new Promise((resolve, reject) => {
290
+ var _a;
253
291
  if (this.debugSocket && this.debugSocket.OPEN) {
254
292
  return resolve();
255
293
  }
256
- this.debugSocket = new ws_1.default(`ws://localhost:${this.debugPort}`);
294
+ this.debugSocket = new ws_1.default(`ws://localhost:${(_a = this.startOptions) === null || _a === void 0 ? void 0 : _a.debugPort}`);
257
295
  this.debugSocket.onopen = () => {
258
296
  ColorConsole_1.default.info(`### Emulator debugSocket connect success`);
259
297
  return resolve();
@@ -280,7 +318,11 @@ class MiwearInstance extends common_1.default {
280
318
  };
281
319
  });
282
320
  }
283
- /** 通知模拟器更新 */
321
+ /** 通知模拟器更新
322
+ * 1. 确保已经成功连接模拟器中的调试服务
323
+ * 2. 使用adb push将build文件下的内容推到/data/quickapp/app/${packageName}
324
+ * 3. 发送Page.reload命令给调试服务,通知更新
325
+ */
284
326
  handleUpdate() {
285
327
  var _a, _b;
286
328
  return __awaiter(this, void 0, void 0, function* () {
@@ -288,22 +330,7 @@ class MiwearInstance extends common_1.default {
288
330
  this.reconnectCount = 0;
289
331
  yield this.initDebugSocket();
290
332
  // 1. 将整包重新推到miwear中(TODO:增量更新)
291
- const sn = `127.0.0.1:${this.adbPort}`;
292
- const { package: appPackageName } = this.projectInfo;
293
- // windows平台adb push时无法使用通配符,需另外处理;
294
- // 项目路径包含空格时,无法通过加引号来使用 * 通配符
295
- if (os_1.default.platform() === 'win32' || this.projectPath.indexOf(' ') > 0) {
296
- const rmCmd = `adb -s ${sn} shell rm -r ${this.appPathInEmulator}/${appPackageName}`;
297
- yield adbMiwt.execAdbCmdAsync(rmCmd);
298
- const sourcePath = path_1.default.resolve(this.projectPath, './build');
299
- const pushCmd = `adb -s ${sn} push "${sourcePath}" ${this.appPathInEmulator}/${appPackageName}`;
300
- yield adbMiwt.execAdbCmdAsync(pushCmd);
301
- }
302
- else {
303
- const sourcePath = path_1.default.resolve(this.projectPath, './build/*');
304
- yield adbMiwt.execAdbCmdAsync(`adb -s ${sn} push ${sourcePath} ${this.appPathInEmulator}/${appPackageName}`);
305
- }
306
- ColorConsole_1.default.info(`### Emulator push to ${this.appPathInEmulator}/${appPackageName} successfully`);
333
+ yield this.pushBuild();
307
334
  // 2. 下发CDP命令给调试服务告知刷新
308
335
  if (((_a = this.debugSocket) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
309
336
  (_b = this.debugSocket) === null || _b === void 0 ? void 0 : _b.send(JSON.stringify({ id: 10000, method: 'Page.reload', params: {} }));
@@ -314,7 +341,55 @@ class MiwearInstance extends common_1.default {
314
341
  }
315
342
  });
316
343
  }
317
- /** 创建server */
344
+ /** 热更新时push目录 */
345
+ pushBuild() {
346
+ return __awaiter(this, void 0, void 0, function* () {
347
+ const { package: appPackageName } = this.projectInfo;
348
+ // windows平台adb push时无法使用通配符,需另外处理;
349
+ // 项目路径包含空格时,无法通过加引号来使用 * 通配符
350
+ if (os_1.default.platform() === 'win32' || this.projectPath.indexOf(' ') > 0) {
351
+ const rmCmd = `adb -s ${this.sn} shell rm -r ${this.appPathInEmulator}/${appPackageName}`;
352
+ yield adbMiwt.execAdbCmdAsync(rmCmd);
353
+ const sourcePath = path_1.default.resolve(this.projectPath, './build');
354
+ const pushCmd = `adb -s ${this.sn} push "${sourcePath}" ${this.appPathInEmulator}/${appPackageName}`;
355
+ yield adbMiwt.execAdbCmdAsync(pushCmd);
356
+ }
357
+ else {
358
+ const sourcePath = path_1.default.resolve(this.projectPath, './build/*');
359
+ yield adbMiwt.execAdbCmdAsync(`adb -s ${this.sn} push ${sourcePath} ${this.appPathInEmulator}/${appPackageName}`);
360
+ }
361
+ ColorConsole_1.default.info(`### Emulator push to ${this.appPathInEmulator}/${appPackageName} successfully`);
362
+ });
363
+ }
364
+ /** 在模拟器中重启快应用(基于am命令,需要保证镜像中已经有am功能)
365
+ * 1. 使用adb push将build文件下的内容推到/data/quickapp/app/${packageName}
366
+ * 2. nsh中执行am stop命令退出快应用
367
+ * 3. nsh中执行am start命令启动快应用
368
+ */
369
+ reloadApp() {
370
+ return __awaiter(this, void 0, void 0, function* () {
371
+ try {
372
+ // 1. 将整包重新推到miwear中(TODO:增量更新)
373
+ const { package: appPackageName } = this.projectInfo;
374
+ yield this.pushBuild();
375
+ // 2. 执行am stop和am start命令
376
+ const stopCmd = `adb -s ${this.sn} shell am stop ${appPackageName}`;
377
+ yield adbMiwt.execAdbCmdAsync(stopCmd);
378
+ ColorConsole_1.default.info(`### Emulator stop ${appPackageName} successfully`);
379
+ // 这里是为了等am stop命令清除资源等
380
+ yield (0, utils_1.sleep)(500);
381
+ const startCmd = `adb -s ${this.sn} shell am start ${appPackageName}`;
382
+ yield adbMiwt.execAdbCmdAsync(startCmd);
383
+ ColorConsole_1.default.info(`### Emulator start ${appPackageName} successfully`);
384
+ }
385
+ catch (e) {
386
+ ColorConsole_1.default.error(`${e}`);
387
+ }
388
+ });
389
+ }
390
+ /**
391
+ * 创建server端,监听打包过程中client端发来的消息
392
+ */
318
393
  createWebsockeServer() {
319
394
  var _a;
320
395
  return __awaiter(this, void 0, void 0, function* () {
@@ -326,6 +401,7 @@ class MiwearInstance extends common_1.default {
326
401
  socket.on('error', err => {
327
402
  ColorConsole_1.default.error(`### App Socket server ### Websocket server error: ${err.message}`);
328
403
  });
404
+ // data的格式:{ type: string, data: any }
329
405
  socket.on('message', (data) => __awaiter(this, void 0, void 0, function* () {
330
406
  var _a;
331
407
  const message = JSON.parse(data.toString());
@@ -2,20 +2,48 @@
2
2
  import { ChildProcess } from 'child_process';
3
3
  import { INewGoldfishInstanceParams, IStartOptions } from '../typing/Instance';
4
4
  import CommonInstance from './common';
5
+ /**
6
+ * OldGoldfishInstance
7
+ * 针对 Vela开发版(dev, 0.0.2)的镜像
8
+ */
5
9
  declare class OldGoldfishInstance extends CommonInstance {
6
10
  private host9pPort;
7
11
  v9fsProcess: ChildProcess | undefined;
8
12
  constructor(params: INewGoldfishInstanceParams);
9
- /** 在goldfish模拟器中运行快应用 */
13
+ /**
14
+ * 1. 启动9p server
15
+ * 2. 将打包好的rpk推到host的挂载目录
16
+ * 3. 启动模拟器
17
+ * 4. 模拟器启动成功后,adb连接模拟器
18
+ * 5. 连接成功后,在模拟器中启动快应用
19
+ */
10
20
  start(options: IStartOptions): Promise<void>;
11
- /** 在goldfish中启动快应用 */
21
+ /**
22
+ * 在模拟器中启动快应用(快应用都在模拟器的/data/app目录下)
23
+ * 1. 是否为调试模式
24
+ * 若是,在模拟器终端执行vapp app/${packageName} &
25
+ * 若否,在模拟器终端执行vapp --jsdebugger=10.0.2.15:101 app/${packageName} &
26
+ */
12
27
  startupQuickApp(options: IStartOptions): void;
13
- /** host启动9pServer */
28
+ /** host启动9pServer
29
+ * 作用是将本地的quickappMountDir目录挂载到模拟器的/data目录
30
+ */
14
31
  ensure9pServerRunnning(): Promise<void>;
15
- /** 启动goldfish模拟器 */
32
+ /**
33
+ * 启动模拟器
34
+ * 1. 通过options生成模拟器的启动命令
35
+ * 2. 执行启动命令
36
+ * 3. 判断模拟器是否启动成功
37
+ * 3.1 若disableNSH=true,输出流中匹配到/quickapp_rpk_installer_init|rpk installer init done/,认为模拟器启动成功了
38
+ * 3.2 若disableNSH=false,认为8s过后模拟器启动成功了
39
+ * @param options
40
+ * @returns
41
+ */
16
42
  startGoldfish(options: IStartOptions): Promise<void>;
17
- /** 通过adb连接模拟器 */
18
- connectGoldfish(): Promise<boolean>;
43
+ /**
44
+ * 推送文件到本地的${sdkHome}/qa/app/${packageName}目录
45
+ * @param sourceRoot 源目录
46
+ */
19
47
  pushRpk(sourceRoot: string): Promise<void>;
20
48
  /** 停止模拟器并释放相关资源 */
21
49
  stop(): Promise<void>;
@@ -46,25 +46,38 @@ const path_1 = __importDefault(require("path"));
46
46
  const portfinder_1 = __importDefault(require("portfinder"));
47
47
  const constants_1 = require("../static/constants");
48
48
  const common_1 = __importDefault(require("./common"));
49
+ /**
50
+ * OldGoldfishInstance
51
+ * 针对 Vela开发版(dev, 0.0.2)的镜像
52
+ */
49
53
  class OldGoldfishInstance extends common_1.default {
50
54
  constructor(params) {
51
55
  super(params);
52
56
  this.host9pPort = 7878;
53
57
  }
54
- /** 在goldfish模拟器中运行快应用 */
58
+ /**
59
+ * 1. 启动9p server
60
+ * 2. 将打包好的rpk推到host的挂载目录
61
+ * 3. 启动模拟器
62
+ * 4. 模拟器启动成功后,adb连接模拟器
63
+ * 5. 连接成功后,在模拟器中启动快应用
64
+ */
55
65
  start(options) {
56
66
  return __awaiter(this, void 0, void 0, function* () {
67
+ this.startOptions = options;
68
+ this.sn = `127.0.0.1:${this.startOptions.adbPort}`;
57
69
  // host启动9p server
58
70
  yield this.ensure9pServerRunnning();
59
- this.startOptions = options;
60
71
  // 将rpk推到host的./export/qa/app目录
61
72
  const buildedFilesPath = this.isRpk ? this.projectPath : path_1.default.resolve(this.projectPath, './build');
62
73
  this.pushRpk(buildedFilesPath);
63
74
  // 启动模拟器
64
75
  yield this.startGoldfish(options);
76
+ // adb连接快应用
65
77
  const connected = yield this.connectGoldfish();
66
78
  if (connected) {
67
79
  ColorConsole_1.default.log('### Emulator ### Goldfish emulator connected successfully');
80
+ // 如果是首次启动,创建server端监听事件
68
81
  if (this.isFirstStart && this.startOptions.serverPort) {
69
82
  yield this.createWebsockeServer();
70
83
  }
@@ -77,17 +90,23 @@ class OldGoldfishInstance extends common_1.default {
77
90
  }
78
91
  });
79
92
  }
80
- /** 在goldfish中启动快应用 */
93
+ /**
94
+ * 在模拟器中启动快应用(快应用都在模拟器的/data/app目录下)
95
+ * 1. 是否为调试模式
96
+ * 若是,在模拟器终端执行vapp app/${packageName} &
97
+ * 若否,在模拟器终端执行vapp --jsdebugger=10.0.2.15:101 app/${packageName} &
98
+ */
81
99
  startupQuickApp(options) {
82
100
  try {
83
101
  const { package: packageName } = this.projectInfo;
84
102
  const appMountDir = path_1.default.resolve(this.sdkHome, 'qa');
85
- const mountCmd = `adb -s 127.0.0.1:${this.adbPort} shell mount -t v9fs -o tag=10.0.2.2,port=${this.host9pPort},aname=${appMountDir} /data`;
103
+ const mountCmd = `adb -s 127.0.0.1:${options.adbPort} shell mount -t v9fs -o tag=10.0.2.2,port=${this.host9pPort},aname=${appMountDir} /data`;
86
104
  ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${mountCmd}`);
87
105
  adbMiwt.execAdbCmdSync(mountCmd);
88
- let vappCmd = `adb -s 127.0.0.1:${this.adbPort} shell vapp app/${packageName} &`;
106
+ let vappCmd = `adb -s 127.0.0.1:${options.adbPort} shell vapp app/${packageName} &`;
107
+ // 调试情况下,需要加--jsdebugger=10.0.2.15:101
89
108
  if (options.devtool) {
90
- vappCmd = `adb -s 127.0.0.1:${this.adbPort} shell vapp --jsdebugger=10.0.2.15:101 app/${packageName} &`;
109
+ vappCmd = `adb -s 127.0.0.1:${options.adbPort} shell vapp --jsdebugger=10.0.2.15:101 app/${packageName} &`;
91
110
  }
92
111
  ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${vappCmd}`);
93
112
  // vapp进程会一直pending,不会退出。这里必须加stdio: 'ignore',否则快应用无法运行成功
@@ -97,7 +116,9 @@ class OldGoldfishInstance extends common_1.default {
97
116
  ColorConsole_1.default.error(`### Emulator ### Failed to startup quickapp: ${e.message}`);
98
117
  }
99
118
  }
100
- /** host启动9pServer */
119
+ /** host启动9pServer
120
+ * 作用是将本地的quickappMountDir目录挂载到模拟器的/data目录
121
+ */
101
122
  ensure9pServerRunnning() {
102
123
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
103
124
  var _a;
@@ -121,6 +142,7 @@ class OldGoldfishInstance extends common_1.default {
121
142
  address,
122
143
  '--debug'
123
144
  ]);
145
+ // 监听stderr,判断9p server是否启动成功了
124
146
  (_a = this.v9fsProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
125
147
  const output = data.toString();
126
148
  if (output.match(/Server started, listening on: 127.0.0.1:(\d+)/)) {
@@ -129,35 +151,45 @@ class OldGoldfishInstance extends common_1.default {
129
151
  return resolve();
130
152
  }
131
153
  });
154
+ // 监听exit事件,判断9p server是否退出了
132
155
  this.v9fsProcess.on('exit', (code) => {
133
156
  ColorConsole_1.default.error(`### Emulator ### ya-vm-file-server exited with code ${code}`);
134
157
  return reject();
135
158
  });
136
159
  }));
137
160
  }
138
- /** 启动goldfish模拟器 */
161
+ /**
162
+ * 启动模拟器
163
+ * 1. 通过options生成模拟器的启动命令
164
+ * 2. 执行启动命令
165
+ * 3. 判断模拟器是否启动成功
166
+ * 3.1 若disableNSH=true,输出流中匹配到/quickapp_rpk_installer_init|rpk installer init done/,认为模拟器启动成功了
167
+ * 3.2 若disableNSH=false,认为8s过后模拟器启动成功了
168
+ * @param options
169
+ * @returns
170
+ */
139
171
  startGoldfish(options) {
140
172
  return __awaiter(this, void 0, void 0, function* () {
141
173
  const { avdName, devtool } = options;
174
+ // 获取emulator bin的绝对路径
142
175
  const emulatorBin = this.getEmulatorBinPath();
143
176
  ColorConsole_1.default.log(`### Emulator ### emulator path: ${emulatorBin}`);
177
+ // 获取vela镜像的绝对路径
144
178
  const avdInfo = this.velaAvdCls.getVelaAvdInfo(avdName);
145
179
  const { avdArch, avdImagePath } = avdInfo;
146
- this.adbPort = yield portfinder_1.default.getPortPromise({ port: this.adbPort });
147
- ColorConsole_1.default.log(`### Emulator ### adb port: ${this.adbPort}`);
180
+ ColorConsole_1.default.log(`### Emulator ### adb port: ${options.adbPort}`);
148
181
  if (!avdImagePath) {
149
182
  return ColorConsole_1.default.throw(`### Emulator ### Unable to find vela image via avd`);
150
183
  }
151
184
  const nuttxBinPath = path_1.default.resolve(avdImagePath, 'nuttx');
152
185
  ColorConsole_1.default.log(`### Emulator ### nuttx path: ${nuttxBinPath}`);
153
- let portMappingStr = `user,id=u1,hostfwd=tcp:127.0.0.1:${this.adbPort}-10.0.2.15:5555`;
186
+ // 端口映射,adb端口和debug端口
187
+ let portMappingStr = `user,id=u1,hostfwd=tcp:127.0.0.1:${options.adbPort}-10.0.2.15:5555`;
154
188
  if (devtool) {
155
- this.debugPort = yield portfinder_1.default.getPortPromise({ port: this.debugPort });
156
- ColorConsole_1.default.log(`### Emulator ### debug port: ${this.debugPort}`);
157
- portMappingStr += `,hostfwd=tcp:127.0.0.1:${this.debugPort}-10.0.2.15:101`;
189
+ ColorConsole_1.default.log(`### Emulator ### debug port: ${options.debugPort}`);
190
+ portMappingStr += `,hostfwd=tcp:127.0.0.1:${options.debugPort}-10.0.2.15:101`;
158
191
  }
159
192
  ColorConsole_1.default.log(`### Emulator ### Start qemu with TCP: ${portMappingStr}`);
160
- const stdioType = options.disableNSH ? 'pipe' : 'inherit';
161
193
  // vnc配置
162
194
  let noWindow = false;
163
195
  let vncStr = '';
@@ -166,6 +198,8 @@ class OldGoldfishInstance extends common_1.default {
166
198
  const portSuffix = options.vncPort - constants_1.defaultVncPort;
167
199
  vncStr = `-vnc :${portSuffix}`;
168
200
  }
201
+ // 根据disableNSH参数配置stdio
202
+ const stdioType = options.disableNSH ? 'pipe' : 'inherit';
169
203
  // 启动goldfish的命令和参数
170
204
  const spawnBin = emulatorBin;
171
205
  const spawnArgs = [
@@ -189,16 +223,19 @@ class OldGoldfishInstance extends common_1.default {
189
223
  return new Promise((resolve) => {
190
224
  var _a;
191
225
  this.goldfishProcess = (0, child_process_1.spawn)(spawnBin, spawnArgs, { stdio: stdioType, shell: true });
226
+ // 监听错误流
192
227
  (_a = this.goldfishProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
193
228
  const stderrCb = options.stderrCallback || console.log;
194
229
  stderrCb(data.toString());
195
230
  });
231
+ // 监听模拟器的退出事件
196
232
  this.goldfishProcess.on('exit', (code) => {
197
233
  ColorConsole_1.default.error(`### Emulator ### Goldfish emulator exited with code ${code}`);
198
234
  if (options.exitCallback) {
199
235
  options.exitCallback(code);
200
236
  }
201
237
  });
238
+ // 监听输出流。输出了'(NSH)'标识或者2s后则认为模拟器启动成功
202
239
  const p1 = new Promise((resolve) => {
203
240
  var _a, _b;
204
241
  (_b = (_a = this.goldfishProcess) === null || _a === void 0 ? void 0 : _a.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
@@ -218,36 +255,10 @@ class OldGoldfishInstance extends common_1.default {
218
255
  });
219
256
  });
220
257
  }
221
- /** 通过adb连接模拟器 */
222
- connectGoldfish() {
223
- return __awaiter(this, void 0, void 0, function* () {
224
- let adbConnected = false;
225
- const connectFn = () => __awaiter(this, void 0, void 0, function* () {
226
- const sn = `127.0.0.1:${this.adbPort}`;
227
- while (!adbConnected) {
228
- const adbKillCmd = `adb kill-server`;
229
- ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${adbKillCmd}`);
230
- adbMiwt.execAdbCmdSync(adbKillCmd);
231
- const adbConnectCmd = `adb connect ${sn}`;
232
- ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${adbConnectCmd}`);
233
- const str = adbMiwt.execAdbCmdSync(adbConnectCmd);
234
- ColorConsole_1.default.log(`### Emulator ### ${str}`);
235
- const devices = yield adbMiwt.getAdbDevices();
236
- ColorConsole_1.default.log(`### Emulator ### adb devices: ${JSON.stringify(devices)}`);
237
- adbConnected =
238
- devices.filter((item) => item.sn === sn && item.status === 'device').length > 0;
239
- }
240
- Promise.resolve(adbConnected);
241
- });
242
- yield Promise.race([
243
- connectFn(),
244
- new Promise((resolve) => {
245
- setTimeout(() => resolve(false), 600 * 1000);
246
- })
247
- ]);
248
- return adbConnected;
249
- });
250
- }
258
+ /**
259
+ * 推送文件到本地的${sdkHome}/qa/app/${packageName}目录
260
+ * @param sourceRoot 源目录
261
+ */
251
262
  pushRpk(sourceRoot) {
252
263
  return __awaiter(this, void 0, void 0, function* () {
253
264
  const { package: appPackageName } = this.projectInfo;
@@ -6,6 +6,8 @@ export declare const defaultSkinHome: string;
6
6
  export declare const defaultQuickappHome: string;
7
7
  export declare const defaultToolsHome: string;
8
8
  export declare const defaultVncPort = 5900;
9
+ export declare const defaultAdbPort = 5555;
10
+ export declare const defaultDebugPort = 10055;
9
11
  export declare const VelaImageVersionList: {
10
12
  label: string;
11
13
  value: string;