1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | const
|
16 | appc = require('node-appc'),
|
17 | async = require('async'),
|
18 | assemblies = require('./assemblies'),
|
19 | DOMParser = require('xmldom').DOMParser,
|
20 | fs = require('fs'),
|
21 | magik = require('./utilities').magik,
|
22 | checkOutdated = require('./utilities').checkOutdated,
|
23 | emulator = require('./emulator'),
|
24 | path = require('path'),
|
25 | spawn = require('child_process').spawn,
|
26 | visualstudio = require('./visualstudio'),
|
27 | windowsphone = require('./windowsphone'),
|
28 | wrench = require('wrench'),
|
29 | wptool = path.resolve(__dirname, '..', 'bin', 'wptool.exe'),
|
30 | __ = appc.i18n(__dirname).__,
|
31 | os = require('os'),
|
32 |
|
33 |
|
34 |
|
35 | tmpBuildDir = path.join(os.tmpdir(), 'appcelerator', 'wptool'),
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | wpsdks = ['8.0', '8.1', '10.0'],
|
41 | PREFERRED_SDK = '10.0';
|
42 |
|
43 | var cache;
|
44 |
|
45 | exports.enumerate = enumerate;
|
46 | exports.connect = connect;
|
47 | exports.install = install;
|
48 | exports.detect = detect;
|
49 |
|
50 | exports.test = {
|
51 | parseWinAppDeployCmdListing: parseWinAppDeployCmdListing,
|
52 | parseAppDeployCmdListing: parseAppDeployCmdListing
|
53 | };
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | function winAppDeployCmdEnumerate(deployCmd, next) {
|
63 | var cmd = deployCmd,
|
64 | args = ['devices', '2'],
|
65 | child = spawn(cmd, args),
|
66 | out = '',
|
67 | result;
|
68 |
|
69 | child.stdout.on('data', function (data) {
|
70 | out += data.toString();
|
71 | });
|
72 |
|
73 | child.stderr.on('data', function (data) {
|
74 | out += data.toString();
|
75 | });
|
76 |
|
77 | child.on('close', function (code) {
|
78 | if (code) {
|
79 | var errmsg = out.trim().split(/\r\n|\n/).shift(),
|
80 | ex = new Error(/^Error: /.test(errmsg) ? errmsg.substring(7) : __('Failed to enumerate devices for WP SDK 10.0 (code %s)', code));
|
81 | next(ex, null);
|
82 | } else {
|
83 | var devices = parseWinAppDeployCmdListing(out);
|
84 | next(null, {
|
85 | devices: devices,
|
86 | emulators: []
|
87 | });
|
88 | }
|
89 | });
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | function parseWinAppDeployCmdListing(out) {
|
97 | var deviceListingRE = /^((\d{1,3}\.){3}\d{1,3})\s+([0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12})\s+(.+?)$/igm;
|
98 | var devices = [];
|
99 | var match,
|
100 | i = 0;
|
101 | while ((match = deviceListingRE.exec(out)) !== null)
|
102 | {
|
103 |
|
104 | devices.push({name: match[5], udid: match[3], index: i, wpsdk: null, ip: match[1], type: 'device'});
|
105 | i++;
|
106 | }
|
107 |
|
108 | return devices;
|
109 | }
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | function wptoolEnumerate(wpsdk, options, next) {
|
119 | function run(wpsdk, next) {
|
120 | var child = spawn(wptool, ['enumerate', '--wpsdk', wpsdk]),
|
121 | out = '',
|
122 | result;
|
123 |
|
124 | child.stdout.on('data', function (data) {
|
125 | out += data.toString();
|
126 | });
|
127 |
|
128 | child.stderr.on('data', function (data) {
|
129 | out += data.toString();
|
130 | });
|
131 |
|
132 | child.on('close', function (code) {
|
133 | if (code) {
|
134 | var errmsg = out.trim().split(/\r\n|\n/).shift(),
|
135 | ex = new Error(/^Error: /.test(errmsg) ? errmsg.substring(7) : __('Failed to enumerate devices/emulators for WP SDK %s (code %s)', wpsdk, code));
|
136 | next(ex, null);
|
137 | } else {
|
138 | try {
|
139 | next(null, JSON.parse(out));
|
140 | } catch (E) {
|
141 | next(null, {});
|
142 | }
|
143 | }
|
144 | });
|
145 | }
|
146 |
|
147 | return windowsphone.detect(options, function (err, phoneResults) {
|
148 | if (err) {
|
149 | return next(err);
|
150 | }
|
151 |
|
152 | if (!phoneResults.windowsphone[wpsdk]) {
|
153 |
|
154 | return next(null, {devices:[],emulators:[]});
|
155 | }
|
156 |
|
157 |
|
158 | async.parallel([
|
159 |
|
160 | function (cb) {
|
161 | if (phoneResults.windowsphone[wpsdk].deployCmd) {
|
162 | winAppDeployCmdEnumerate(phoneResults.windowsphone[wpsdk].deployCmd, cb);
|
163 | } else {
|
164 | cb(null, {devices: [],emulators: []});
|
165 | }
|
166 | },
|
167 |
|
168 | function (cb) {
|
169 |
|
170 | var wpToolCs = path.resolve(__dirname, '..', 'wptool', 'wptool.cs');
|
171 | checkOutdated(wpToolCs, wptool, function (err, outdated) {
|
172 | if (err) {
|
173 | return cb(err);
|
174 | }
|
175 | if (outdated) {
|
176 | return buildWpTool(options, function (err, path) {
|
177 | if (err) {
|
178 | return cb(err);
|
179 | }
|
180 | run(wpsdk, cb);
|
181 | });
|
182 | }
|
183 |
|
184 | run(wpsdk, cb);
|
185 | });
|
186 | }
|
187 | ], function (err, results) {
|
188 | if (err) {
|
189 | return next(err);
|
190 | }
|
191 |
|
192 | var combined = results[1];
|
193 | combined.devices = results[0].devices.concat(combined.devices);
|
194 | return next(null, combined);
|
195 | });
|
196 | });
|
197 | }
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | function parseAppDeployCmdListing(out, wpsdk) {
|
207 |
|
208 | var deviceListingRE = /^\s*(\d+)\s+([\w \.]+)/mg;
|
209 | deviceListingRE.exec(out);
|
210 | var emulators = [];
|
211 | var match;
|
212 | while ((match = deviceListingRE.exec(out)) !== null)
|
213 | {
|
214 | emulators.push({name: match[2], udid: wpsdk.replace('.', '-') + "-" + match[1], index: parseInt(match[1]), wpsdk: wpsdk, type: 'emulator'});
|
215 | }
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | if (wpsdk != '8.0') {
|
221 |
|
222 | emulators = emulators.filter(function (e) {
|
223 | return new RegExp("Emulator\ " + wpsdk).test(e.name);
|
224 | });
|
225 |
|
226 | }
|
227 | return emulators;
|
228 | }
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | function nativeEnumerate(wpsdk, options, next) {
|
238 | return windowsphone.detect(options, function (err, phoneResults) {
|
239 | if (err) {
|
240 | return next(err, null);
|
241 | }
|
242 |
|
243 | if (!phoneResults.windowsphone[wpsdk]) {
|
244 |
|
245 | return next(null, {devices:[],emulators:[]});
|
246 | }
|
247 |
|
248 | if (!phoneResults.windowsphone[wpsdk].deployCmd) {
|
249 | var ex = new Error(__('No deploy command found for WP SDK %s. Cannot enumerate devices.', wpsdk));
|
250 | return next(ex, null);
|
251 | }
|
252 |
|
253 | var cmd = phoneResults.windowsphone[wpsdk].deployCmd,
|
254 | args = ['/EnumerateDevices'],
|
255 | child = spawn(cmd, args),
|
256 | out = '',
|
257 | result;
|
258 |
|
259 | child.stdout.on('data', function (data) {
|
260 | out += data.toString();
|
261 | });
|
262 |
|
263 | child.stderr.on('data', function (data) {
|
264 | out += data.toString();
|
265 | });
|
266 |
|
267 | child.on('close', function (code) {
|
268 | if (code) {
|
269 | var errmsg = out.trim().split(/\r\n|\n/).shift(),
|
270 | ex = new Error(/^Error: /.test(errmsg) ? errmsg.substring(7) : __('Failed to enumerate devices/emulators for WP SDK %s (code %s)', wpsdk, code));
|
271 | next(ex, null);
|
272 | } else {
|
273 | var emulators = parseAppDeployCmdListing(out, wpsdk);
|
274 | next(null, {
|
275 | devices: [{name: 'Device', udid: 0, index: 0, wpsdk: null, type: 'device'}],
|
276 | emulators: emulators
|
277 | });
|
278 | }
|
279 | });
|
280 | });
|
281 | }
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | function detect(options, callback) {
|
291 | return enumerate(options, function (err, results) {
|
292 | var result = {
|
293 | emulators: {},
|
294 | devices: [],
|
295 | issues: []
|
296 | },
|
297 | tmp = {};
|
298 |
|
299 | if (err && !results) {
|
300 |
|
301 | callback(err);
|
302 | } else {
|
303 | Object.keys(results).forEach(function (wpsdk) {
|
304 | result.emulators[wpsdk] = results[wpsdk].emulators;
|
305 | results[wpsdk].devices.forEach(function (dev) {
|
306 | if (!tmp[dev.udid]) {
|
307 | tmp[dev.udid] = result.devices.length+1;
|
308 | result.devices.push(dev);
|
309 | } else if (dev.wpsdk) {
|
310 | result.devices[tmp[dev.udid]-1] = dev;
|
311 | }
|
312 | });
|
313 | });
|
314 |
|
315 |
|
316 | var wpsdkIndex = -1,
|
317 | realDeviceIndex = -1;
|
318 | for (var i = 0; i < result.devices.length; i++) {
|
319 | var dev = result.devices[i];
|
320 | if (dev.udid == 0 && dev.wpsdk) {
|
321 | wpsdkIndex = i;
|
322 | } else if (dev.udid != 0 && !dev.wpsdk) {
|
323 |
|
324 | realDeviceIndex = i;
|
325 | }
|
326 | if (wpsdkIndex != -1 && realDeviceIndex != -1) {
|
327 | break;
|
328 | }
|
329 | };
|
330 | if (wpsdkIndex != -1 && realDeviceIndex != -1) {
|
331 |
|
332 | result.devices[realDeviceIndex].wpsdk = result.devices[wpsdkIndex].wpsdk;
|
333 |
|
334 | result.devices.splice(wpsdkIndex, 1);
|
335 | }
|
336 | }
|
337 |
|
338 | callback(null, result);
|
339 | });
|
340 | }
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 | function enumerate(options, callback) {
|
355 | return magik(options, callback, function (emitter, options, callback) {
|
356 | if (cache && !options.bypassCache) {
|
357 | emitter.emit('detected', cache);
|
358 | return callback(null, cache);
|
359 | }
|
360 |
|
361 | function runTool() {
|
362 | var results = {},
|
363 | errors = [],
|
364 | toDetect;
|
365 | if (options.supportedWindowsPhoneSDKVersions) {
|
366 | toDetect = wpsdks.filter(ver => appc.version.satisfies(ver, options.supportedWindowsPhoneSDKVersions, false));
|
367 | } else {
|
368 | toDetect = wpsdks;
|
369 | }
|
370 |
|
371 | async.eachSeries(toDetect, function (wpsdk, next) {
|
372 |
|
373 | var funcToCall = (wpsdk == '10.0') ? wptoolEnumerate : nativeEnumerate;
|
374 | funcToCall(wpsdk, options, function (err, result) {
|
375 | if (err) {
|
376 |
|
377 |
|
378 | if (!results[wpsdk]) {
|
379 | results[wpsdk] = {
|
380 | devices: [],
|
381 | emulators: [],
|
382 | };
|
383 | }
|
384 | errors.push(err);
|
385 | next();
|
386 | } else {
|
387 | results[wpsdk] = result;
|
388 | next();
|
389 | }
|
390 | });
|
391 | }, function (err) {
|
392 | if (err) {
|
393 | emitter.emit('error', err);
|
394 | return callback(err);
|
395 | }
|
396 |
|
397 | if (errors.length > 0 && !Object.keys(results).some(function (wpsdk) {
|
398 | return results[wpsdk].emulators.length > 0;
|
399 | })) {
|
400 | emitter.emit('error', errors[0]);
|
401 | return callback(errors[0], results);
|
402 | }
|
403 |
|
404 |
|
405 | Object.defineProperty(results, 'getByUdid', {
|
406 | value: function (udid) {
|
407 | var dev = null;
|
408 |
|
409 | function testDev(d) {
|
410 | if (d.udid == udid) {
|
411 | dev = d;
|
412 | return true;
|
413 | }
|
414 | }
|
415 |
|
416 | Object.keys(results).some(function (wpsdk) {
|
417 | return results[wpsdk].devices.some(testDev) || results[wpsdk].emulators.some(testDev);
|
418 | });
|
419 |
|
420 | return dev;
|
421 | }
|
422 | });
|
423 |
|
424 | cache = results;
|
425 | emitter.emit('detected', cache);
|
426 | callback(null, cache);
|
427 | });
|
428 | }
|
429 |
|
430 | runTool();
|
431 | });
|
432 | }
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 | function connect(udid, options, callback) {
|
452 | return magik(options, callback, function (emitter, options, callback) {
|
453 | if (udid === null || udid === void 0) {
|
454 | var ex = new Error(__('Missing required "%s" argument', 'udid'));
|
455 | emitter.emit('error', ex);
|
456 | return callback(ex);
|
457 | }
|
458 |
|
459 | enumerate(options)
|
460 | .on('error', function (err) {
|
461 | emitter.emit('error', err);
|
462 | callback(err);
|
463 | })
|
464 | .on('detected', function (results) {
|
465 |
|
466 | var dev = results.getByUdid(udid);
|
467 |
|
468 | if (!dev) {
|
469 | var err = new Error(__('Invalid udid "%s"', udid));
|
470 | emitter.emit('error', err);
|
471 | return callback(err);
|
472 | }
|
473 |
|
474 | var wpsdk = dev.wpsdk || options.wpsdk || options.preferredWindowsPhoneSDK || PREFERRED_SDK,
|
475 | done = function (err, result) {
|
476 | if (err) {
|
477 | emitter.emit(result || 'error', err);
|
478 | return callback(err);
|
479 | }
|
480 | emitter.emit('connected', result);
|
481 | callback(null, result);
|
482 | };
|
483 |
|
484 | if (wpsdk == '10.0') {
|
485 |
|
486 | wpToolConnect(dev, options, done);
|
487 | } else {
|
488 |
|
489 | nativeLaunch(dev, 'f8ce6878-0aeb-497f-bcf4-65be961d4bba', options, done);
|
490 | }
|
491 | });
|
492 | });
|
493 | }
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 | function buildWpTool(options, callback) {
|
504 |
|
505 |
|
506 |
|
507 |
|
508 | return assemblies.detect(options, function (err, results) {
|
509 | if (err) {
|
510 | return callback(err);
|
511 | }
|
512 |
|
513 |
|
514 | var requiredAssemblies = {
|
515 | 'Microsoft.SmartDevice.Connectivity.Interface': null,
|
516 | 'Microsoft.SmartDevice.MultiTargeting.Connectivity': null
|
517 | },
|
518 | missing = Object.keys(requiredAssemblies).filter(function (assembly) {
|
519 | var r = results.assemblies[assembly];
|
520 | if (!r) return true;
|
521 | requiredAssemblies[assembly] = r[Object.keys(r).sort().pop()];
|
522 | });
|
523 |
|
524 | if (missing.length) {
|
525 | var ex = new Error(__('Missing one or more required Microsoft .NET assemblies: %s', missing.join(', ')));
|
526 | return callback(ex);
|
527 | }
|
528 |
|
529 |
|
530 | var project = path.resolve(__dirname, '..', 'wptool', 'wptool.csproj'),
|
531 | parser = new DOMParser({ errorHandler: function () {} }),
|
532 | dom = parser.parseFromString(fs.readFileSync(project).toString(), 'text/xml');
|
533 |
|
534 | (function updateRefs(node) {
|
535 | while (node) {
|
536 | if (node.nodeType === appc.xml.ELEMENT_NODE) {
|
537 | switch (node.tagName) {
|
538 | case 'Reference':
|
539 | var inc = node.getAttribute('Include');
|
540 | if (inc) {
|
541 | var name = inc.split(',').shift();
|
542 |
|
543 | if (requiredAssemblies[name]) {
|
544 | node.setAttribute('Include', name + ', Version=' + requiredAssemblies[name].assemblyVersion + ', Culture=neutral, PublicKeyToken=' + requiredAssemblies[name].publicKeyToken + ', processorArchitecture=MSIL');
|
545 |
|
546 | var child = node.firstChild,
|
547 | found = false;
|
548 |
|
549 | while (child) {
|
550 | if (child.nodeType === appc.xml.ELEMENT_NODE && child.tagName === 'HintPath') {
|
551 | while (child.firstChild) {
|
552 | child.removeChild(child.firstChild);
|
553 | }
|
554 | child.appendChild(dom.createTextNode(requiredAssemblies[name].assemblyFile));
|
555 | found = true;
|
556 | break;
|
557 | }
|
558 | child = child.nextSibling;
|
559 | }
|
560 |
|
561 | if (!found) {
|
562 | child = dom.createElement('HintPath');
|
563 | child.appendChild(dom.createTextNode(requiredAssemblies[name].assemblyFile));
|
564 | node.appendChild(child);
|
565 | }
|
566 | }
|
567 | }
|
568 | break;
|
569 | default:
|
570 | updateRefs(node.firstChild);
|
571 | }
|
572 | }
|
573 | node = node.nextSibling;
|
574 | }
|
575 | }(dom.documentElement.firstChild));
|
576 |
|
577 | fs.writeFileSync(project, '<?xml version="1.0" encoding="UTF-8"?>\n' + dom.documentElement.toString());
|
578 |
|
579 |
|
580 | var d;
|
581 | fs.existsSync(d = path.resolve(__dirname, '..', 'wptool', 'bin')) && wrench.rmdirSyncRecursive(d);
|
582 | fs.existsSync(d = path.resolve(__dirname, '..', 'wptool', 'obj')) && wrench.rmdirSyncRecursive(d);
|
583 |
|
584 | var pathLength = 0;
|
585 | for(var assembly of Object.keys(requiredAssemblies)) {
|
586 | var assemblyPath = path.join(__dirname,'wptool', 'bin', 'Release' , assembly);
|
587 | if (assemblyPath.length > pathLength) {
|
588 | pathLength = assemblyPath.length;
|
589 | }
|
590 | }
|
591 | if (pathLength >= 260) {
|
592 | if (!fs.existsSync(tmpBuildDir)) {
|
593 | wrench.mkdirSyncRecursive(tmpBuildDir);
|
594 | }
|
595 | wrench.copyDirSyncRecursive(path.join(__dirname, '..', 'wptool'), tmpBuildDir, {
|
596 | forceDelete: true
|
597 | });
|
598 | project = path.join(tmpBuildDir, 'wptool.csproj');
|
599 | }
|
600 |
|
601 | visualstudio.build(appc.util.mix({
|
602 | buildConfiguration: 'Release',
|
603 | project: project
|
604 | }, options), function (err, result) {
|
605 | if (err) {
|
606 | return callback(err);
|
607 | }
|
608 |
|
609 | var src = path.resolve(path.dirname(project), 'bin', 'Release', 'wptool.exe');
|
610 |
|
611 | if (!fs.existsSync(src)) {
|
612 | var ex = new Error(__('Failed to build the wptool executable.'));
|
613 | return callback(ex);
|
614 | }
|
615 |
|
616 |
|
617 | fs.writeFileSync(wptool, fs.readFileSync(src));
|
618 |
|
619 |
|
620 | var srcdir = path.resolve(path.dirname(project), 'bin', 'Release');
|
621 | fs.readdirSync(srcdir).forEach(function(filename) {
|
622 | if (path.extname(filename) == '.dll') {
|
623 | var dest = path.resolve(__dirname, '..', 'bin', filename);
|
624 | fs.writeFileSync(dest, fs.readFileSync(path.resolve(srcdir, filename)));
|
625 | }
|
626 | });
|
627 |
|
628 | return callback(null, wptool);
|
629 | });
|
630 | });
|
631 | }
|
632 |
|
633 | function wpToolConnect(device, options, callback) {
|
634 | var args = [
|
635 | 'connect',
|
636 | device.index
|
637 | ],
|
638 | child = spawn(wptool, args),
|
639 | out = '',
|
640 | abortTimer,
|
641 | timedOut = false;
|
642 |
|
643 | child.stdout.on('data', function (data) {
|
644 | out += data.toString();
|
645 | });
|
646 |
|
647 | child.stderr.on('data', function (data) {
|
648 | out += data.toString();
|
649 | });
|
650 |
|
651 | child.on('close', function (code) {
|
652 | clearTimeout(abortTimer);
|
653 |
|
654 | try {
|
655 | var result = JSON.parse(out),
|
656 | pollEmulator;
|
657 |
|
658 | if (!result.success) {
|
659 | clearTimeout(abortTimer);
|
660 | return callback(new Error(__('Failed to connect to %s', device.name)));
|
661 | }
|
662 | device.ip = result.ip;
|
663 |
|
664 |
|
665 | if (device.type == 'emulator') {
|
666 | pollEmulator = function() {
|
667 | if (timedOut) {
|
668 | return;
|
669 | }
|
670 |
|
671 |
|
672 | emulator.status(device, function(err, status) {
|
673 | if (err) {
|
674 | clearTimeout(abortTimer);
|
675 | return callback(err);
|
676 | }
|
677 |
|
678 | if (status == 2) {
|
679 | clearTimeout(abortTimer);
|
680 | device.running = true;
|
681 | callback(null, device);
|
682 | } else {
|
683 |
|
684 | setTimeout(pollEmulator, 500);
|
685 | }
|
686 | });
|
687 | };
|
688 |
|
689 | setTimeout(pollEmulator, 250);
|
690 | } else {
|
691 |
|
692 | clearTimeout(abortTimer);
|
693 | device.running = true;
|
694 | callback(null, device);
|
695 | }
|
696 | } catch (e) {
|
697 | clearTimeout(abortTimer);
|
698 | callback(new Error(__('Failed to connect to %s', device.name)));
|
699 | }
|
700 | });
|
701 | if (options.timeout) {
|
702 | abortTimer = setTimeout(function () {
|
703 | timedOut = true;
|
704 | child.kill();
|
705 |
|
706 | var ex = new Error(__('Timed out after %d milliseconds trying to connect to %s', options.timeout, device.name));
|
707 | callback(ex, 'timeout');
|
708 | }, options.timeout);
|
709 | }
|
710 | }
|
711 |
|
712 |
|
713 |
|
714 |
|
715 | function wpToolLaunch(device, productGuid, options, callback) {
|
716 | var args = [
|
717 | 'launch',
|
718 | device.index,
|
719 | productGuid
|
720 | ],
|
721 | child = spawn(wptool, args),
|
722 | out = '',
|
723 | abortTimer;
|
724 |
|
725 | child.stdout.on('data', function (data) {
|
726 | out += data.toString();
|
727 | });
|
728 |
|
729 | child.stderr.on('data', function (data) {
|
730 | out += data.toString();
|
731 | });
|
732 |
|
733 | child.on('close', function (code) {
|
734 | clearTimeout(abortTimer);
|
735 |
|
736 | try {
|
737 | var result = JSON.parse(out);
|
738 | if (result.success) {
|
739 | return callback(null, device);
|
740 | }
|
741 | var ex = new Error(__('Failed to launch app: %s', result.message));
|
742 | callback(ex);
|
743 | } catch (e) {
|
744 | var ex = new Error(__('Failed to connect to emulator: %s', out));
|
745 | callback(ex);
|
746 | }
|
747 | });
|
748 | if (options.timeout) {
|
749 | abortTimer = setTimeout(function () {
|
750 | child.kill();
|
751 |
|
752 | var ex = new Error(__('Timed out after %d milliseconds trying to connect to %s', options.timeout, device.type));
|
753 | callback(ex, 'timeout');
|
754 | }, options.timeout);
|
755 | }
|
756 | }
|
757 |
|
758 | function nativeLaunch(device, appid, options, callback) {
|
759 | windowsphone.detect(options, function (err, phoneResults) {
|
760 | if (err) {
|
761 | return callback(err);
|
762 | }
|
763 |
|
764 | var wpsdk = device.wpsdk || options.wpsdk || options.preferredWindowsPhoneSDK || PREFERRED_SDK,
|
765 | deployCmd = phoneResults.windowsphone[wpsdk].deployCmd,
|
766 | args = [
|
767 | '/launch',
|
768 | appid,
|
769 | '/targetdevice:' + device.index
|
770 | ],
|
771 | child,
|
772 | out = '',
|
773 | abortTimer;
|
774 | if (!deployCmd) {
|
775 | var ex = new Error(__('Windows Phone SDK v%s does not appear to have an App deploy tool.', wpsdk));
|
776 | return callback(ex);
|
777 | }
|
778 |
|
779 | child = spawn(deployCmd, args);
|
780 |
|
781 | child.stdout.on('data', function (data) {
|
782 | out += data.toString();
|
783 | });
|
784 |
|
785 | child.stderr.on('data', function (data) {
|
786 | out += data.toString();
|
787 | });
|
788 |
|
789 | child.on('close', function (code) {
|
790 | clearTimeout(abortTimer);
|
791 |
|
792 | var errmsg = out.trim().split(/\r\n|\n/).shift(),
|
793 | ex = new Error(/^Error: /.test(errmsg) ? errmsg.substring(7) : __('Failed to start %s (code %s)', device.name, code));
|
794 |
|
795 |
|
796 |
|
797 | if (errmsg == '' || errmsg.indexOf('The application is not installed.') != -1) {
|
798 |
|
799 | callback(null, device);
|
800 | } else {
|
801 |
|
802 | callback(ex);
|
803 | }
|
804 | });
|
805 | if (options.timeout) {
|
806 | abortTimer = setTimeout(function () {
|
807 | child.kill();
|
808 |
|
809 | var ex = new Error(__('Timed out after %d milliseconds trying to connect to %s', options.timeout, device.type));
|
810 | callback(ex, 'timeout');
|
811 | }, options.timeout);
|
812 | }
|
813 | });
|
814 | }
|
815 |
|
816 | function nativeInstall(deployCmd, device, appPath, options, callback) {
|
817 |
|
818 | var args = [
|
819 | options.skipLaunch ? '/install' : '/installlaunch',
|
820 | appPath,
|
821 | '/targetdevice:' + device.index
|
822 | ],
|
823 | child = spawn(deployCmd, args),
|
824 | out = '',
|
825 | abortTimer;
|
826 |
|
827 | child.stdout.on('data', function (data) {
|
828 | out += data.toString();
|
829 | });
|
830 |
|
831 | child.stderr.on('data', function (data) {
|
832 | out += data.toString();
|
833 | });
|
834 |
|
835 | child.on('close', function (code) {
|
836 | clearTimeout(abortTimer);
|
837 |
|
838 | if (out.trim() != '' && code) {
|
839 | var errmsg = out.trim().split(/\r\n|\n/).shift(),
|
840 | ex = new Error(/^Error: /.test(errmsg) ? errmsg.substring(7) : __('Failed to install app (code %s)', code));
|
841 | callback(ex);
|
842 | } else {
|
843 | device.running = true;
|
844 | callback(null, device);
|
845 | }
|
846 | });
|
847 | if (options.timeout) {
|
848 | abortTimer = setTimeout(function () {
|
849 | child.kill();
|
850 |
|
851 | var ex = new Error(__('Timed out after %d milliseconds trying to connect to %s', options.timeout, device.type));
|
852 | callback(ex, 'timeout');
|
853 | }, options.timeout);
|
854 | }
|
855 | }
|
856 |
|
857 |
|
858 |
|
859 |
|
860 |
|
861 |
|
862 |
|
863 |
|
864 |
|
865 |
|
866 |
|
867 | function wpToolInstall(deployCmd, device, appPath, options, callback) {
|
868 | if (!device.ip || !device.running) {
|
869 |
|
870 | return wpToolConnect(device, options, function(err, dev) {
|
871 | if (err) {
|
872 | return callback(err);
|
873 | }
|
874 |
|
875 | wpToolInstall(deployCmd, dev, appPath, options, callback);
|
876 | });
|
877 | }
|
878 |
|
879 | var args = [
|
880 | options.forceUnInstall ? 'uninstall' : 'install',
|
881 | '-file',
|
882 | appPath,
|
883 | '-ip',
|
884 | device.ip
|
885 | ],
|
886 | child = spawn(deployCmd, args),
|
887 | out = '',
|
888 | abortTimer;
|
889 |
|
890 | child.stdout.on('data', function (data) {
|
891 | out += data.toString();
|
892 | });
|
893 |
|
894 | child.stderr.on('data', function (data) {
|
895 | out += data.toString();
|
896 | });
|
897 |
|
898 |
|
899 |
|
900 | child.on('close', function (code) {
|
901 | clearTimeout(abortTimer);
|
902 |
|
903 | if (code) {
|
904 |
|
905 | if (code == '2148734208') {
|
906 | if (out.indexOf('0x80131500 - Failed to install or update package: Unspecified error') != -1) {
|
907 |
|
908 | callback(new Error('A debug application is already installed, please remove existing debug application.'));
|
909 | } else if (out.indexOf('because the current user does not have that package installed') == -1) {
|
910 | if (options.forceUnInstall) {
|
911 | wpToolInstall(deployCmd, device, appPath, options, callback);
|
912 | } else {
|
913 | callback(new Error('A debug application is already installed. Please increment the version number of the application, or use forceUnInstall option to explicitly delete existing app.'));
|
914 | }
|
915 | } else {
|
916 |
|
917 | options.forceUnInstall = false;
|
918 | wpToolInstall(deployCmd, device, appPath, options, callback);
|
919 | }
|
920 | } else {
|
921 | var errmsg = out.trim().split(/\r\n|\n/).shift(),
|
922 | ex = new Error(/^Error: /.test(errmsg) ? errmsg.substring(7) : __('Failed to install app (code %s): %s', code, out));
|
923 | callback(ex);
|
924 | }
|
925 | } else {
|
926 | var errmsg = /failed\. (\w*)\r?\n(.*)/.exec(out);
|
927 | if (errmsg) {
|
928 | var err = errmsg[1],
|
929 | msg = errmsg[2];
|
930 |
|
931 | if (err == '0x80073CF9') {
|
932 | callback(new Error('A debug application is already installed, please remove existing debug application'));
|
933 | } else if (err == '0x80073CFB') {
|
934 | if (options.forceUnInstall) {
|
935 |
|
936 | wpToolInstall(deployCmd, device, appPath, options, callback);
|
937 | } else {
|
938 | callback(new Error('A debug application is already installed. Please increment the version number of the application, or use forceUnInstall option to explicitly delete existing app.'));
|
939 | }
|
940 | } else {
|
941 | callback(new Error(__('Failed to install app (code %s): %s', err, msg)));
|
942 | }
|
943 | } else {
|
944 |
|
945 | if (options.forceUnInstall) {
|
946 | options.forceUnInstall = false;
|
947 | wpToolInstall(deployCmd, device, appPath, options, callback);
|
948 | } else {
|
949 | callback(null, device);
|
950 | }
|
951 | }
|
952 | }
|
953 | });
|
954 | if (options.timeout) {
|
955 | abortTimer = setTimeout(function () {
|
956 | child.kill();
|
957 |
|
958 | var ex = new Error(__('Timed out after %d milliseconds trying to connect to %s', options.timeout, device.type));
|
959 | callback(ex, 'timeout');
|
960 | }, options.timeout);
|
961 | }
|
962 |
|
963 | }
|
964 |
|
965 |
|
966 |
|
967 |
|
968 |
|
969 |
|
970 |
|
971 |
|
972 |
|
973 |
|
974 |
|
975 |
|
976 |
|
977 |
|
978 |
|
979 |
|
980 |
|
981 |
|
982 |
|
983 | function install(device, appPath, options, callback) {
|
984 | return magik(options, callback, function (emitter, options, callback) {
|
985 | windowsphone.detect(options, function (err, phoneResults) {
|
986 | if (err) {
|
987 | emitter.emit('error', err);
|
988 | return callback(err);
|
989 | }
|
990 |
|
991 | var wpsdk = device.wpsdk || options.wpsdk || options.preferredWindowsPhoneSDK || PREFERRED_SDK,
|
992 | cmd = phoneResults.windowsphone[wpsdk].deployCmd;
|
993 | if (!cmd) {
|
994 | var ex = new Error(__('Windows Phone SDK v%s does not appear to have an App deploy tool.', wpsdk));
|
995 | return callback(ex);
|
996 | }
|
997 |
|
998 | if (wpsdk == '10.0') {
|
999 | if (!options.skipLaunch) {
|
1000 | var guid;
|
1001 |
|
1002 | async.parallel([
|
1003 | function (next) {
|
1004 | wpToolInstall(cmd, device, appPath, options, function (err, result) {
|
1005 | if (err) {
|
1006 | emitter.emit(result || 'error', err);
|
1007 | return next(err);
|
1008 | }
|
1009 | emitter.emit('installed', device);
|
1010 | next();
|
1011 | });
|
1012 | },
|
1013 | function (next) {
|
1014 | if (options.appGuid) {
|
1015 | guid = options.appGuid;
|
1016 | next();
|
1017 | } else {
|
1018 | getProductGUID(appPath, options, function(err, productGuid) {
|
1019 | if (err) {
|
1020 | emitter.emit('error', err);
|
1021 | return next(err);
|
1022 | }
|
1023 |
|
1024 | guid = productGuid;
|
1025 | next();
|
1026 | });
|
1027 | }
|
1028 | }
|
1029 | ], function (err, results) {
|
1030 | if (err) {
|
1031 | return callback(err);
|
1032 | }
|
1033 |
|
1034 | wpToolLaunch(device, guid, options, function (err, result) {
|
1035 | if (err) {
|
1036 | emitter.emit(result || 'error', err);
|
1037 | return callback(err);
|
1038 | }
|
1039 | emitter.emit('launched', device);
|
1040 | callback(null, result);
|
1041 | });
|
1042 | });
|
1043 | } else {
|
1044 |
|
1045 | wpToolInstall(cmd, device, appPath, options, function (err, result) {
|
1046 | if (err) {
|
1047 | emitter.emit(result || 'error', err);
|
1048 | return callback(err);
|
1049 | }
|
1050 |
|
1051 | emitter.emit('installed', device);
|
1052 | return callback(null, result);
|
1053 | });
|
1054 | }
|
1055 | } else {
|
1056 | nativeInstall(cmd, device, appPath, options, function (err, result) {
|
1057 | if (err) {
|
1058 | emitter.emit(result || 'error', err);
|
1059 | return callback(err);
|
1060 | }
|
1061 | emitter.emit('installed', device);
|
1062 | if (!options.skipLaunch) {
|
1063 | emitter.emit('launched', device);
|
1064 | }
|
1065 | callback(null, result);
|
1066 | });
|
1067 | }
|
1068 | });
|
1069 | });
|
1070 | }
|
1071 |
|
1072 |
|
1073 |
|
1074 |
|
1075 |
|
1076 |
|
1077 |
|
1078 |
|
1079 |
|
1080 |
|
1081 | function getProductGUID(appxFile, options, callback) {
|
1082 | appc.subprocess.getRealName(path.resolve(__dirname, '..', 'bin', 'wp_get_appx_metadata.ps1'), function (err, script) {
|
1083 | if (err) {
|
1084 | return callback(err);
|
1085 | }
|
1086 |
|
1087 | appc.subprocess.run(options.powershell || 'powershell', [
|
1088 | '-ExecutionPolicy', 'Bypass', '-NoLogo', '-NonInteractive', '-NoProfile',
|
1089 | '-File',
|
1090 | script,
|
1091 | appxFile
|
1092 | ], function (code, out, err) {
|
1093 | if (code) {
|
1094 | var ex = new Error(__('Failed to detect product id of appx: %s', out));
|
1095 | return callback(ex);
|
1096 | }
|
1097 |
|
1098 | callback(null, out.trim());
|
1099 | });
|
1100 | });
|
1101 | }
|