@aiot-toolkit/emulator 2.0.2-beta.3 → 2.0.2-beta.5

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.
package/README.md CHANGED
@@ -1,2 +1,15 @@
1
+ ## emulator
1
2
 
2
- emulator tool
3
+ QEMU模拟器
4
+
5
+ 模拟器的介绍可参考[开发帮助文档](https://xiaomi.f.mioffice.cn/docx/doxk4Rk6x67GanHlzQ8bEOrxtEe)里的「模拟器」章节
6
+
7
+ ## 目录结构
8
+
9
+ | 目录 | 描述 |
10
+ | -------- | ------------------------------------------------------------------------------- |
11
+ | avd | 模拟器的AVD,配置统一放置$HOME/.android/avd目录下 |
12
+ | instance | 模拟器实例,不同的Vela镜像版本会使用不同的instance,通过findInstance确定 |
13
+ | static | 创建AVD时需要用到的静态资源,常量配置文件 |
14
+ | typing | 接口定义 |
15
+ | utils | 工具函数 |
@@ -3,10 +3,22 @@ declare class VelaAvdCls {
3
3
  private avdHome;
4
4
  private sdkHome;
5
5
  constructor(avdResourcePaths: IAvdResourcePaths);
6
+ /**
7
+ * 创建Vela端的AVD,统一保存在.android目录下
8
+ * 1. 创建.android/advancedFeatures.ini文件
9
+ * 2. 创建.android/${avdName}.ini文件
10
+ * 3. 创建.android/${avdName}.avd/config.ini文件
11
+ * @param avdParams AVD参数,宽高、绑定的镜像路径等
12
+ * @returns
13
+ */
6
14
  createVelaAvd(avdParams: IAvdParams): boolean;
15
+ /** 根据AVD名字获取模拟器的详细信息 */
7
16
  getVelaAvdInfo(avdName: string): IAvdParams;
17
+ /** 根据名字删除AVD */
8
18
  deleteVelaAvd(avdName: string): boolean;
19
+ /** 获取已经创建的模拟器列表 */
9
20
  getVelaAvdList(): IAvdParams[];
21
+ /** 获取模拟器皮肤列表 */
10
22
  getVelaSkinList(): string[];
11
23
  }
12
24
  export default VelaAvdCls;
package/lib/avd/index.js CHANGED
@@ -26,6 +26,14 @@ class VelaAvdCls {
26
26
  fs_1.default.mkdirSync(this.avdHome, { recursive: true });
27
27
  }
28
28
  }
29
+ /**
30
+ * 创建Vela端的AVD,统一保存在.android目录下
31
+ * 1. 创建.android/advancedFeatures.ini文件
32
+ * 2. 创建.android/${avdName}.ini文件
33
+ * 3. 创建.android/${avdName}.avd/config.ini文件
34
+ * @param avdParams AVD参数,宽高、绑定的镜像路径等
35
+ * @returns
36
+ */
29
37
  createVelaAvd(avdParams) {
30
38
  // 在.android下创建advancedFeatures.ini文件
31
39
  const advancedFeaturesIni = path_1.default.resolve(os_1.default.homedir(), '.android/advancedFeatures.ini');
@@ -76,6 +84,7 @@ class VelaAvdCls {
76
84
  throw (`createVelaAvd: ${e.message}`);
77
85
  }
78
86
  }
