Detection of bundled CMake in Visual Studio (#942)

This commit is contained in:
Zingam 2020-01-24 21:03:56 +02:00 коммит произвёл Bob Brown
Родитель fa44e83328
Коммит eba7051022
3 изменённых файлов: 184 добавлений и 87 удалений

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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;
}
}

Просмотреть файл

@ -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();