diff --git a/src/installs/visual-studio.ts b/src/installs/visual-studio.ts new file mode 100644 index 00000000..1ad79e67 --- /dev/null +++ b/src/installs/visual-studio.ts @@ -0,0 +1,89 @@ +/** + * Module for querying MS Visual Studio + */ /** */ + +import * as path from 'path'; + +import * as logging from '../logging'; +import * as proc from '../proc'; +import {thisExtensionPath} from '../util'; +import * as nls from 'vscode-nls'; + + +nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); +const localize: nls.LocalizeFunc = nls.loadMessageBundle(); + +const log = logging.createLogger('visual-studio'); + + +export interface VSCatalog { + productDisplayVersion: string; +} + + /** + * Description of a Visual Studio installation returned by vswhere.exe + * + * This isn't _all_ the properties, just the ones we need so far. + */ +export interface VSInstallation { + catalog?: VSCatalog; + channelId?: string; + instanceId: string; + displayName?: string; + installationPath: string; + installationVersion: string; + description: string; + isPrerelease: boolean; +} + +/** + * Cache the results of invoking 'vswhere' + */ +interface VSInstallationCache { + installations: VSInstallation[]; + queryTime: number; +} + +let cachedVSInstallations: VSInstallationCache|null = null; + +/** + * Get a list of all Visual Studio installations available from vswhere.exe. + * Results are cached for 15 minutes. + * Will not include older versions. vswhere doesn't seem to list them? + */ +export async function vsInstallations(): Promise { + const now = Date.now(); + if (cachedVSInstallations && cachedVSInstallations.queryTime && (now - cachedVSInstallations.queryTime) < 900000) { + // If less than 15 minutes old, cache is considered ok. + return cachedVSInstallations.installations; + } + + const installs = [] as VSInstallation[]; + const inst_ids = [] as string[]; + const vswhere_exe = path.join(thisExtensionPath(), 'res', 'vswhere.exe'); + const sys32_path = path.join(process.env.WINDIR as string, 'System32'); + + const vswhere_args = + ['/c', `${sys32_path}\\chcp 65001>nul && "${vswhere_exe}" -all -format json -products * -legacy -prerelease`]; + const vswhere_res + = await proc.execute(`${sys32_path}\\cmd.exe`, vswhere_args, null, {silent: true, encoding: 'utf8', shell: true}) + .result; + + if (vswhere_res.retc !== 0) { + log.error(localize('failed.to.execute', 'Failed to execute {0}: {1}', "vswhere.exe", vswhere_res.stderr)); + return []; + } + + const vs_installs = JSON.parse(vswhere_res.stdout) as VSInstallation[]; + for (const inst of vs_installs) { + if (inst_ids.indexOf(inst.instanceId) < 0) { + installs.push(inst); + inst_ids.push(inst.instanceId); + } + } + cachedVSInstallations = { + installations: installs, + queryTime: now + }; + return installs; +} diff --git a/src/kit.ts b/src/kit.ts index fbe41024..d1ce356b 100644 --- a/src/kit.ts +++ b/src/kit.ts @@ -8,13 +8,14 @@ import * as json5 from 'json5'; import * as path from 'path'; import * as vscode from 'vscode'; +import {VSInstallation, vsInstallations} from './installs/visual-studio'; import * as expand from './expand'; import * as logging from './logging'; import paths from './paths'; import {fs} from './pr'; import * as proc from './proc'; import {loadSchema} from './schema'; -import {compare, dropNulls, objectPairs, Ordering, thisExtensionPath} from './util'; +import {compare, dropNulls, objectPairs, Ordering} from './util'; import * as nls from 'vscode-nls'; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); @@ -24,16 +25,6 @@ const log = logging.createLogger('kit'); type ProgressReporter = vscode.Progress<{message?: string}>; -/** - * Cache the results of invoking 'vswhere' - */ -interface VSInstallationCache { - installations: VSInstallation[]; - queryTime: number; -} - -let cachedVSInstallations: VSInstallationCache|null = null; - /** * The path to the user-local kits file. */ @@ -373,26 +364,6 @@ export async function scanDirForCompilerKits(dir: string, pr?: ProgressReporter) return kits; } -export interface VSCatalog { - productDisplayVersion: string; -} - -/** - * Description of a Visual Studio installation returned by vswhere.exe - * - * This isn't _all_ the properties, just the ones we need so far. - */ -export interface VSInstallation { - catalog?: VSCatalog; - channelId?: string; - instanceId: string; - displayName?: string; - installationPath: string; - installationVersion: string; - description: string; - isPrerelease: boolean; -} - /** * Construct the Kit.visualStudio property (legacy) * @@ -411,13 +382,26 @@ function kitVSName(inst: VSInstallation): string { return `${inst.instanceId}`; } +/** + * Construct the Visual Studio version string. + * + * @param inst The VSInstallation to use + */ +export function vsVersionName(inst: VSInstallation): string { + if (!inst.catalog) { + return inst.instanceId; + } + const end = inst.catalog.productDisplayVersion.indexOf('['); + return end < 0 ? inst.catalog.productDisplayVersion : inst.catalog.productDisplayVersion.substring(0, end - 1); +} + /** * Construct the display name (this will be paired with an * arch later to construct the Kit.name property). * * @param inst The VSInstallation to use */ -function vsDisplayName(inst: VSInstallation): string { +export function vsDisplayName(inst: VSInstallation): string { if (inst.displayName) { if (inst.channelId) { const index = inst.channelId.lastIndexOf('.'); @@ -430,14 +414,6 @@ function vsDisplayName(inst: VSInstallation): string { return inst.instanceId; } -function vsVersionName(inst: VSInstallation): string { - if (!inst.catalog) { - return inst.instanceId; - } - const end = inst.catalog.productDisplayVersion.indexOf('['); - return end < 0 ? inst.catalog.productDisplayVersion : inst.catalog.productDisplayVersion.substring(0, end - 1); -} - /** * Construct the Kit.name property. * @@ -448,48 +424,6 @@ function kitName(inst: VSInstallation, arch: string): string { return `${vsDisplayName(inst)} - ${arch}`; } -/** - * Get a list of all Visual Studio installations available from vswhere.exe - * - * Will not include older versions. vswhere doesn't seem to list them? - */ -export async function vsInstallations(): Promise { - const now = Date.now(); - if (cachedVSInstallations && cachedVSInstallations.queryTime && (now - cachedVSInstallations.queryTime) < 900000) { - // If less than 15 minutes old, cache is considered ok. - return cachedVSInstallations.installations; - } - - const installs = [] as VSInstallation[]; - const inst_ids = [] as string[]; - const vswhere_exe = path.join(thisExtensionPath(), 'res', 'vswhere.exe'); - const sys32_path = path.join(process.env.WINDIR as string, 'System32'); - - const vswhere_args = - ['/c', `${sys32_path}\\chcp 65001>nul && "${vswhere_exe}" -all -format json -products * -legacy -prerelease`]; - const vswhere_res - = await proc.execute(`${sys32_path}\\cmd.exe`, vswhere_args, null, {silent: true, encoding: 'utf8', shell: true}) - .result; - - if (vswhere_res.retc !== 0) { - log.error(localize('failed.to.execute', 'Failed to execute {0}: {1}', "vswhere.exe", vswhere_res.stderr)); - return []; - } - - const vs_installs = JSON.parse(vswhere_res.stdout) as VSInstallation[]; - for (const inst of vs_installs) { - if (inst_ids.indexOf(inst.instanceId) < 0) { - installs.push(inst); - inst_ids.push(inst.instanceId); - } - } - cachedVSInstallations = { - installations: installs, - queryTime: now - }; - return installs; -} - /** * List of environment variables required for Visual C++ to run as expected for * a VS installation. @@ -632,6 +566,20 @@ async function varsForVSInstallation(inst: VSInstallation, arch: string): Promis // configure. variables.set('CC', 'cl.exe'); variables.set('CXX', 'cl.exe'); + + if (null !== paths.ninjaPath) { + let envPATH = variables.get('PATH'); + if (undefined !== envPATH) { + const env_paths = envPATH.split(';'); + const ninja_path = path.dirname(paths.ninjaPath); + const ninja_base_path = env_paths.find(path_el => path_el === ninja_path); + if (undefined === ninja_base_path) { + envPATH = envPATH.concat(';' + ninja_path); + variables.set('PATH', envPATH); + } + } + } + return variables; } } diff --git a/src/paths.ts b/src/paths.ts index a952e56a..8d5b7e75 100644 --- a/src/paths.ts +++ b/src/paths.ts @@ -6,13 +6,21 @@ import {DirectoryContext} from '@cmt/workspace'; import * as path from 'path'; import * as which from 'which'; +import {vsInstallations} from './installs/visual-studio'; import {expandString} from './expand'; import {fs} from './pr'; +interface VSCMakePaths { + cmake: string | null; + ninja: string | null; +} + /** * Directory class. */ class Paths { + private _ninjaPath : string | null = null; + /** * The current user's home directory */ @@ -77,6 +85,10 @@ class Paths { } } + get ninjaPath() { + return this._ninjaPath; + } + async which(name: string): Promise { return new Promise(resolve => { which(name, (err, resolved) => { @@ -117,6 +129,8 @@ class Paths { } async getCMakePath(wsc: DirectoryContext): Promise { + this._ninjaPath = null; + const raw = await expandString(wsc.config.raw_cmakePath, { vars: { workspaceRoot: wsc.folder.uri.fsPath, @@ -128,28 +142,74 @@ class Paths { workspaceRootFolderName: path.basename(wsc.folder.uri.fsPath), }, }); + if (raw == 'auto' || raw == 'cmake') { // We start by searching $PATH for cmake const on_path = await this.which('cmake'); - if (!on_path) { + if (!on_path && (process.platform === 'win32')) { if (raw == 'auto' || raw == 'cmake') { // We didn't find it on the $PATH. Try some good guesses - const candidates = [ + const default_cmake_paths = [ `C:\\Program Files\\CMake\\bin\\cmake.exe`, `C:\\Program Files (x86)\\CMake\\bin\\cmake.exe`, ]; - for (const cand of candidates) { - if (await fs.exists(cand)) { - return cand; + for (const cmake_path of default_cmake_paths) { + if (await fs.exists(cmake_path)) { + return cmake_path; } } + + // Look for bundled CMake executables in Visual Studio install paths + const bundled_tools_paths = await this.vsCMakePaths(); + if (null !== bundled_tools_paths.cmake) { + this._ninjaPath = bundled_tools_paths.ninja; + + return bundled_tools_paths.cmake; + } } + return null; } + return on_path; } + return raw; } + + async vsCMakePaths(): Promise { + const vsCMakePaths = {} as VSCMakePaths; + + const vs_installations = await vsInstallations(); + if (vs_installations.length > 0) { + const bundled_tool_paths = [] as {cmake: string, ninja: string}[]; + + for (const install of vs_installations) { + const bundled_tool_path = { + cmake: install.installationPath + '\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe', + ninja: install.installationPath + '\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\Ninja\\ninja.exe' + }; + bundled_tool_paths.push(bundled_tool_path); + } + + for (const tool_path of bundled_tool_paths) { + if (await fs.exists(tool_path.cmake)) { + // CMake can be still used without Ninja + vsCMakePaths.cmake = tool_path.cmake; + + // Check for Ninja in case it was removed in later VS versions + if (await fs.exists(tool_path.ninja)) { + vsCMakePaths.ninja = tool_path.ninja; + + // Return the first CMake/Ninja set found + break; + } + } + } + } + + return vsCMakePaths; + } } const paths = new Paths();