87
+ /** 根据AVD名字获取模拟器的详细信息 */
79
88
  getVelaAvdInfo(avdName) {
80
89
  const avdInfo = {
81
90
  avdName,
@@ -112,6 +121,7 @@ class VelaAvdCls {
112
121
  return avdInfo;
113
122
  }
114
123
  }
124
+ /** 根据名字删除AVD */
115
125
  deleteVelaAvd(avdName) {
116
126
  const avdDir = path_1.default.resolve(this.avdHome, `${avdName}.avd`);
117
127
  const avdIni = path_1.default.resolve(this.avdHome, `${avdName}.ini`);
@@ -124,6 +134,7 @@ class VelaAvdCls {
124
134
  return false;
125
135
  }
126
136
  }
137
+ /** 获取已经创建的模拟器列表 */
127
138
  getVelaAvdList() {
128
139
  const avdList = [];
129
140
  const files = fs_1.default.readdirSync(this.avdHome);
@@ -138,6 +149,7 @@ class VelaAvdCls {
138
149
  }
139
150
  return avdList;
140
151
  }
152
+ /** 获取模拟器皮肤列表 */
141
153
  getVelaSkinList() {
142
154
  try {
143
155
  const skinList = [];
@@ -3,5 +3,11 @@ import CommonInstance from './common';
3
3
  import GoldfishInstance from './dev';
4
4
  import MiwearInstance from "./miwear";
5
5
  import OldGoldfishInstance from './preDev';
6
+ /**
7
+ * 根据镜像决定使用哪个instance
8
+ * Vela正式版(4.0) -> MiwearInstance
9
+ * Vela开发版(dev, 0.0.2) -> OldGoldfishInstance
10
+ * Vela开发版(dev),除0.0.2的其他版本 -> GoldfishInstance
11
+ */
6
12
  declare function findInstance(avdName: string, params: INewGoldfishInstanceParams): GoldfishInstance | MiwearInstance | OldGoldfishInstance | undefined;
7
13
  export { CommonInstance, GoldfishInstance, MiwearInstance, OldGoldfishInstance, findInstance };
@@ -14,6 +14,12 @@ const miwear_1 = __importDefault(require("./miwear"));
14
14
  exports.MiwearInstance = miwear_1.default;
15
15
  const preDev_1 = __importDefault(require("./preDev"));
16
16
  exports.OldGoldfishInstance = preDev_1.default;
17
+ /**
18
+ * 根据镜像决定使用哪个instance
19
+ * Vela正式版(4.0) -> MiwearInstance
20
+ * Vela开发版(dev, 0.0.2) -> OldGoldfishInstance
21
+ * Vela开发版(dev),除0.0.2的其他版本 -> GoldfishInstance
22
+ */
17
23
  function findInstance(avdName, params) {
18
24
  const { sdkHome, avdHome } = params;
19
25
  const { avdImagePath } = new avd_1.default({ sdkHome, avdHome }).getVelaAvdInfo(avdName);
@@ -2,27 +2,69 @@ import { INewGoldfishInstanceParams, IStartOptions } from '../typing/Instance';
2
2
  import CommonInstance from './common';
3
3
  /**
4
4
  * MiwearInstance
5
- * 针对 vela4.0 的镜像
5
+ * 针对 Vela正式版(4.0)的镜像
6
6
  */
7
7
  declare class MiwearInstance extends CommonInstance {
8
+ private params;
8
9
  private appPathInEmulator;
9
10
  private debugSocket?;
10
11
  private reconnectCount;
11
12
  constructor(params: INewGoldfishInstanceParams);
12
- /** 在goldfish模拟器中运行快应用 */
13
+ /**
14
+ * 1. 启动模拟器
15
+ * 2. 启动成功后,adb连接模拟器
16
+ * 3. 连接成功后,在模拟器中启动快应用
17
+ */
13
18
  start(options: IStartOptions): Promise<void>;
14
- /** 启动goldfish模拟器 */
19
+ /**
20
+ * 启动模拟器
21
+ * 1. 通过options生成模拟器的启动命令
22
+ * 2. 执行启动命令
23
+ * 3. 判断模拟器是否启动成功
24
+ * 3.1 若disableNSH=true,输出流中匹配到/quickapp_rpk_installer_init|rpk installer init done/,认为模拟器启动成功了
25
+ * 3.2 若disableNSH=false,认为8s过后模拟器启动成功了
26
+ * @param options
27
+ * @returns
28
+ */
15
29
  startGoldfish(options: IStartOptions): Promise<void>;
16
- /** 通过adb连接模拟器 */
30
+ /**
31
+ * 通过adb连接模拟器。
32
+ * 时间限制为10分钟,超时则表示连接失败
33
+ * @returns
34
+ */
17
35
  connectGoldfish(): Promise<boolean>;
18
- /** 在goldfish中启动快应用 */
36
+ /**
37
+ * 在模拟器中启动快应用
38
+ * 1. 检查release目录是否有打包好的rpk
39
+ * 2. 检查当前是否为调试模式.
40
+ * 若是,将debugger_ip.cfg推到模拟器的/data/目录下
41
+ * 若否,则要删除模拟器中已有的data/debugger_ip.cfg
42
+ * 3. 调用installRpkToAppList将当前快应用安装到模拟器的应用列表中
43
+ * @param options
44
+ */
19
45
  startupQuickApp(options: IStartOptions): Promise<void>;
20
- /** 将快应用安装到应用列表 */
46
+ /**
47
+ * 将快应用安装到模拟器的应用列表
48
+ * 1. 使用adb push将打包好的rpk推到模拟器的/data/quickapp/app/
49
+ * 2. nsh中调用pm install命令安装应用
50
+ * @param rpkPath rpk的绝对目录
51
+ * @param targetDir 要将rpk放到模拟器的哪个目录下
52
+ */
21
53
  installRpkToAppList(rpkPath: string, targetDir: string): Promise<void>;
22
- initDebugSocket(): Promise<void> | undefined;
23
- /** 通知模拟器更新 */
54
+ /** 连接模拟器中的调试服务,创建debugSocket
55
+ * 主要用于热更新时,通过debugSocket通知调试服务更新页面
56
+ * 设置了重连机制,会重复连接8次
57
+ */
58
+ initDebugSocket(): Promise<void>;
59
+ /** 通知模拟器更新
60
+ * 1. 确保已经成功连接模拟器中的调试服务
61
+ * 2. 使用adb push将build文件下的内容推到/data/quickapp/app/${packageName}
62
+ * 3. 发送Page.reload命令给调试服务,通知更新
63
+ */
24
64
  handleUpdate(): Promise<void>;
25
- /** 创建server */
65
+ /**
66
+ * 创建server端,监听打包过程中client端发来的消息
67
+ */
26
68
  createWebsockeServer(): Promise<void>;
27
69
  }
28
70
  export default MiwearInstance;
@@ -35,7 +35,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- const JavascriptDefaultCompileOption_1 = __importDefault(require("@aiot-toolkit/aiotpack/lib/compiler/javascript/JavascriptDefaultCompileOption"));
39
38
  const ColorConsole_1 = __importDefault(require("@aiot-toolkit/shared-utils/lib/ColorConsole"));
40
39
  const adbMiwt = __importStar(require("@miwt/adb"));
41
40
  const child_process_1 = require("child_process");
@@ -50,15 +49,20 @@ const common_1 = __importDefault(require("./common"));
50
49
  const MAX_RECONNECT_COUNT = 8;
51
50
  /**
52
51
  * MiwearInstance
53
- * 针对 vela4.0 的镜像
52
+ * 针对 Vela正式版(4.0)的镜像
54
53
  */
55
54
  class MiwearInstance extends common_1.default {
56
55
  constructor(params) {
57
56
  super(params);
57
+ this.params = params;
58
58
  this.appPathInEmulator = '/data/quickapp/app';
59
59
  this.reconnectCount = 0;
60
60
  }
61
- /** 在goldfish模拟器中运行快应用 */
61
+ /**
62
+ * 1. 启动模拟器
63
+ * 2. 启动成功后,adb连接模拟器
64
+ * 3. 连接成功后,在模拟器中启动快应用
65
+ */
62
66
  start(options) {
63
67
  return __awaiter(this, void 0, void 0, function* () {
64
68
  this.startOptions = options;
@@ -68,9 +72,11 @@ class MiwearInstance extends common_1.default {
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,13 +85,24 @@ 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
108
  this.adbPort = yield portfinder_1.default.getPortPromise({ port: this.adbPort });
@@ -95,15 +112,15 @@ class MiwearInstance extends common_1.default {
95
112
  }
96
113
  const nuttxBinPath = path_1.default.resolve(avdImagePath, 'nuttx');
97
114
  ColorConsole_1.default.log(`### Emulator ### nuttx path: ${nuttxBinPath}`);
98
- // 端口映射
115
+ // 端口映射,adb端口和debug端口
99
116
  let portMappingStr = `-network-user-mode-options hostfwd=tcp:127.0.0.1:${this.adbPort}-10.0.2.15:5555`;
100
117
  if (devtool) {
101
118
  this.debugPort = yield portfinder_1.default.getPortPromise({ port: this.debugPort });
102
119
  portMappingStr += `,hostfwd=tcp:127.0.0.1:${this.debugPort}-10.0.2.15:101`;
103
120
  }
104
121
  // 文件系统配置,第一次使用fatfs镜像挂载,后续使用adb push更新应用
105
- const systemImageBin = path_1.default.resolve(avdImagePath, 'vela_resource.img');
106
- const dataImageBin = path_1.default.resolve(avdImagePath, 'data.img');
122
+ const systemImageBin = path_1.default.resolve(avdImagePath, 'vela_resource.bin');
123
+ const dataImageBin = path_1.default.resolve(avdImagePath, 'vela_data.bin');
107
124
  const coreBin = path_1.default.resolve(avdImagePath, 'coredump.core');
108
125
  const imageMountStr = `-drive index=0,id=system,if=none,format=raw,file=${systemImageBin} \
109
126
  -device virtio-blk-device,bus=virtio-mmio-bus.0,drive=system \
@@ -120,8 +137,9 @@ class MiwearInstance extends common_1.default {
120
137
  const portSuffix = this.startOptions.vncPort - constants_1.defaultVncPort;
121
138
  vncStr = `-vnc :${portSuffix}`;
122
139
  }
140
+ // 根据disableNSH参数配置stdio
123
141
  const stdioType = options.disableNSH ? 'pipe' : 'inherit';
124
- // 启动goldfish的命令和参数
142
+ // 启动模拟器的命令和参数
125
143
  const cmd = `${emulatorBin} -nuttx -avd ${avdName} -avd-arch ${avdArch} -show-kernel -kernel ${nuttxBinPath} ${portMappingStr} ${windowStr} -qemu ${vncStr} ${imageMountStr}`;
126
144
  const spawnArgs = cmd.split(' ');
127
145
  const spawnBin = spawnArgs.shift();
@@ -129,16 +147,19 @@ class MiwearInstance extends common_1.default {
129
147
  return new Promise((resolve) => {
130
148
  var _a, _b, _c;
131
149
  this.goldfishProcess = (0, child_process_1.spawn)(spawnBin, spawnArgs, { stdio: stdioType, shell: true, cwd: this.sdkHome });
150
+ // 监听错误流
132
151
  (_a = this.goldfishProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
133
152
  const stderrCb = options.stderrCallback || console.log;
134
153
  stderrCb(data.toString());
135
154
  });
155
+ // 监听模拟器的退出事件
136
156
  this.goldfishProcess.on('exit', (code) => {
137
157
  ColorConsole_1.default.error(`### Emulator ### Goldfish emulator exited with code ${code}`);
138
158
  if (options.exitCallback) {
139
159
  options.exitCallback(code);
140
160
  }
141
161
  });
162
+ // disableNSH=false时,输出流会直接定向到终端,可在终端输入「回车」进入模拟器的终端,这种情况下无法拿到stdout中的数据
142
163
  if (options.disableNSH) {
143
164
  (_c = (_b = this.goldfishProcess) === null || _b === void 0 ? void 0 : _b.stdout) === null || _c === void 0 ? void 0 : _c.on('data', (data) => {
144
165
  const msg = data.toString();
@@ -151,17 +172,20 @@ class MiwearInstance extends common_1.default {
151
172
  });
152
173
  }
153
174
  else {
175
+ // disableNSH=true时,默认8s后模拟器启动成功
154
176
  setTimeout(() => {
155
177
  ColorConsole_1.default.info(`### Emulator ### Goldfish emulator starts successfully`);
156
178
  resolve();
157
179
  }, 8000);
158
- ColorConsole_1.default.info(`### Emulator ### Goldfish emulator starts successfully`);
159
- resolve();
160
180
  }
161
181
  });
162
182
  });
163
183
  }
164
- /** 通过adb连接模拟器 */
184
+ /**
185
+ * 通过adb连接模拟器。
186
+ * 时间限制为10分钟,超时则表示连接失败
187
+ * @returns
188
+ */
165
189
  connectGoldfish() {
166
190
  return __awaiter(this, void 0, void 0, function* () {
167
191
  let adbConnected = false;
@@ -175,6 +199,7 @@ class MiwearInstance extends common_1.default {
175
199
  ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${adbConnectCmd}`);
176
200
  const str = yield adbMiwt.execAdbCmdAsync(adbConnectCmd);
177
201
  ColorConsole_1.default.log(`### Emulator ### ${str}`);
202
+ // 查询模拟器的状态是否为“device”
178
203
  const devices = yield adbMiwt.getAdbDevices();
179
204
  ColorConsole_1.default.log(`### Emulator ### adb devices: ${JSON.stringify(devices)}`);
180
205
  adbConnected =
@@ -191,15 +216,26 @@ class MiwearInstance extends common_1.default {
191
216
  return adbConnected;
192
217
  });
193
218
  }
194
- /** 在goldfish中启动快应用 */
219
+ /**
220
+ * 在模拟器中启动快应用
221
+ * 1. 检查release目录是否有打包好的rpk
222
+ * 2. 检查当前是否为调试模式.
223
+ * 若是,将debugger_ip.cfg推到模拟器的/data/目录下
224
+ * 若否,则要删除模拟器中已有的data/debugger_ip.cfg
225
+ * 3. 调用installRpkToAppList将当前快应用安装到模拟器的应用列表中
226
+ * @param options
227
+ */
195
228
  startupQuickApp(options) {
229
+ var _a;
196
230
  return __awaiter(this, void 0, void 0, function* () {
197
231
  const { package: appPackageName } = this.projectInfo;
198
- const releaseDir = path_1.default.resolve(this.projectPath, JavascriptDefaultCompileOption_1.default.releasePath || 'dist');
232
+ const releaseDir = this.isRpk
233
+ ? path_1.default.resolve(this.projectPath, '../')
234
+ : path_1.default.resolve(this.projectPath, ((_a = this.params.compilerOption) === null || _a === void 0 ? void 0 : _a.releasePath) || 'dist');
199
235
  const files = fs_extra_1.default
200
236
  .readdirSync(releaseDir)
201
237
  .filter(item => item.includes(appPackageName) && item.endsWith('.rpk'));
202
- if (files.length < 0) {
238
+ if (files.length === 0) {
203
239
  ColorConsole_1.default.error(`### Emulator the rpk does not exist`);
204
240
  }
205
241
  const rpkPath = path_1.default.resolve(releaseDir, files[0]);
@@ -215,68 +251,75 @@ class MiwearInstance extends common_1.default {
215
251
  this.installRpkToAppList(rpkPath, this.appPathInEmulator);
216
252
  });
217
253
  }
218
- /** 将快应用安装到应用列表 */
254
+ /**
255
+ * 将快应用安装到模拟器的应用列表
256
+ * 1. 使用adb push将打包好的rpk推到模拟器的/data/quickapp/app/
257
+ * 2. nsh中调用pm install命令安装应用
258
+ * @param rpkPath rpk的绝对目录
259
+ * @param targetDir 要将rpk放到模拟器的哪个目录下
260
+ */
219
261
  installRpkToAppList(rpkPath, targetDir) {
220
262
  return __awaiter(this, void 0, void 0, function* () {
221
263
  try {
222
264
  const sn = `127.0.0.1:${this.adbPort}`;
223
265
  const { package: packageName } = this.projectInfo;
224
- const rpkName = path_1.default.basename(rpkPath);
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
266
+ // 1. adb push应用的rpk
236
267
  const targetPath = `${targetDir}/${packageName}.rpk`;
237
- const pushCmd = `adb -s ${sn} push ${rpkPath} ${targetPath}`;
268
+ const pushCmd = `adb -s ${sn} push "${rpkPath}" ${targetPath}`;
238
269
  ColorConsole_1.default.info(`### Emulator ### Excuting cmd: ${pushCmd}`);
239
270
  yield adbMiwt.execAdbCmdAsync(pushCmd);
240
- yield (0, utils_1.sleep)(100);
241
- // 3. 安装应用
271
+ yield (0, utils_1.sleep)(1000);
272
+ // 2. 安装应用(pm install时如何应用重名会覆盖)
242
273
  const installCmd = `adb -s ${sn} shell pm install ${targetPath}`;
243
274
  ColorConsole_1.default.info(`### Emulator ### Excuting cmd: ${installCmd}`);
244
275
  adbMiwt.execAdbCmdAsync(installCmd);
245
276
  }
246
- catch (e) { }
277
+ catch (e) {
278
+ ColorConsole_1.default.error(`### Emulator ### ${e.message}`);
279
+ }
247
280
  });
248
281
  }
282
+ /** 连接模拟器中的调试服务,创建debugSocket
283
+ * 主要用于热更新时,通过debugSocket通知调试服务更新页面
284
+ * 设置了重连机制,会重复连接8次
285
+ */
249
286
  initDebugSocket() {
250
- if (this.debugSocket && this.debugSocket.OPEN) {
251
- return Promise.resolve();
252
- }
253
- this.debugSocket = new ws_1.default(`ws://localhost:${this.debugPort}`);
254
- this.debugSocket.onopen = () => {
255
- ColorConsole_1.default.info(`### Emulator debugSocket connect success`);
256
- return Promise.resolve();
257
- };
258
- this.debugSocket.onerror = (e) => {
259
- var _a;
260
- // 重连机制
261
- (_a = this.debugSocket) === null || _a === void 0 ? void 0 : _a.terminate();
262
- if (this.reconnectCount < MAX_RECONNECT_COUNT) {
263
- ColorConsole_1.default.info(`### Emulator the ${this.reconnectCount + 1}th time to reconnect debug server`);
264
- this.reconnectCount++;
265
- setTimeout(() => this.initDebugSocket(), 2000);
287
+ return new Promise((resolve, reject) => {
288
+ if (this.debugSocket && this.debugSocket.OPEN) {
289
+ return resolve();
266
290
  }
267
- else {
291
+ this.debugSocket = new ws_1.default(`ws://localhost:${this.debugPort}`);
292
+ this.debugSocket.onopen = () => {
293
+ ColorConsole_1.default.info(`### Emulator debugSocket connect success`);
294
+ return resolve();
295
+ };
296
+ this.debugSocket.onerror = (errorEvent) => {
297
+ var _a;
298
+ // 重连机制
299
+ (_a = this.debugSocket) === null || _a === void 0 ? void 0 : _a.terminate();
300
+ if (this.reconnectCount < MAX_RECONNECT_COUNT) {
301
+ ColorConsole_1.default.info(`### Emulator the ${this.reconnectCount + 1}th time to reconnect debug server`);
302
+ this.reconnectCount++;
303
+ setTimeout(() => this.initDebugSocket(), 2000);
304
+ }
305
+ else {
306
+ this.debugSocket = undefined;
307
+ this.reconnectCount = 0;
308
+ return reject(`### Emulator debugSocket connect failed, ${errorEvent.message}`);
309
+ }
310
+ };
311
+ this.debugSocket.onclose = (closeEvent) => {
268
312
  this.debugSocket = undefined;
269
313
  this.reconnectCount = 0;
270
- return Promise.reject(`### Emulator debugSocket connect failed`);
271
- }
272
- };
273
- this.debugSocket.onclose = (e) => {
274
- this.debugSocket = undefined;
275
- this.reconnectCount = 0;
276
- ColorConsole_1.default.log(`### Emulator debugSocket connect close: ${e.data}`);
277
- };
314
+ ColorConsole_1.default.log(`### Emulator debugSocket connect close: ${closeEvent.reason}`);
315
+ };
316
+ });
278
317
  }
279
- /** 通知模拟器更新 */
318
+ /** 通知模拟器更新
319
+ * 1. 确保已经成功连接模拟器中的调试服务
320
+ * 2. 使用adb push将build文件下的内容推到/data/quickapp/app/${packageName}
321
+ * 3. 发送Page.reload命令给调试服务,通知更新
322
+ */
280
323
  handleUpdate() {
281
324
  var _a, _b;
282
325
  return __awaiter(this, void 0, void 0, function* () {
@@ -286,12 +329,13 @@ class MiwearInstance extends common_1.default {
286
329
  // 1. 将整包重新推到miwear中(TODO:增量更新)
287
330
  const sn = `127.0.0.1:${this.adbPort}`;
288
331
  const { package: appPackageName } = this.projectInfo;
289
- // windows平台adb push时无法使用通配符,需另外处理
290
- if (os_1.default.platform() === 'win32') {
332
+ // windows平台adb push时无法使用通配符,需另外处理;
333
+ // 项目路径包含空格时,无法通过加引号来使用 * 通配符
334
+ if (os_1.default.platform() === 'win32' || this.projectPath.indexOf(' ') > 0) {
291
335
  const rmCmd = `adb -s ${sn} shell rm -r ${this.appPathInEmulator}/${appPackageName}`;
292
336
  yield adbMiwt.execAdbCmdAsync(rmCmd);
293
337
  const sourcePath = path_1.default.resolve(this.projectPath, './build');
294
- const pushCmd = `adb -s ${sn} push ${sourcePath} ${this.appPathInEmulator}/${appPackageName}`;
338
+ const pushCmd = `adb -s ${sn} push "${sourcePath}" ${this.appPathInEmulator}/${appPackageName}`;
295
339
  yield adbMiwt.execAdbCmdAsync(pushCmd);
296
340
  }
297
341
  else {
@@ -309,7 +353,9 @@ class MiwearInstance extends common_1.default {
309
353
  }
310
354
  });
311
355
  }
312
- /** 创建server */
356
+ /**
357
+ * 创建server端,监听打包过程中client端发来的消息
358
+ */
313
359
  createWebsockeServer() {
314
360
  var _a;
315
361
  return __awaiter(this, void 0, void 0, function* () {
@@ -321,6 +367,7 @@ class MiwearInstance extends common_1.default {
321
367
  socket.on('error', err => {
322
368
  ColorConsole_1.default.error(`### App Socket server ### Websocket server error: ${err.message}`);
323
369
  });
370
+ // data的格式:{ type: string, data: any }
324
371
  socket.on('message', (data) => __awaiter(this, void 0, void 0, function* () {
325
372
  var _a;
326
373
  const message = JSON.parse(data.toString());
@@ -2,20 +2,54 @@
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连接模拟器 */
43
+ /**
44
+ * 通过adb连接模拟器。
45
+ * 时间限制为10分钟,超时则表示连接失败
46
+ * @returns
47
+ */
18
48
  connectGoldfish(): Promise<boolean>;
49
+ /**
50
+ * 推送文件到本地的${sdkHome}/qa/app/${packageName}目录
51
+ * @param sourceRoot 源目录
52
+ */
19
53
  pushRpk(sourceRoot: string): Promise<void>;
20
54
  /** 停止模拟器并释放相关资源 */
21
55
  stop(): Promise<void>;
@@ -46,12 +46,22 @@ 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* () {
57
67
  // host启动9p server
@@ -62,9 +72,11 @@ class OldGoldfishInstance extends common_1.default {
62
72
  this.pushRpk(buildedFilesPath);
63
73
  // 启动模拟器
64
74
  yield this.startGoldfish(options);
75
+ // adb连接快应用
65
76
  const connected = yield this.connectGoldfish();
66
77
  if (connected) {
67
78
  ColorConsole_1.default.log('### Emulator ### Goldfish emulator connected successfully');
79
+ // 如果是首次启动,创建server端监听事件
68
80
  if (this.isFirstStart && this.startOptions.serverPort) {
69
81
  yield this.createWebsockeServer();
70
82
  }
@@ -77,7 +89,12 @@ class OldGoldfishInstance extends common_1.default {
77
89
  }
78
90
  });
79
91
  }
80
- /** 在goldfish中启动快应用 */
92
+ /**
93
+ * 在模拟器中启动快应用(快应用都在模拟器的/data/app目录下)
94
+ * 1. 是否为调试模式
95
+ * 若是,在模拟器终端执行vapp app/${packageName} &
96
+ * 若否,在模拟器终端执行vapp --jsdebugger=10.0.2.15:101 app/${packageName} &
97
+ */
81
98
  startupQuickApp(options) {
82
99
  try {
83
100
  const { package: packageName } = this.projectInfo;
@@ -86,6 +103,7 @@ class OldGoldfishInstance extends common_1.default {
86
103
  ColorConsole_1.default.log(`### Emulator ### Excuting adb cmd: ${mountCmd}`);
87
104
  adbMiwt.execAdbCmdSync(mountCmd);
88
105
  let vappCmd = `adb -s 127.0.0.1:${this.adbPort} shell vapp app/${packageName} &`;
106
+ // 调试情况下,需要加--jsdebugger=10.0.2.15:101
89
107
  if (options.devtool) {
90
108
  vappCmd = `adb -s 127.0.0.1:${this.adbPort} shell vapp --jsdebugger=10.0.2.15:101 app/${packageName} &`;
91
109
  }
@@ -97,7 +115,9 @@ class OldGoldfishInstance extends common_1.default {
97
115
  ColorConsole_1.default.error(`### Emulator ### Failed to startup quickapp: ${e.message}`);
98
116
  }
99
117
  }
100
- /** host启动9pServer */
118
+ /** host启动9pServer
119
+ * 作用是将本地的quickappMountDir目录挂载到模拟器的/data目录
120
+ */
101
121
  ensure9pServerRunnning() {
102
122
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
103
123
  var _a;
@@ -121,6 +141,7 @@ class OldGoldfishInstance extends common_1.default {
121
141
  address,
122
142
  '--debug'
123
143
  ]);
144
+ // 监听stderr,判断9p server是否启动成功了
124
145
  (_a = this.v9fsProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
125
146
  const output = data.toString();
126
147
  if (output.match(/Server started, listening on: 127.0.0.1:(\d+)/)) {
@@ -129,18 +150,30 @@ class OldGoldfishInstance extends common_1.default {
129
150
  return resolve();
130
151
  }
131
152
  });
153
+ // 监听exit事件,判断9p server是否退出了
132
154
  this.v9fsProcess.on('exit', (code) => {
133
155
  ColorConsole_1.default.error(`### Emulator ### ya-vm-file-server exited with code ${code}`);
134
156
  return reject();
135
157
  });
136
158
  }));
137
159
  }
138
- /** 启动goldfish模拟器 */
160
+ /**
161
+ * 启动模拟器
162
+ * 1. 通过options生成模拟器的启动命令
163
+ * 2. 执行启动命令
164
+ * 3. 判断模拟器是否启动成功
165
+ * 3.1 若disableNSH=true,输出流中匹配到/quickapp_rpk_installer_init|rpk installer init done/,认为模拟器启动成功了
166
+ * 3.2 若disableNSH=false,认为8s过后模拟器启动成功了
167
+ * @param options
168
+ * @returns
169
+ */
139
170
  startGoldfish(options) {
140
171
  return __awaiter(this, void 0, void 0, function* () {
141
172
  const { avdName, devtool } = options;
173
+ // 获取emulator bin的绝对路径
142
174
  const emulatorBin = this.getEmulatorBinPath();
143
175
  ColorConsole_1.default.log(`### Emulator ### emulator path: ${emulatorBin}`);
176
+ // 获取vela镜像的绝对路径
144
177
  const avdInfo = this.velaAvdCls.getVelaAvdInfo(avdName);
145
178
  const { avdArch, avdImagePath } = avdInfo;
146
179
  this.adbPort = yield portfinder_1.default.getPortPromise({ port: this.adbPort });
@@ -150,6 +183,7 @@ class OldGoldfishInstance extends common_1.default {
150
183
  }
151
184
  const nuttxBinPath = path_1.default.resolve(avdImagePath, 'nuttx');
152
185
  ColorConsole_1.default.log(`### Emulator ### nuttx path: ${nuttxBinPath}`);
186
+ // 端口映射,adb端口和debug端口
153
187
  let portMappingStr = `user,id=u1,hostfwd=tcp:127.0.0.1:${this.adbPort}-10.0.2.15:5555`;
154
188
  if (devtool) {
155
189
  this.debugPort = yield portfinder_1.default.getPortPromise({ port: this.debugPort });
@@ -157,7 +191,6 @@ class OldGoldfishInstance extends common_1.default {
157
191
  portMappingStr += `,hostfwd=tcp:127.0.0.1:${this.debugPort}-10.0.2.15:101`;
158
192
  }
159
193
  ColorConsole_1.default.log(`### Emulator ### Start qemu with TCP: ${portMappingStr}`);
160
- const stdioType = options.disableNSH ? 'pipe' : 'inherit';
161
194
  // vnc配置
162
195
  let noWindow = false;
163
196
  let vncStr = '';
@@ -166,6 +199,8 @@ class OldGoldfishInstance extends common_1.default {
166
199
  const portSuffix = options.vncPort - constants_1.defaultVncPort;
167
200
  vncStr = `-vnc :${portSuffix}`;
168
201
  }
202
+ // 根据disableNSH参数配置stdio
203
+ const stdioType = options.disableNSH ? 'pipe' : 'inherit';
169
204
  // 启动goldfish的命令和参数
170
205
  const spawnBin = emulatorBin;
171
206
  const spawnArgs = [
@@ -189,16 +224,19 @@ class OldGoldfishInstance extends common_1.default {
189
224
  return new Promise((resolve) => {
190
225
  var _a;
191
226
  this.goldfishProcess = (0, child_process_1.spawn)(spawnBin, spawnArgs, { stdio: stdioType, shell: true });
227
+ // 监听错误流
192
228
  (_a = this.goldfishProcess.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
193
229
  const stderrCb = options.stderrCallback || console.log;
194
230
  stderrCb(data.toString());
195
231
  });
232
+ // 监听模拟器的退出事件
196
233
  this.goldfishProcess.on('exit', (code) => {
197
234
  ColorConsole_1.default.error(`### Emulator ### Goldfish emulator exited with code ${code}`);
198
235
  if (options.exitCallback) {
199
236
  options.exitCallback(code);
200
237
  }
201
238
  });
239
+ // 监听输出流。输出了'(NSH)'标识或者2s后则认为模拟器启动成功
202
240
  const p1 = new Promise((resolve) => {
203
241
  var _a, _b;
204
242
  (_b = (_a = this.goldfishProcess) === null || _a === void 0 ? void 0 : _a.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
@@ -218,7 +256,11 @@ class OldGoldfishInstance extends common_1.default {
218
256
  });
219
257
  });
220
258
  }
221
- /** 通过adb连接模拟器 */
259
+ /**
260
+ * 通过adb连接模拟器。
261
+ * 时间限制为10分钟,超时则表示连接失败
262
+ * @returns
263
+ */
222
264
  connectGoldfish() {
223
265
  return __awaiter(this, void 0, void 0, function* () {
224
266
  let adbConnected = false;
@@ -248,6 +290,10 @@ class OldGoldfishInstance extends common_1.default {
248
290
  return adbConnected;
249
291
  });
250
292
  }
293
+ /**
294
+ * 推送文件到本地的${sdkHome}/qa/app/${packageName}目录
295
+ * @param sourceRoot 源目录
296
+ */
251
297
  pushRpk(sourceRoot) {
252
298
  return __awaiter(this, void 0, void 0, function* () {
253
299
  const { package: appPackageName } = this.projectInfo;
@@ -6,14 +6,17 @@ 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 emulatorEnvVersion: {
9
+ export declare const VelaImageVersionList: {
10
+ label: string;
11
+ value: string;
12
+ time: string;
13
+ }[];
14
+ export declare const EmulatorEnvVersion: {
10
15
  name: string;
11
16
  emulator: string;
12
17
  qa: string;
13
18
  skins: string;
14
- 'system-images': {
15
- release: string;
16
- dev: string;
17
- };
19
+ 'system-images': string;
18
20
  tools: string;
21
+ modem_simulator: string;
19
22
  };
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.emulatorEnvVersion = exports.defaultVncPort = exports.defaultToolsHome = exports.defaultQuickappHome = exports.defaultSkinHome = exports.defaultEmulatorHome = exports.defaultImageHome = exports.defaultAvdHome = exports.defaultSDKHome = void 0;
6
+ exports.EmulatorEnvVersion = exports.VelaImageVersionList = exports.defaultVncPort = exports.defaultToolsHome = exports.defaultQuickappHome = exports.defaultSkinHome = exports.defaultEmulatorHome = exports.defaultImageHome = exports.defaultAvdHome = exports.defaultSDKHome = void 0;
7
7
  const os_1 = __importDefault(require("os"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  exports.defaultSDKHome = path_1.default.resolve(os_1.default.homedir(), '.export');
@@ -14,14 +14,24 @@ exports.defaultSkinHome = path_1.default.resolve(exports.defaultSDKHome, 'skins'
14
14
  exports.defaultQuickappHome = path_1.default.resolve(exports.defaultSDKHome, 'qa');
15
15
  exports.defaultToolsHome = path_1.default.resolve(exports.defaultSDKHome, 'tools');
16
16
  exports.defaultVncPort = 5900;
17
- exports.emulatorEnvVersion = {
17
+ exports.VelaImageVersionList = [
18
+ {
19
+ label: 'vela正式版(4.0)',
20
+ value: 'vela-release-4.0',
21
+ time: '2024-04-10T04:10:00'
22
+ },
23
+ {
24
+ label: 'vela开发版(dev, 0.0.2)',
25
+ value: 'vela-dev-0.0.2',
26
+ time: '2023-12-04T16:31:00',
27
+ },
28
+ ];
29
+ exports.EmulatorEnvVersion = {
18
30
  name: '模拟器资源版本管理',
19
- emulator: '0.0.1',
31
+ emulator: '0.0.3',
20
32
  qa: '0.0.1',
21
33
  skins: '0.0.1',
22
- 'system-images': {
23
- release: '4.0',
24
- dev: '0.0.2'
25
- },
26
- tools: '0.0.2'
34
+ 'system-images': exports.VelaImageVersionList[0].value,
35
+ tools: '0.0.2',
36
+ modem_simulator: '0.0.1'
27
37
  };
@@ -1,8 +1,10 @@
1
1
  /// <reference types="node" />
2
+ import IJavascriptCompileOption from "@aiot-toolkit/aiotpack/lib/compiler/javascript/interface/IJavascriptCompileOption";
2
3
  import { IAvdResourcePaths } from "./Avd";
3
4
  export interface INewGoldfishInstanceParams extends IAvdResourcePaths {
4
5
  projectPath: string;
5
6
  sourceRoot?: string;
7
+ compilerOption?: Partial<IJavascriptCompileOption>;
6
8
  }
7
9
  export interface IStartOptions {
8
10
  avdName: string;
@@ -9,4 +9,5 @@ export declare function killProcessByPid(pid: string): void;
9
9
  * 根据命令杀死进程
10
10
  */
11
11
  export declare function killProcessByCmd(cmd: string): Promise<void>;
12
+ /** 延迟函数 */
12
13
  export declare function sleep(time: number): Promise<void>;
@@ -79,6 +79,7 @@ function killProcessByCmd(cmd) {
79
79
  });
80
80
  }
81
81
  exports.killProcessByCmd = killProcessByCmd;
82
+ /** 延迟函数 */
82
83
  function sleep(time) {
83
84
  return __awaiter(this, void 0, void 0, function* () {
84
85
  return new Promise(resolve => setTimeout(resolve, time));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiot-toolkit/emulator",
3
- "version": "2.0.2-beta.3",
3
+ "version": "2.0.2-beta.5",
4
4
  "description": "vela emulator tool.",
5
5
  "homepage": "",
6
6
  "license": "ISC",
@@ -35,10 +35,14 @@
35
35
  "emulator"
36
36
  ],
37
37
  "dependencies": {
38
- "@aiot-toolkit/aiotpack": "2.0.2-beta.3",
39
- "@aiot-toolkit/shared-utils": "2.0.2-beta.3",
38
+ "@aiot-toolkit/aiotpack": "2.0.2-beta.5",
39
+ "@aiot-toolkit/shared-utils": "2.0.2-beta.5",
40
40
  "find-process": "^1.4.7",
41
41
  "portfinder": "^1.0.32"
42
42
  },
43
- "gitHead": "8bfdd8895ffd8fc4aa1465779def5ab4392a7a4b"
43
+ "devDependencies": {
44
+ "@types/fs-extra": "^11.0.4",
45
+ "fs-extra": "^11.2.0"
46
+ },
47
+ "gitHead": "8c18753992c453c6569a3125378e75fbf4760a1f"
44
48
  }