@aiot-toolkit/emulator 2.0.3-beta.7 → 2.0.3-beta.9

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.
Files changed (45) hide show
  1. package/README.md +133 -8
  2. package/lib/emulatorutil/EmulatorLog.js +22 -18
  3. package/lib/emulatorutil/constants.d.ts +18 -5
  4. package/lib/emulatorutil/constants.js +94 -53
  5. package/lib/emulatorutil/index.d.ts +3 -2
  6. package/lib/emulatorutil/index.js +38 -8
  7. package/lib/emulatorutil/running.d.ts +24 -0
  8. package/lib/emulatorutil/running.js +108 -0
  9. package/lib/emulatorutil/skinLayoutParser.d.ts +14 -0
  10. package/lib/emulatorutil/skinLayoutParser.js +111 -0
  11. package/lib/index.d.ts +5 -5
  12. package/lib/index.js +76 -26
  13. package/lib/instance/common.d.ts +38 -39
  14. package/lib/instance/common.js +141 -223
  15. package/lib/instance/dev.d.ts +7 -42
  16. package/lib/instance/dev.js +54 -235
  17. package/lib/instance/index.d.ts +6 -5
  18. package/lib/instance/index.js +51 -35
  19. package/lib/instance/miwear.d.ts +14 -75
  20. package/lib/instance/miwear.js +93 -370
  21. package/lib/instance/pre.d.ts +11 -3
  22. package/lib/instance/pre.js +55 -93
  23. package/lib/instance/pre5.d.ts +11 -0
  24. package/lib/instance/pre5.js +38 -0
  25. package/lib/static/avdConfigIni.json +5 -5
  26. package/lib/typing/Instance.d.ts +30 -15
  27. package/lib/typing/Instance.js +13 -6
  28. package/lib/typing/Vvd.d.ts +105 -0
  29. package/lib/typing/Vvd.js +31 -0
  30. package/lib/utils/file.d.ts +0 -0
  31. package/lib/utils/file.js +1 -0
  32. package/lib/utils/index.js +86 -100
  33. package/lib/vvd/index.d.ts +107 -0
  34. package/lib/vvd/index.js +698 -0
  35. package/lib/vvd/logcat.d.ts +16 -0
  36. package/lib/vvd/logcat.js +67 -0
  37. package/package.json +9 -8
  38. package/lib/avd/index.d.ts +0 -28
  39. package/lib/avd/index.js +0 -173
  40. package/lib/emulatorutil/EmulatorCmd.d.ts +0 -9
  41. package/lib/emulatorutil/EmulatorCmd.js +0 -226
  42. package/lib/instance/preDev.d.ts +0 -53
  43. package/lib/instance/preDev.js +0 -249
  44. package/lib/typing/Avd.d.ts +0 -23
  45. package/lib/typing/Avd.js +0 -8
