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