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