@@ -0,0 +1,698 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.VvdManager = void 0;
7
+ var _ColorConsole = _interopRequireDefault(require("@aiot-toolkit/shared-utils/lib/ColorConsole"));
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _os = _interopRequireDefault(require("os"));
10
+ var _path = _interopRequireDefault(require("path"));
11
+ var _skinLayoutParser = require("../emulatorutil/skinLayoutParser");
12
+ var _child_process = require("child_process");
13
+ var _ini = require("ini");
14
+ var _constants = require("../emulatorutil/constants");
15
+ var _Vvd = require("../typing/Vvd");
16
+ var _utils = require("../utils");
17
+ var _Instance = require("../typing/Instance");
18
+ var _portfinder = require("portfinder");
19
+ var _dayjs = _interopRequireDefault(require("dayjs"));
20
+ var _emulatorutil = require("../emulatorutil");
21
+ var _adb = require("@miwt/adb");
22
+ var _semver = require("semver");
23
+ var _admZip = _interopRequireDefault(require("adm-zip"));
24
+ var _instance = require("../instance");
25
+ var _logcat = require("./logcat");
26
+ var _sharedUtils = require("@aiot-toolkit/shared-utils");
27
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
28
+ // TODO: 升级构建工具支持 esm @xujunjie
29
+ const getPort = (async () => {
30
+ return (await import('get-port')).default;
31
+ })();
32
+ const ipull = (async () => {
33
+ return await import('ipull');
34
+ })();
35
+ const EAvdParamsToIni = {
36
+ arm: {
37
+ abiType: 'armeabi-v7a'
38
+ },
39
+ arm64: {
40
+ abiType: 'arm64-v8a'
41
+ }
42
+ };
43
+ class VvdManager {
44
+ constructor(vvdResourcePaths) {
45
+ const {
46
+ vvdHome: avdHome,
47
+ sdkHome
48
+ } = vvdResourcePaths;
49
+ this.vvdHome = avdHome || _constants.defaultVvdHome;
50
+ this.sdkHome = sdkHome || _constants.defaultSDKHome;
51
+ if (!_fs.default.existsSync(this.vvdHome)) {
52
+ _fs.default.mkdirSync(this.vvdHome, {
53
+ recursive: true
54
+ });
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 创建Vela端的 VVD ,统一保存在 .vela/vvd 目录下
60
+ * 1. 创建.vela/advancedFeatures.ini文件
61
+ * 2. 创建.vela/vvd/${avdName}.ini文件
62
+ * 3. 创建.vela/vvd/${avdName}.vvd/config.ini文件
63
+ * @param vvdParams VVD参数,宽高、绑定的镜像路径等
64
+ * @returns
65
+ */
66
+ createVvd(vvdParams) {
67
+ const {
68
+ name: vvdName,
69
+ arch,
70
+ width,
71
+ height,
72
+ skin,
73
+ shape,
74
+ flavor,
75
+ density,
76
+ imageDir: imagePath = _constants.defaultImageHome,
77
+ customLcdRadius,
78
+ imageType
79
+ } = vvdParams;
80
+ const vvdDir = _path.default.resolve(this.vvdHome, `${vvdName}.vvd`);
81
+ const vvdIni = _path.default.resolve(this.vvdHome, `${vvdName}.ini`);
82
+ const vvdConfigIni = _path.default.resolve(vvdDir, 'config.ini');
83
+ const nuttxVvdIniContent = `path=${vvdDir}\npath.rel=avd${_path.default.sep}${vvdName}.vvd`;
84
+ const abiType = EAvdParamsToIni[arch]['abiType'];
85
+ const configIniJson = JSON.parse(_fs.default.readFileSync(_path.default.join(__dirname, '../static/avdConfigIni.json'), 'utf-8'));
86
+ configIniJson['AvdId'] = vvdName;
87
+ configIniJson['abi.type'] = abiType;
88
+ configIniJson['avd.ini.displayname'] = vvdName;
89
+ configIniJson['hw.cpu.arch'] = arch;
90
+ configIniJson['hw.lcd.shape'] = shape;
91
+ configIniJson['hw.device.flavor'] = flavor;
92
+ configIniJson['hw.lcd.density'] = density;
93
+ configIniJson['ide.lcd.radius'] = customLcdRadius;
94
+ configIniJson['ide.image.type'] = imageType;
95
+ if (skin) {
96
+ delete configIniJson['hw.lcd.height'];
97
+ delete configIniJson['hw.lcd.width'];
98
+ configIniJson['skin.dynamic'] = 'yes';
99
+ configIniJson['skin.name'] = skin;
100
+ configIniJson['skin.path'] = vvdParams['skin.path'];
101
+ } else {
102
+ configIniJson['hw.lcd.height'] = height;
103
+ configIniJson['hw.lcd.width'] = width;
104
+ configIniJson['skin.dynamic'] = 'no';
105
+ delete configIniJson['skin.name'];
106
+ delete configIniJson['skin.path'];
107
+ }
108
+
109
+ // 默认使用 'image.sysdir.2' 自定义使用 'image.sysdir.1'
110
+ configIniJson['image.sysdir.2'] = imagePath;
111
+ try {
112
+ _fs.default.mkdirSync(vvdDir, {
113
+ recursive: true
114
+ });
115
+ // 写入Vela_Virtual_Device.ini文件
116
+ _fs.default.writeFileSync(vvdIni, nuttxVvdIniContent);
117
+ // 写入Vela_Virtual_Device.vvd/config.ini文件
118
+ _fs.default.writeFileSync(vvdConfigIni, (0, _ini.stringify)(configIniJson));
119
+
120
+ // 在 sysdir 下创建advancedFeatures.ini文件
121
+ const advancedFeaturesIni = _path.default.resolve(imagePath, 'advancedFeatures.ini');
122
+ if (!_fs.default.existsSync(advancedFeaturesIni)) {
123
+ const iniSourcePath = _path.default.join(__dirname, '../static/advancedFeatures.ini');
124
+ _fs.default.copyFileSync(iniSourcePath, advancedFeaturesIni);
125
+ }
126
+
127
+ // 拷贝 MODEM_SIMULATOR
128
+ const modemPath = this.getSDKChild(_Vvd.SDKChildren.MODEM_SIMULATOR);
129
+ if (_fs.default.existsSync(modemPath)) {
130
+ _sharedUtils.FileUtil.copyFiles(modemPath, _path.default.join(vvdDir, 'modem_simulator'));
131
+ }
132
+ return true;
133
+ } catch (e) {
134
+ // 出错后要删除创建的目录, 恢复现场
135
+ this.deleteVvd(vvdName);
136
+ throw `createVelaAvd: ${e.message}`;
137
+ }
138
+ }
139
+ getVvdDir(vvdName) {
140
+ // 模拟器有可能在 .vvd 和 .avd 目录下
141
+ let res = _path.default.resolve(this.vvdHome, `${vvdName}.vvd`);
142
+ if (!_fs.default.existsSync(res)) res = _path.default.resolve(this.vvdHome, `${vvdName}.avd`);
143
+ return res;
144
+ }
145
+
146
+ /** 根据AVD名字获取模拟器的详细信息 */
147
+ getVvdInfo(vvdName) {
148
+ const vvdInfo = {
149
+ name: vvdName,
150
+ arch: _Vvd.IVvdArchType.arm,
151
+ height: '',
152
+ width: '',
153
+ skin: '',
154
+ imageDir: '',
155
+ customLcdRadius: '',
156
+ imageType: _Vvd.VelaImageType.REL
157
+ };
158
+ let currVvdDir = this.getVvdDir(vvdName);
159
+ const configIni = _path.default.resolve(currVvdDir, 'config.ini');
160
+ try {
161
+ const contents = _fs.default.readFileSync(configIni, 'utf-8');
162
+ const config = (0, _ini.parse)(contents);
163
+ vvdInfo.arch = config['hw.cpu.arch'];
164
+ vvdInfo.height = config['hw.lcd.height'];
165
+ vvdInfo.width = config['hw.lcd.width'];
166
+ vvdInfo.skin = config['skin.name'];
167
+ vvdInfo.imageDir = config['image.sysdir.2'];
168
+ vvdInfo.customImagePath = config['image.sysdir.1'];
169
+ vvdInfo.customLcdRadius = config['ide.lcd.radius'];
170
+ vvdInfo.imageType = config['ide.image.type'];
171
+ if (vvdInfo.skin) {
172
+ try {
173
+ vvdInfo.skinInfo = this.getSkinInfo(vvdInfo.skin, config['skin.path']);
174
+ } catch (error) {
175
+ _ColorConsole.default.warn(`get skin ${vvdInfo.skin} failed: ${error}`);
176
+ }
177
+ }
178
+ return vvdInfo;
179
+ } catch (err) {
180
+ _ColorConsole.default.log(`getVelaAvdInfo: ${err.message}`);
181
+ return vvdInfo;
182
+ }
183
+ }
184
+
185
+ /** 根据名字删除AVD */
186
+ deleteVvd(vvdName) {
187
+ const vvdDir = this.getVvdDir(vvdName);
188
+ const vvdIni = _path.default.resolve(this.vvdHome, `${vvdName}.ini`);
189
+ try {
190
+ _fs.default.rmSync(vvdDir, {
191
+ recursive: true,
192
+ force: true
193
+ });
194
+ _fs.default.rmSync(vvdIni, {
195
+ force: true
196
+ });
197
+ return true;
198
+ } catch (e) {
199
+ return false;
200
+ }
201
+ }
202
+
203
+ /** 获取已经创建的模拟器列表 */
204
+ getVvdList() {
205
+ const avdList = [];
206
+ const files = _fs.default.readdirSync(this.vvdHome);
207
+ const regex = /^([\d\D]*)\.(avd|vvd)$/;
208
+ for (const fileName of files) {
209
+ const matcher = fileName.match(regex);
210
+ if (matcher) {
211
+ const avdName = matcher[1];
212
+ const avdInfo = this.getVvdInfo(avdName);
213
+ avdList.push(avdInfo);
214
+ }
215
+ }
216
+ return avdList;
217
+ }
218
+
219
+ /** 获取 SDK 子目录 */
220
+ getSDKChild(name) {
221
+ return _path.default.resolve(this.sdkHome, name);
222
+ }
223
+ getSkinInfo(skinName, skinPath) {
224
+ const skinDir = skinPath;
225
+ const layoutPath = _path.default.join(skinDir, 'layout');
226
+ if (_fs.default.existsSync(layoutPath)) {
227
+ const layoutContent = _fs.default.readFileSync(layoutPath, 'utf-8');
228
+ const parser = new _skinLayoutParser.ConfigParser();
229
+ const skinInfo = parser.toObject(parser.parse(layoutContent));
230
+ const defaultLayout = skinInfo.layouts.portrait || skinInfo.layouts.landscape;
231
+ defaultLayout.part1.value = skinInfo.parts[defaultLayout.part1.name];
232
+ defaultLayout.part2.value = skinInfo.parts[defaultLayout.part2.name];
233
+ const backgroundImage = _path.default.join(skinDir, skinInfo.parts[defaultLayout.part1.name].background.image);
234
+ const maskImage = skinInfo.parts[defaultLayout.part1.name]?.foreground?.mask;
235
+ const maskImagePath = maskImage ? _path.default.join(skinDir, maskImage) : undefined;
236
+ return {
237
+ name: skinName,
238
+ path: skinPath,
239
+ info: skinInfo,
240
+ backgroundImage,
241
+ maskImage: maskImagePath,
242
+ defaultLayout
243
+ };
244
+ }
245
+ }
246
+
247
+ /** 获取模拟器皮肤列表 */
248
+ async getVelaSkinList() {
249
+ try {
250
+ const skinList = [];
251
+ const skinHome = this.getSDKChild(_Vvd.SDKChildren.SKINS);
252
+ const builinDir = _path.default.join(skinHome, 'builtin');
253
+ const userDir = _path.default.join(skinHome, 'user');
254
+ const builtinFiles = await _fs.default.promises.readdir(builinDir).catch(() => []);
255
+ const userFiles = await _fs.default.promises.readdir(userDir).catch(() => []);
256
+ for (const fileName of builtinFiles) {
257
+ try {
258
+ const item = this.getSkinInfo(fileName, _path.default.join(builinDir, fileName));
259
+ if (item) skinList.push(item);
260
+ } catch (error) {
261
+ _ColorConsole.default.warn(`parser skin info ${fileName} error:`, error.toString());
262
+ continue;
263
+ }
264
+ }
265
+ for (const fileName of userFiles) {
266
+ try {
267
+ const item = this.getSkinInfo(fileName, _path.default.join(userDir, fileName));
268
+ if (item) skinList.push(item);
269
+ } catch (error) {
270
+ _ColorConsole.default.warn(`parser skin info ${fileName} error:`, error.toString());
271
+ continue;
272
+ }
273
+ }
274
+ return skinList;
275
+ } catch (e) {
276
+ _ColorConsole.default.error(`get skin error:`, e.toString());
277
+ return [];
278
+ }
279
+ }
280
+
281
+ /** 自定义模拟器的镜像目录 */
282
+ customImageDir(vvdName, target) {
283
+ const currVvdDir = this.getVvdDir(vvdName);
284
+ const configIni = _path.default.resolve(currVvdDir, 'config.ini');
285
+ const contents = _fs.default.readFileSync(configIni, 'utf-8');
286
+ const config = (0, _ini.parse)(contents);
287
+ config['image.sysdir.2'] = target;
288
+ _fs.default.writeFileSync(configIni, (0, _ini.stringify)(config));
289
+ }
290
+
291
+ /** 重置自定义的镜像目录 */
292
+ resetImageDir(vvdName) {
293
+ const currVvdDir = this.getVvdDir(vvdName);
294
+ const configIni = _path.default.resolve(currVvdDir, 'config.ini');
295
+ const contents = _fs.default.readFileSync(configIni, 'utf-8');
296
+ const config = (0, _ini.parse)(contents);
297
+ delete config['image.sysdir.2'];
298
+ _fs.default.writeFileSync(configIni, (0, _ini.stringify)(config));
299
+ }
300
+ getEmulatorBinPath(sdkHome) {
301
+ const osPlatform = _os.default.platform();
302
+ const arch = (0, _utils.getSystemArch)();
303
+ const platform = osPlatform === 'win32' ? 'windows' : osPlatform;
304
+ const emulatorHome = _path.default.resolve(sdkHome, 'emulator');
305
+ return _path.default.resolve(emulatorHome, `${platform}-${arch}`, 'emulator');
306
+ }
307
+ async getVvdStartCmd(options) {
308
+ const vvdName = options.vvdName;
309
+
310
+ // 获取emulator bin的绝对路径
311
+ const emulatorBin = this.getEmulatorBinPath(this.sdkHome);
312
+
313
+ // 端口映射
314
+ const degbugProt = options.debugPort || (await (await getPort)({
315
+ port: Array.from({
316
+ length: 100
317
+ }).map((_, i) => 10055 + i)
318
+ }));
319
+ let portMappingStr = `-network-user-mode-options hostfwd=tcp:127.0.0.1:${degbugProt}-10.0.2.15:101`;
320
+
321
+ // qemu 配置
322
+ const qemuOption = `-device virtio-snd,bus=virtio-mmio-bus.2 -allow-host-audio -semihosting`;
323
+
324
+ // qt windows 配置
325
+ const windowOption = options.qtHideWindow ? `-qt-hide-window` : '';
326
+ let grpcStr = options.grpcPort ? `-grpc ${options.grpcPort}` : '';
327
+ let serialStr = ``;
328
+ if (options.serialPort) {
329
+ serialStr = `-port`;
330
+
331
+ // 由于模拟器方面原因,如果指定了 serialPort 则必须同时指定 grpc 参数,否则 grpc 不会默认开启,从而导致不会创建 running 文件
332
+ const grpcPort = options.grpcPort || (await (0, _portfinder.getPortPromise)());
333
+ grpcStr = `-grpc ${grpcPort}`;
334
+ }
335
+ const verboseOption = options.verbose ? `-verbose` : ``;
336
+ // 启动模拟器的命令和参数
337
+ const cmd = `${emulatorBin} -vela -avd ${options.vvdName} ${serialStr} -show-kernel ${portMappingStr} ${windowOption} ${grpcStr} ${verboseOption} -qemu ${qemuOption}`;
338
+ const vvdInfo = this.getVvdInfo(vvdName);
339
+ if (!vvdInfo.imageDir) {
340
+ const errMsg = `${vvdName} is not supported`;
341
+ _ColorConsole.default.throw(errMsg);
342
+ throw new Error(errMsg);
343
+ }
344
+
345
+ // 需要复制的文件
346
+ const files = ['system.img', 'data.img', 'coredump.core', 'vela_data.bin', 'vela_resource.bin', 'vela_system.bin'];
347
+ const vvdDir = this.getVvdDir(vvdName);
348
+ const imageDir = _path.default.resolve(vvdInfo.imageDir);
349
+ for (const file of files) {
350
+ const pOfVvd = _path.default.join(vvdDir, file);
351
+ const pOfImageDir = _path.default.join(imageDir, file);
352
+ if (!_fs.default.existsSync(pOfImageDir)) continue;
353
+ if (!_fs.default.existsSync(pOfVvd)) {
354
+ // 文件不存在则直接复制
355
+ _fs.default.copyFileSync(pOfImageDir, pOfVvd);
356
+ } else {
357
+ // 文件存在但过时
358
+ const statsInAvd = _fs.default.statSync(pOfVvd);
359
+ const stats = _fs.default.statSync(pOfImageDir);
360
+ if ((0, _dayjs.default)(stats.mtime).isAfter(statsInAvd.mtime)) {
361
+ _fs.default.copyFileSync(pOfImageDir, pOfVvd);
362
+ }
363
+ }
364
+ }
365
+
366
+ // 拷贝 MODEM_SIMULATOR
367
+ const modemPath = this.getSDKChild(_Vvd.SDKChildren.MODEM_SIMULATOR);
368
+ if (_fs.default.existsSync(modemPath) && !_fs.default.existsSync(_path.default.join(vvdDir, 'modem_simulator', 'modem_nvram.json'))) {
369
+ _sharedUtils.FileUtil.copyFiles(modemPath, _path.default.join(vvdDir, 'modem_simulator'));
370
+ }
371
+ return cmd;
372
+ }
373
+ async startVvd(options) {
374
+ const runningVvds = await (0, _emulatorutil.getRunningVvds)();
375
+ const vvdName = options.vvdName;
376
+ const onStdout = options.stdoutCallback || console.log;
377
+ const onErrout = options.stderrCallback || console.log;
378
+ const e = runningVvds.find(e => e['avd.name'] === vvdName);
379
+ const vvdInfo = this.getVvdInfo(vvdName);
380
+
381
+ // 该模拟器已经启动,则创建一个日志流返回
382
+ if (e) {
383
+ // 找出来它原来使用的 debugPort
384
+ const regex = /hostfwd=tcp:(.*?):(\d+)-10\.0\.2\.15:101/;
385
+ const debugPort = e.cmdline?.match(regex)?.[2];
386
+ const emulatorInstance = (0, _instance.findInstance)(vvdInfo.imageType, {
387
+ serialPort: e['port.serial'],
388
+ vvdName: vvdName,
389
+ onStdout,
390
+ onErrout,
391
+ debugPort
392
+ });
393
+ return {
394
+ coldBoot: false,
395
+ emulatorInstance
396
+ };
397
+ }
398
+
399
+ // 启动模拟器的命令和参数
400
+ const cmd = await this.getVvdStartCmd(options);
401
+ const spawnArgs = cmd.split(' ');
402
+ const spawnBin = spawnArgs.shift();
403
+ _ColorConsole.default.log(`### Emulator ### Start CMD miwear: ${cmd}`);
404
+ const func = vvdInfo.imageType === _Vvd.VelaImageType.REL ? _emulatorutil.EmulatorLog.rpkIsStart : _emulatorutil.EmulatorLog.devIsStart;
405
+ return new Promise((resolve, reject) => {
406
+ const emulatorProcess = (0, _child_process.spawn)(spawnBin, spawnArgs, {
407
+ stdio: 'pipe',
408
+ shell: true,
409
+ cwd: this.sdkHome
410
+ });
411
+ if (options.origin === _Instance.IStartOrigin.Terminal) {
412
+ process.stdin.pipe(emulatorProcess.stdin);
413
+ }
414
+
415
+ // 利用 readline 接口可解决子进程日志换行的问题
416
+ const readlines = (0, _logcat.attachReadline)(emulatorProcess, async msg => {
417
+ if (func(msg)) {
418
+ const runningVvds = await (0, _emulatorutil.getRunningVvds)();
419
+ const e = runningVvds.find(e => e['avd.id'] === vvdName);
420
+ if (e) {
421
+ const emulatorInstance = (0, _instance.findInstance)(vvdInfo.imageType, {
422
+ serialPort: e['port.serial'],
423
+ vvdName: vvdName,
424
+ logcatProcess: emulatorProcess,
425
+ ...readlines,
426
+ onStdout,
427
+ onErrout,
428
+ debugPort: options.debugPort
429
+ });
430
+ _ColorConsole.default.info(`### Emulator ### Goldfish emulator starts successfully`);
431
+ resolve({
432
+ coldBoot: true,
433
+ emulatorInstance
434
+ });
435
+ } else {
436
+ reject('get emulator running config failed');
437
+ }
438
+ }
439
+ onStdout(msg);
440
+ }, onErrout);
441
+
442
+ // 监听模拟器的退出事件
443
+ emulatorProcess.on('exit', code => {
444
+ _ColorConsole.default.error(`### Emulator ### Goldfish emulator exited with code ${code}`);
445
+ if (options.exitCallback) {
446
+ options.exitCallback(code);
447
+ }
448
+ readlines.dispose();
449
+ reject();
450
+ });
451
+
452
+ // 监听模拟器的错误事件
453
+ emulatorProcess.on('error', err => {
454
+ readlines.dispose();
455
+ reject(err);
456
+ });
457
+ });
458
+ }
459
+ stopVvd(name) {
460
+ let timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10 * 1000;
461
+ return new Promise((resolve, reject) => {
462
+ const config = (0, _emulatorutil.getRunningAvdConfigByName)(name);
463
+ if (!config) {
464
+ return resolve();
465
+ }
466
+ const t = setTimeout(() => {
467
+ (0, _utils.killProcessByCmd)(config['avd.name']).then(resolve).catch(e => {
468
+ reject(`poweroff failed ${e}`);
469
+ });
470
+ }, timeout);
471
+ (0, _adb.execAdbCmdAsync)(`adb -s emulator-${config['port.serial']} shell poweroff`).then(() => {
472
+ clearTimeout(t);
473
+ resolve();
474
+ }).catch(() => {
475
+ (0, _utils.killProcessByCmd)(config['avd.name']).then(() => {
476
+ clearTimeout(t);
477
+ resolve();
478
+ }).catch(e => {
479
+ reject(`poweroff failed ${e}`);
480
+ });
481
+ });
482
+ });
483
+ }
484
+
485
+ //#endregion sdk manager
486
+
487
+ /** 获取模拟器平台的名称,darwin-aarch64 linux-aarch64 windows-x86_64等 */
488
+ getEmulatorPlatform() {
489
+ const systemOs = _os.default.platform();
490
+ const hostArch = (0, _utils.getSystemArch)();
491
+ const hostOs = systemOs === 'win32' ? 'windows' : systemOs;
492
+ return `${hostOs}-${hostArch}`;
493
+ }
494
+
495
+ /** 获取本地镜像的构建时间 */
496
+ async getLocalImageBuildTime(imagePath) {
497
+ if (!imagePath) return;
498
+ if (_fs.default.existsSync(imagePath)) {
499
+ const stats = await _fs.default.promises.stat(imagePath);
500
+ // 兼容传递的参数是镜像目录的情况
501
+ if (stats.isDirectory()) {
502
+ const stats = await _fs.default.promises.stat(_path.default.join(imagePath, 'nuttx'));
503
+ return stats.mtime;
504
+ }
505
+ return stats.mtime;
506
+ }
507
+ return;
508
+ }
509
+ getSDKVersionPath() {
510
+ return _path.default.resolve(this.sdkHome, 'versions.json');
511
+ }
512
+
513
+ /** 获取本地 SDK 的版本信息 */
514
+ async getSDKVersion() {
515
+ return JSON.parse(await _fs.default.promises.readFile(this.getSDKVersionPath(), 'utf-8'));
516
+ }
517
+
518
+ /** 获取本地已经下载的镜像 */
519
+ async getLocalSystemImage() {
520
+ const home = this.getSDKChild(_Vvd.SDKChildren.SYSTEM_IMAGES);
521
+ const res = [];
522
+ if (_fs.default.existsSync(home)) {
523
+ const files = _fs.default.readdirSync(home);
524
+ for (let file of files) {
525
+ if (file.startsWith('.')) continue;
526
+ const systemPath = _path.default.resolve(home, file, 'nuttx');
527
+ if (!_fs.default.existsSync(systemPath)) continue;
528
+ const time = await this.getLocalImageBuildTime(file);
529
+ res.push({
530
+ value: file,
531
+ time
532
+ });
533
+ }
534
+ }
535
+ return res;
536
+ }
537
+ getLocalSystemPath(imageId) {
538
+ const home = this.getSDKChild(_Vvd.SDKChildren.SYSTEM_IMAGES);
539
+ return _path.default.resolve(home, imageId, 'nuttx');
540
+ }
541
+ hasLocaleImage(imageId) {
542
+ const systemPath = this.getLocalSystemPath(imageId);
543
+ if (_fs.default.existsSync(systemPath)) return true;
544
+ return false;
545
+ }
546
+
547
+ /** 判断本地的某个 vela 镜像是否需要更新 */
548
+ async isLocalImageNeedUpdate(imageId) {
549
+ if (!this.hasLocaleImage(imageId)) return true;
550
+ const local = await this.getLocalImageBuildTime(this.getLocalSystemPath(imageId));
551
+ const latest = (0, _dayjs.default)(_constants.VelaImageVersionList.find(t => t.value === imageId).time, 'YYYYMMDD');
552
+ if ((0, _dayjs.default)(local).isBefore(latest)) return true;
553
+ return false;
554
+ }
555
+ async getNotInstalledSDKPart() {
556
+ const allResouce = Object.values(_Vvd.SDKChildren);
557
+ const res = [];
558
+ try {
559
+ const res = [];
560
+ for (const resouces of allResouce) {
561
+ // 镜像的版本是时间格式
562
+ if (!_fs.default.existsSync(_path.default.resolve(this.sdkHome, resouces))) {
563
+ res.push(resouces);
564
+ break;
565
+ }
566
+ }
567
+ return res;
568
+ } catch (error) {
569
+ // 未读取到版本信息,则认为所有资源都需要更新
570
+ return allResouce;
571
+ }
572
+ }
573
+
574
+ /** 检查 SDK 有哪些部分需要更新 */
575
+ async hasSDKChildrenUpdate() {
576
+ const allResouce = Object.values(_Vvd.SDKChildren);
577
+ try {
578
+ const res = [];
579
+ const localVersion = await this.getSDKVersion();
580
+ for (const resouces of allResouce) {
581
+ // 镜像的版本是时间格式
582
+ if (!_fs.default.existsSync(_path.default.resolve(this.sdkHome, resouces))) {
583
+ res.push(resouces);
584
+ continue;
585
+ }
586
+ if (resouces === _Vvd.SDKChildren.SYSTEM_IMAGES) {
587
+ if (await this.isLocalImageNeedUpdate((0, _constants.getDefaultImage)())) res.push(resouces);
588
+ continue;
589
+ }
590
+ if ((0, _semver.lt)(localVersion[resouces], _constants.EmulatorEnvVersion[resouces])) {
591
+ res.push(resouces);
592
+ }
593
+ }
594
+ return res;
595
+ } catch (error) {
596
+ // 未读取到版本信息,则认为所有资源都需要更新
597
+ return allResouce;
598
+ }
599
+ }
600
+
601
+ /**
602
+ * 下载 SDK,默认只下载需要更新的部分
603
+ * @param opt.force 强制下载所有部分
604
+ *
605
+ * @example
606
+ * async function main() {
607
+ * const sdkHome = path.resolve(os.homedir(), '.export_dev1')
608
+ * const velaAvdCls = new VelaAvdCls({ sdkHome })
609
+ *
610
+ * const downloder = await velaAvdCls.downloadSDK({
611
+ * force: true,
612
+ * cliProgress: false,
613
+ * parallelDownloads: 6
614
+ * })
615
+ *
616
+ * downloder.on('progress', (progress) => {
617
+ * console.log(
618
+ * `progress: ${progress.formattedSpeed} ${progress.formattedPercentage} ${progress.formatTotal} ${progress.formatTimeLeft}`
619
+ * )
620
+ * })
621
+ *
622
+ * await downloder.downlodPromise
623
+ *
624
+ * console.log('download success')
625
+ * }
626
+ */
627
+ async downloadSDK(opt) {
628
+ const updateList = opt.force ? Object.values(_Vvd.SDKChildren) : await this.hasSDKChildrenUpdate();
629
+ const urls = updateList.map(t => ({
630
+ name: t,
631
+ url: (0, _constants.getSDKChildDownloadUrl)(t)
632
+ }));
633
+ const downloads = urls.map(async u => {
634
+ const d = await (await ipull).downloadFile({
635
+ url: u.url,
636
+ directory: _path.default.resolve(this.sdkHome),
637
+ skipExisting: false,
638
+ parallelStreams: opt.parallelStreams || 6
639
+ });
640
+ return d;
641
+ });
642
+ const downloader = await (await ipull).downloadSequence(opt, ...downloads);
643
+ const p = downloader.download();
644
+ const p2 = p.then(async () => {
645
+ // 如果下载被取消或者出错,就不再执行后续操作
646
+ if (downloader.downloadStatues.some(status => status.downloadStatus !== 'Finished')) {
647
+ return;
648
+ }
649
+ for (const u of urls) {
650
+ // 解压
651
+ const targetDirName = u.name;
652
+ const targetDir = u.name === _Vvd.SDKChildren.SYSTEM_IMAGES ? _path.default.resolve(this.sdkHome, targetDirName, _path.default.basename(u.url).replace('.zip', '')) : _path.default.resolve(this.sdkHome, targetDirName);
653
+ const targetDirExist = _fs.default.existsSync(targetDir);
654
+ if (!targetDirExist) await _fs.default.promises.mkdir(targetDir, {
655
+ recursive: true
656
+ });
657
+ const zipFile = _path.default.resolve(this.sdkHome, _path.default.basename(u.url));
658
+ const zip = new _admZip.default(zipFile);
659
+ zip.extractAllTo(targetDir, true, true);
660
+ await _fs.default.promises.rm(zipFile, {
661
+ force: true
662
+ });
663
+ }
664
+ const verFile = this.getSDKVersionPath();
665
+ await _fs.default.promises.writeFile(verFile, JSON.stringify(_constants.EmulatorEnvVersion, null, 2));
666
+ });
667
+ return Object.assign(downloader, {
668
+ downlodPromise: p2
669
+ });
670
+ }
671
+
672
+ /** 下载vela系统镜像 */
673
+ async downloadImage(imageId, opt) {
674
+ const downloadUrls = (0, _constants.getImageDownloadUrl)();
675
+ const u = downloadUrls[imageId];
676
+ const downloader = await (await ipull).downloadFile({
677
+ url: u,
678
+ directory: this.getSDKChild(_Vvd.SDKChildren.SYSTEM_IMAGES),
679
+ skipExisting: false,
680
+ ...opt
681
+ });
682
+ const p = downloader.download();
683
+ const p2 = p.then(async () => {
684
+ //如果下载被取消或者出错,就不再执行后续操作
685
+ if (downloader.status.downloadStatus !== 'Finished') return;
686
+ const zipFile = _path.default.resolve(this.sdkHome, _Vvd.SDKChildren.SYSTEM_IMAGES, _path.default.basename(u));
687
+ const zip = new _admZip.default(zipFile);
688
+ zip.extractAllTo(_path.default.resolve(this.sdkHome, _Vvd.SDKChildren.SYSTEM_IMAGES, imageId), true, true);
689
+ await _fs.default.promises.rm(zipFile, {
690
+ force: true
691
+ });
692
+ });
693
+ return Object.assign(downloader, {
694
+ downlodPromise: p2
695
+ });
696
+ }
697
+ }
698
+ exports.VvdManager = VvdManager;