blob: b52260987b39f6048d27ebc5dbf4e110afd86d38 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var Q = require('q');
var path = require('path');
var fs = require('fs');
var shell = require('shelljs');
var Version = require('./Version');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
function MSBuildTools (version, path) {
this.version = version;
this.path = path;
}
MSBuildTools.prototype.buildProject = function (projFile, buildType, buildarch, buildFlags) {
events.emit('log', 'Building project: ' + projFile);
events.emit('log', '\tConfiguration : ' + buildType);
events.emit('log', '\tPlatform : ' + buildarch);
events.emit('log', '\tBuildflags : ' + buildFlags);
events.emit('log', '\tMSBuildTools : ' + this.path);
// Additional requirement checks
var checkWinSDK = function (target_platform) {
return require('./check_reqs').isWinSDKPresent(target_platform);
};
var checkPhoneSDK = function () {
return require('./check_reqs').isPhoneSDKPresent();
};
// default build args
var args = ['/clp:NoSummary;NoItemAndPropertyList;Verbosity=minimal', '/nologo',
'/p:Configuration=' + buildType,
'/p:Platform=' + buildarch];
// add buildFlags if present
if (buildFlags) {
args = args.concat(buildFlags);
}
var that = this;
var promise;
// Check if SDK required to build the respective platform is present. If not present, return with corresponding error, else call msbuild.
if (projFile.indexOf('CordovaApp.Phone.jsproj') > -1) {
promise = checkPhoneSDK();
} else if (projFile.indexOf('CordovaApp.Windows.jsproj') > -1) {
promise = checkWinSDK('8.1');
} else {
promise = checkWinSDK('10.0');
}
return promise.then(function () {
console.log('buildProject spawn:', path.join(that.path, 'msbuild'), [projFile].concat(args), { stdio: 'inherit' });
return spawn(path.join(that.path, 'msbuild'), [projFile].concat(args), { stdio: 'inherit' });
});
};
// check_reqs.js -> checkMSBuild()
module.exports.findAllAvailableVersions = function () {
// console.log('findAllAvailableVersions');
var msBuildPath = '';
// Use MSBUILDDIR environment variable if defined to find MSBuild.
// MSBUILDDIR = C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin
// MSBUILDDIR = C:\Program Files (x86)\MSBuild\14.0\bin\
if (process.env.MSBUILDDIR) {
events.emit('log', 'ENV var MSBUILDDIR is set', process.env.MSBUILDDIR);
msBuildPath = process.env.MSBUILDDIR;
return module.exports.getMSBuildToolsAt(msBuildPath)
.then(function (msBuildTools) {
return [msBuildTools];
})
// If MSBUILDDIR is not specified or doesn't contain a valid MSBuild
// - fall back to default discovery mechanism.
.catch(findAllAvailableVersionsFallBack);
}
// CB-11548 use VSINSTALLDIR environment if defined to find MSBuild.
if (process.env.VSINSTALLDIR) {
events.emit('log', 'ENV var VSINSTALLDIR is set', process.env.VSINSTALLDIR);
msBuildPath = path.join(process.env.VSINSTALLDIR, 'MSBuild/15.0/Bin');
return module.exports.getMSBuildToolsAt(msBuildPath)
.then(function (msBuildTools) {
return [msBuildTools];
})
// If VSINSTALLDIR is not specified or doesn't contain the MSBuild path we are
// looking for - fall back to default discovery mechanism.
.catch(findAllAvailableVersionsFallBack);
}
return findAllAvailableVersionsFallBack();
};
function findAllAvailableVersionsFallBack () {
// console.log('findAllAvailableVersionsFALLBACK');
var versions = ['15.9', '15.5', '15.0', '14.0', '12.0', '4.0'];
events.emit('verbose', 'Searching for available MSBuild versions...');
return Q.all(versions.map(checkMSBuildVersion)).then(function (unprocessedResults) {
return unprocessedResults.filter(function (item) {
return !!item;
});
});
}
// returns full path to msbuild tools required to build the project and tools version
// check_reqs.js -> run()
module.exports.findAvailableVersion = function () {
var versions = ['15.5', '15.0', '14.0', '12.0', '4.0'];
return Q.all(versions.map(checkMSBuildVersion)).then(function (versions) {
// console.log('findAvailableVersion', versions);
// select first msbuild version available, and resolve promise with it
var msbuildTools = versions[0] || versions[1] || versions[2] || versions[3] || versions[4];
return msbuildTools ? Q.resolve(msbuildTools) : Q.reject('MSBuild tools not found');
});
};
/**
* Gets MSBuildTools instance for user-specified location
*
* @param {String} location FS location where to search for MSBuild
* @returns Promise<MSBuildTools> The MSBuildTools instance at specified location
*/
module.exports.getMSBuildToolsAt = function (location) {
// console.log('getMSBuildToolsAt', location);
var msbuildExe = path.resolve(location, 'msbuild');
// TODO: can we account on these params availability and printed version format?
return spawn(msbuildExe, ['-version', '-nologo'])
.then(function (output) {
// MSBuild prints its' version as 14.0.25123.0, so we pick only first 2 segments
var version = output.match(/^(\d+\.\d+)/)[1];
// console.log('return `new MSBuildTools`', version, location);
return new MSBuildTools(version, location);
});
};
function checkMSBuildVersion (version) {
// console.log('checkMSBuildVersion', version);
// first, check if we have a VS 2017+ with such a version
var willows = module.exports.getWillowInstallations();
// console.log('willows', willows);
var correspondingWillows = willows.filter(function (inst) {
// console.log('willows.filter', inst.version, version, inst.version === version);
return inst.version === version;
});
// console.log('correspondingWillows', correspondingWillows);
var correspondingWillow = correspondingWillows[0]; // TODO Do not only handle one!
if (correspondingWillow) {
// super hacky: VS2017/Willow is 15.x but MSBuild is always 15.0 in path - so set that here
version = '15.0';
var toolsPath = path.join(correspondingWillow.path, 'MSBuild', version, 'Bin');
// console.log('matching VS:', version, toolsPath);
// console.log('from list of VS installations: ', correspondingWillows);
if (shell.test('-e', toolsPath)) {
var msbuild = module.exports.getMSBuildToolsAt(toolsPath);
// console.log('selected VS exists:', toolsPath);
// TODO check for JavaScript folder
return msbuild;
}
}
// older vs versions that were registered in registry
return spawn('reg', ['query', 'HKLM\\SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\' + version, '/v', 'MSBuildToolsPath'])
.then(function (output) {
// console.log('spawn', output);
// fetch msbuild path from 'reg' output
var toolsPath = /MSBuildToolsPath\s+REG_SZ\s+(.*)/i.exec(output);
if (toolsPath) {
toolsPath = toolsPath[1];
// CB-9565: Windows 10 invokes .NET Native compiler, which only runs on x86 arch,
// so if we're running an x64 Node, make sure to use x86 tools.
if ((version === '15.0' || version === '14.0') && toolsPath.indexOf('amd64') > -1) {
toolsPath = path.resolve(toolsPath, '..');
}
events.emit('verbose', 'Found MSBuild v' + version + ' at ' + toolsPath);
return new MSBuildTools(version, toolsPath);
}
}).catch(function (err) { /* eslint handle-callback-err : 0 */
// console.log('no registry result for version ' + version);
// if 'reg' exits with error, assume that registry key not found
});
}
// build.js -> run()
module.exports.getLatestMatchingMSBuild = function (selectedBuildTargets) {
events.emit('verbose', 'getLatestMatchingMSBuild');
// console.log('getLatestMatchingMSBuild', selectedBuildTargets);
// TODO
// 1. findAllAvailableVersions
// 2. filter down to versions that can build all selectedBuildTargets
// 3. filter for latest one of those
return this.getLatestMSBuild()
.then(function (msbuild) {
// filter targets to make sure they are supported on this development machine
var myBuildTargets = filterSupportedTargets(selectedBuildTargets, msbuild);
return [msbuild, myBuildTargets];
});
};
// gets the latest MSBuild version from a list of versions
module.exports.getLatestMSBuild = function () {
events.emit('verbose', 'getLatestMSBuild');
return this.findAllAvailableVersions()
.then(function (allMsBuildVersions) {
var availableVersions = allMsBuildVersions
.filter(function (buildTools) {
// Sanitize input - filter out tools w/ invalid versions
return Version.tryParse(buildTools.version);
}).sort(function (a, b) {
// Sort tools list - use parsed Version objects for that
// to respect both major and minor versions segments
var parsedA = Version.fromString(a.version);
var parsedB = Version.fromString(b.version);
if (parsedA.gt(parsedB)) return -1;
if (parsedA.eq(parsedB)) return 0;
return 1;
});
// console.log('availableVersions', availableVersions);
if (availableVersions.length > 0) {
// After sorting the first item will be the highest version available
var msbuild = availableVersions[0];
events.emit('verbose', 'Using MSBuild v' + msbuild.version + ' from ' + msbuild.path);
return msbuild;
}
});
};
var projFiles = {
phone: 'CordovaApp.Phone.jsproj',
win: 'CordovaApp.Windows.jsproj',
win10: 'CordovaApp.Windows10.jsproj'
};
// TODO: Fix this so that it outlines supported versions based on version criteria:
// - v14: Windows 8.1, Windows 10
// - v12: Windows 8.1
function msBuild12TargetsFilter (target) {
return target === projFiles.win || target === projFiles.phone;
}
function msBuild14TargetsFilter (target) {
return target === projFiles.win || target === projFiles.phone || target === projFiles.win10;
}
function msBuild15TargetsFilter (target) {
return target === projFiles.win || target === projFiles.phone || target === projFiles.win10;
}
function msBuild155TargetsFilter (target) {
return target === projFiles.win10;
}
function filterSupportedTargets (targets, msbuild) {
// console.log('MSBuildTools->filterSupportedTargets', targets, msbuild);
if (!targets || targets.length === 0) {
events.emit('warn', 'No build targets specified');
return [];
}
var targetFilters = {
'12.0': msBuild12TargetsFilter,
'14.0': msBuild14TargetsFilter,
'15.x': msBuild15TargetsFilter,
'15.5': msBuild155TargetsFilter,
get: function (version) {
// Apart from exact match also try to get filter for version range
// so we can find for example targets for version '15.1'
return this[version] || this[version.replace(/\.\d+$/, '.x')];
}
};
var filter = targetFilters.get(msbuild.version);
if (!filter) {
events.emit('warn', 'MSBuild v' + msbuild.version + ' is not supported, aborting.');
return [];
}
var supportedTargets = targets.filter(filter);
// unsupported targets have been detected
if (supportedTargets.length !== targets.length) {
events.emit('warn', 'Not all desired build targets are compatible with the current build environment. ' +
'Please install Visual Studio 2015 for Windows 8.1 and Windows 10, ' +
'or Visual Studio 2013 Update 2 for Windows 8.1.');
}
return supportedTargets;
}
/**
* Lists all VS 2017+ instances dirs in ProgramData
*
* @return {String[]} List of paths to all VS2017+ instances
*/
function getWillowProgDataPaths () {
if (!process.env.systemdrive) {
// running on linux/osx?
return [];
}
var instancesRoot = path.join(process.env.systemdrive, 'ProgramData/Microsoft/VisualStudio/Packages/_Instances');
if (!shell.test('-d', instancesRoot)) {
// can't seem to find VS instances dir, return empty result
return [];
}
return fs.readdirSync(instancesRoot).map(function (file) {
var instanceDir = path.join(instancesRoot, file);
if (shell.test('-d', instanceDir)) {
return instanceDir;
}
}).filter(function (progDataPath) {
// make sure state.json exists
return shell.test('-e', path.join(progDataPath, 'state.json'));
});
}
/**
* Lists all installed VS 2017+ versions
*
* @return {Object[]} List of all VS 2017+ versions
*/
module.exports.getWillowInstallations = function () {
var progDataPaths = getWillowProgDataPaths();
var installations = [];
progDataPaths.forEach(function (progDataPath) {
try {
var stateJsonPath = path.join(progDataPath, 'state.json');
var fileContents = fs.readFileSync(stateJsonPath, 'utf-8');
var state = JSON.parse(fileContents);
// get only major and minor version
var version = state.product.version.match(/^(\d+\.\d+)/)[1];
installations.push({ version: version, path: state.installationPath });
} catch (err) {
// something's wrong, skip this one
}
});
return installations;
};
// returns an array of available UAP Versions
// prepare.js
module.exports.getAvailableUAPVersions = function () {
var programFilesFolder = process.env['ProgramFiles(x86)'] || process.env['ProgramFiles'];
// No Program Files folder found, so we won't be able to find UAP SDK
if (!programFilesFolder) return [];
var uapFolderPath = path.join(programFilesFolder, 'Windows Kits', '10', 'Platforms', 'UAP');
if (!shell.test('-e', uapFolderPath)) {
return []; // No UAP SDK exists on this machine
}
var result = [];
shell.ls(uapFolderPath).filter(function (uapDir) {
return shell.test('-d', path.join(uapFolderPath, uapDir));
}).map(function (folder) {
return Version.tryParse(folder);
}).forEach(function (version, index) {
if (version) {
result.push(version);
}
});
return result;
};