Detection of bundled CMake in Visual Studio (#942)
This commit is contained in:
Родитель
fa44e83328
Коммит
eba7051022
|
@ -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<VSInstallation[]> {
|
||||
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;
|
||||
}
|
112
src/kit.ts
112
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<VSInstallation[]> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
70
src/paths.ts
70
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<string|null> {
|
||||
return new Promise<string|null>(resolve => {
|
||||
which(name, (err, resolved) => {
|
||||
|
@ -117,6 +129,8 @@ class Paths {
|
|||
}
|
||||
|
||||
async getCMakePath(wsc: DirectoryContext): Promise<string|null> {
|
||||
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<VSCMakePaths> {
|
||||
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();
|
||||
|
|
Загрузка…
Ссылка в новой задаче