1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | const
|
15 | appc = require('node-appc'),
|
16 | async = require('async'),
|
17 | fs = require('fs'),
|
18 | magik = require('./utilities').magik,
|
19 | path = require('path'),
|
20 | spawn = require('child_process').spawn,
|
21 | vs2017support = path.resolve(__dirname, '..', 'bin', 'vs2017support.exe'),
|
22 | __ = appc.i18n(__dirname).__;
|
23 |
|
24 | var cache,
|
25 | runningBuilds = {};
|
26 |
|
27 | exports.detect = detect;
|
28 | exports.build = build;
|
29 |
|
30 | function runVS2017Tool(next) {
|
31 | var child = spawn(vs2017support),
|
32 | out = '';
|
33 | child.stdout.on('data', function (data) {
|
34 | out += data.toString();
|
35 | });
|
36 |
|
37 | child.stderr.on('data', function (data) {
|
38 | out += data.toString();
|
39 | });
|
40 |
|
41 | child.on('error', function (err) {
|
42 | next(null, {
|
43 | issues:[
|
44 | {
|
45 | id: 'WINDOWS_VISUALSTUDIO_2017_DETECT',
|
46 | type: 'error',
|
47 | message: err
|
48 | }
|
49 | ]
|
50 | });
|
51 | });
|
52 |
|
53 | child.on('close', function (code) {
|
54 | if (code) {
|
55 | next(null, {
|
56 | issues:[
|
57 | {
|
58 | id: 'WINDOWS_VISUALSTUDIO_2017_DETECT',
|
59 | type: 'error',
|
60 | message: out.trim()
|
61 | }
|
62 | ]
|
63 | });
|
64 | } else {
|
65 | try {
|
66 | next(null, JSON.parse(out));
|
67 | } catch (E) {
|
68 | next(null, {});
|
69 | }
|
70 | }
|
71 | });
|
72 | }
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | function detect(options, callback) {
|
92 | return magik(options, callback, function (emitter, options, callback) {
|
93 | if (cache && !options.bypassCache) {
|
94 | emitter.emit('detected', cache);
|
95 | return callback(null, cache);
|
96 | }
|
97 |
|
98 | detectInstallations(emitter, options, callback);
|
99 | });
|
100 | }
|
101 |
|
102 | function detectVS2017Installations(emitter, options, callback) {
|
103 | var results = {
|
104 | selectedVisualStudio: null,
|
105 | visualstudio: null,
|
106 | issues: []
|
107 | };
|
108 |
|
109 | runVS2017Tool(function(err, result) {
|
110 | if (result.issues) {
|
111 | results.issues = result.issues;
|
112 | }
|
113 |
|
114 | if (result.visualstudio) {
|
115 | results.visualstudio = {};
|
116 | async.each(Object.keys(result.visualstudio), function (vsname, next) {
|
117 | var vsinfo = result.visualstudio[vsname];
|
118 |
|
119 |
|
120 | vsinfo.wpsdk = null;
|
121 | vsinfo.registryKey = null;
|
122 | vsinfo.clrVersion = null;
|
123 | vsinfo.selected = false;
|
124 | vsinfo.vsDevCmd = path.join(vsinfo.path, 'Common7', 'Tools', 'VsDevCmd.bat');
|
125 |
|
126 |
|
127 | var vcvarsall = path.join(vsinfo.path, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat');
|
128 | if (fs.existsSync(vcvarsall)) {
|
129 | appc.subprocess.getRealName(vcvarsall, function (err, vcvarsall) {
|
130 | if (!err) {
|
131 | vsinfo.vcvarsall = vcvarsall;
|
132 | results.visualstudio[vsname] = vsinfo;
|
133 | }
|
134 | next();
|
135 | });
|
136 | } else {
|
137 | next();
|
138 | }
|
139 | }, function() {
|
140 | callback(err, results);
|
141 | });
|
142 | } else {
|
143 | callback(err, results);
|
144 | }
|
145 | });
|
146 | }
|
147 |
|
148 | function detectInstallations(emitter, options, callback) {
|
149 |
|
150 | detectVS2017Installations(emitter, options, function(err, results) {
|
151 | var keyRegExp = /.+\\(\d+\.\d)_config$/i,
|
152 | possibleVersions = {};
|
153 |
|
154 | function finalize() {
|
155 | cache = results;
|
156 | emitter.emit('detected', results);
|
157 | callback(null, results);
|
158 | }
|
159 |
|
160 | async.each([
|
161 | 'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\VisualStudio',
|
162 | 'HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\VisualStudio',
|
163 | 'HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio',
|
164 | 'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\VSWinExpress',
|
165 | 'HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\VSWinExpress',
|
166 | 'HKEY_CURRENT_USER\\Software\\Microsoft\\VSWinExpress',
|
167 | 'HKEY_CURRENT_USER\\Software\\Microsoft\\VPDExpress'
|
168 | ], function (keyPath, next) {
|
169 | appc.subprocess.run('reg', ['query', keyPath], function (code, out, err) {
|
170 | if (!code) {
|
171 | out.trim().split(/\r\n|\n/).forEach(function (configKey) {
|
172 | configKey = configKey.trim();
|
173 | var m = configKey.match(keyRegExp);
|
174 | if (m) {
|
175 | possibleVersions[configKey] = {
|
176 | version: m[1],
|
177 | configKey: configKey
|
178 | };
|
179 | }
|
180 | });
|
181 | }
|
182 | next();
|
183 | });
|
184 | }, function () {
|
185 |
|
186 | if (!results.visualstudio && !Object.keys(possibleVersions).length) {
|
187 | results.issues.push({
|
188 | id: 'WINDOWS_VISUAL_STUDIO_NOT_INSTALLED',
|
189 | type: 'error',
|
190 | message: __('Microsoft Visual Studio not found.') + '\n' +
|
191 | __('You will be unable to build Windows Phone or Windows Store apps.')
|
192 | });
|
193 | return finalize();
|
194 | }
|
195 |
|
196 |
|
197 | async.each(Object.keys(possibleVersions), function (configKey, next) {
|
198 | appc.subprocess.run('reg', ['query', configKey, '/v', '*'], function (code, out, err) {
|
199 | results.visualstudio || (results.visualstudio = {});
|
200 |
|
201 | var ver = possibleVersions[configKey].version,
|
202 | info = results.visualstudio[ver] = {
|
203 | version: ver,
|
204 | registryKey: configKey,
|
205 | supported: !options.supportedVisualStudioVersions || appc.version.satisfies(ver, options.supportedVisualStudioVersions, true),
|
206 | vcvarsall: null,
|
207 | msbuildVersion: null,
|
208 | wpsdk: null,
|
209 | selected: false
|
210 | };
|
211 |
|
212 | if (!code) {
|
213 |
|
214 | out.trim().split(/\r\n|\n/).forEach(function (line) {
|
215 | var parts = line.trim().split(' ').map(function (p) { return p.trim(); });
|
216 | if (parts.length == 3) {
|
217 | if (parts[0] == 'CLR Version') {
|
218 | info.clrVersion = parts[2];
|
219 | } else if (parts[0] == 'ShellFolder') {
|
220 | info.path = parts[2];
|
221 | }
|
222 | }
|
223 | });
|
224 |
|
225 |
|
226 | if (info.path && fs.existsSync(info.path)) {
|
227 |
|
228 | info.vsDevCmd = path.join(info.path, 'Common7', 'Tools', 'VsDevCmd.bat');
|
229 |
|
230 |
|
231 | var vcvarsall = path.join(info.path, 'VC', 'vcvarsall.bat');
|
232 | if (fs.existsSync(vcvarsall)) {
|
233 | info.vcvarsall = vcvarsall;
|
234 | }
|
235 |
|
236 |
|
237 | var wpsdkDir = path.join(info.path, 'VC', 'WPSDK');
|
238 | fs.existsSync(wpsdkDir) && fs.readdirSync(wpsdkDir).forEach(function (ver) {
|
239 | var vcvarsphone = path.join(wpsdkDir, ver, 'vcvarsphoneall.bat');
|
240 | if (fs.existsSync(vcvarsphone) && /^wp\d+$/i.test(ver)) {
|
241 |
|
242 | var name = (parseInt(ver.replace(/^wp/i, '')) / 10).toFixed(1);
|
243 | info.wpsdk || (info.wpsdk = {});
|
244 | info.wpsdk[name] = {
|
245 | vcvarsphone: vcvarsphone
|
246 | };
|
247 | }
|
248 | });
|
249 | }
|
250 | }
|
251 |
|
252 | if (info.vcvarsall) {
|
253 | appc.subprocess.getRealName(info.vcvarsall, function (err, vcvarsall) {
|
254 | if (!err) {
|
255 | info.vcvarsall = vcvarsall;
|
256 |
|
257 |
|
258 | vcvarsall = vcvarsall.replace(/(\(|\)|\s)/g, '^$1');
|
259 |
|
260 |
|
261 | appc.subprocess.run('cmd', [ '/C', vcvarsall + ' && MSBuild /version' ], function (code, out, err) {
|
262 | if (code) {
|
263 | results.issues.push({
|
264 | id: 'WINDOWS_MSBUILD_ERROR',
|
265 | type: 'error',
|
266 | message: __('Failed to run MSBuild.') + '\n' +
|
267 | __('This is most likely due to Visual Studio cannot find a suitable .NET framework.') + '\n' +
|
268 | __('Please install the latest .NET framework.')
|
269 | });
|
270 | } else {
|
271 | var chunks = out.trim().split(/\r\n\r\n|\n\n/);
|
272 | chunks.shift();
|
273 |
|
274 | var ver = info.msbuildVersion = chunks.shift().split(/\r\n|\n/).pop().trim();
|
275 |
|
276 | if (options.supportedMSBuildVersions && !appc.version.satisfies(ver, options.supportedMSBuildVersions)) {
|
277 | results.issues.push({
|
278 | id: 'WINDOWS_MSBUILD_TOO_OLD',
|
279 | type: 'error',
|
280 | message: __('The MSBuild version %s is too old.', ver) + '\n' +
|
281 | __("Titanium requires .NET MSBuild '%s'.", options.supportedMSBuildVersions) + '\n' +
|
282 | __('Please install the latest .NET framework.')
|
283 | });
|
284 | }
|
285 | }
|
286 |
|
287 | next();
|
288 | });
|
289 | } else {
|
290 | next();
|
291 | }
|
292 | });
|
293 | } else {
|
294 | next();
|
295 | }
|
296 | });
|
297 | }, function () {
|
298 |
|
299 | if (!Object.keys(results.visualstudio).length) {
|
300 | results.issues.push({
|
301 | id: 'WINDOWS_VISUAL_STUDIO_NOT_INSTALLED',
|
302 | type: 'error',
|
303 | message: __('Microsoft Visual Studio not found.') + '\n' +
|
304 | __('You will be unable to build Windows Phone or Windows Store apps.')
|
305 | });
|
306 | return finalize();
|
307 | }
|
308 |
|
309 | var preferred = options.preferredVisualStudio;
|
310 | if (!results.visualstudio[preferred] || !results.visualstudio[preferred].supported) {
|
311 | preferred = Object.keys(results.visualstudio).filter(function (v) { return results.visualstudio[v].supported; }).sort().pop();
|
312 | }
|
313 | if (preferred) {
|
314 | results.visualstudio[preferred].selected = true;
|
315 | results.selectedVisualStudio = results.visualstudio[preferred];
|
316 | }
|
317 |
|
318 | finalize();
|
319 | });
|
320 | });
|
321 | });
|
322 | };
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 | function build(options, callback) {
|
344 | return magik(options, callback, function (emitter, options, callback) {
|
345 |
|
346 | if (typeof options.project !== 'string' || !options.project) {
|
347 | var ex = new Error(__('Missing required "%s" argument', 'options.project'));
|
348 | emitter.emit('error', ex);
|
349 | return callback(ex);
|
350 | }
|
351 |
|
352 |
|
353 | if (!fs.existsSync(options.project)) {
|
354 | var err = new Error(__('Specified project does not exists: %s', options.project));
|
355 | emitter.emit('error', err);
|
356 | return callback(err);
|
357 | }
|
358 |
|
359 | detect(options, function (err, results) {
|
360 | if (err) {
|
361 | emitter.emit('error', err);
|
362 | return callback(err);
|
363 | }
|
364 |
|
365 | var vsInfo = results.selectedVisualStudio;
|
366 |
|
367 | if (!vsInfo || !vsInfo.vcvarsall) {
|
368 | var e = new Error(__('Unable to find a supported Visual Studio installation'));
|
369 | emitter.emit('error', e);
|
370 | return callback(e);
|
371 | }
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 | if (runningBuilds[options.project]) {
|
378 | runningBuilds[options.project].push({
|
379 | emitter: emitter,
|
380 | callback: callback
|
381 | });
|
382 | return;
|
383 | }
|
384 |
|
385 | runningBuilds[options.project] = [ {
|
386 | emitter: emitter,
|
387 | callback: callback
|
388 | } ];
|
389 |
|
390 | var p = spawn((process.env.comspec || 'cmd.exe'), [ '/S', '/C', vsInfo.vsDevCmd.replace(/[ \(\)\&]/g, '^$&'),
|
391 | `&& MSBuild /t:rebuild /p:configuration=${(options.buildConfiguration || 'Release')} "${options.project}"`
|
392 | ], { windowsVerbatimArguments: true }),
|
393 | out = '',
|
394 | err = '';
|
395 |
|
396 | p.stdout.on('data', function(data) {
|
397 | out += data.toString();
|
398 | });
|
399 |
|
400 | p.stderr.on('data', function(data) {
|
401 | err += data.toString();
|
402 | });
|
403 |
|
404 | p.on('close', function (code) {
|
405 | var queue = runningBuilds[options.project];
|
406 | delete runningBuilds[options.project];
|
407 |
|
408 | var result = {
|
409 | code: code,
|
410 | out: out,
|
411 | err: err
|
412 | };
|
413 |
|
414 | queue.forEach(function (p) {
|
415 | if (code) {
|
416 | var err = new Error(__('Failed to build project %s (code %s)', options.project, code));
|
417 | err.extendedError = result;
|
418 | p.emitter.emit('error', err);
|
419 | p.callback(err);
|
420 | } else {
|
421 | p.emitter.emit('success', result);
|
422 | p.callback(null, result);
|
423 | }
|
424 | });
|
425 | });
|
426 | });
|
427 | });
|
428 | }
|