| /* |
| 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) { |
| console.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) { |
| console.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; |
| }; |