Implement support for v4 presets files (#2544)

This commit is contained in:
chausner 2022-06-21 00:05:50 +02:00 коммит произвёл GitHub
Родитель 3ca837b3d9
Коммит 2877a9df57
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 1597 добавлений и 66 удалений

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

@ -2015,11 +2015,11 @@
},
{
"fileMatch": "CMakePresets.json",
"url": "cmake-tools-schema:///schemas/CMakePresets-v3-schema.json"
"url": "cmake-tools-schema:///schemas/CMakePresets-v4-schema.json"
},
{
"fileMatch": "CMakeUserPresets.json",
"url": "cmake-tools-schema:///schemas/CMakePresets-v3-schema.json"
"url": "cmake-tools-schema:///schemas/CMakePresets-v4-schema.json"
}
]
},
@ -2042,7 +2042,7 @@
"unitTests": "yarn run pretest && node ./out/test/unit-tests/runTest.js",
"extensionTestsSuccessfulBuild": "yarn run pretest && node ./out/test/extension-tests/successful-build/runTest.js",
"extensionTestsSingleRoot": "yarn run pretest && node ./out/test/extension-tests/single-root-UI/runTest.js",
"extensionTestsMultioot": "yarn run pretest && node ./out/test/extension-tests/multi-root-UI/runTest.js",
"extensionTestsMultiRoot": "yarn run pretest && node ./out/test/extension-tests/multi-root-UI/runTest.js",
"backendTests": "node ./node_modules/mocha/bin/_mocha -u tdd --timeout 999999 --colors -r ts-node/register -r tsconfig-paths/register ./test/backend-unit-tests/**/*.test.ts"
},
"husky": {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -91,4 +91,4 @@ Invoke-ChronicCommand "yarn smokeTests" $yarn run smokeTests
Invoke-ChronicCommand "yarn unitTests" $yarn run unitTests
Invoke-ChronicCommand "yarn extensionTestsSuccessfulBuild" $yarn run extensionTestsSuccessfulBuild
Invoke-ChronicCommand "yarn extensionTestsSingleRoot" $yarn run extensionTestsSingleRoot
Invoke-ChronicCommand "yarn extensionTestsMultioot" $yarn run extensionTestsMultioot
Invoke-ChronicCommand "yarn extensionTestsMultiRoot" $yarn run extensionTestsMultiRoot

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

@ -52,6 +52,7 @@ export interface PresetContextVars extends RequiredExpansionContextVars {
sourceParentDir: string;
sourceDirName: string;
presetName: string;
fileDir: string;
}
export interface MinimalPresetContextVars extends RequiredExpansionContextVars {

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

@ -703,7 +703,7 @@ class ExtensionManager implements vscode.Disposable {
log.debug(localize('update.intellisense.disabled', 'Not updating the configuration provider because {0} is set to {1}', '"C_Cpp.intelliSenseEngine"', '"Disabled"'));
return;
}
if (!this.cppToolsAPI) {
if (!this.cppToolsAPI && !util.isTestMode()) {
try {
this.cppToolsAPI = await cpt.getCppToolsApi(cpt.Version.latest);
} catch (err) {

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

@ -18,9 +18,12 @@ const log = logging.createLogger('preset');
export interface PresetsFile {
version: number;
cmakeMinimumRequired?: util.Version;
configurePresets?: ConfigurePreset[] | undefined;
buildPresets?: BuildPreset[] | undefined;
testPresets?: TestPreset[] | undefined;
include?: string[];
configurePresets?: ConfigurePreset[];
buildPresets?: BuildPreset[];
testPresets?: TestPreset[];
__path?: string; // Private field holding the path to the file.
}
export type VendorType = { [key: string]: any };
@ -38,6 +41,7 @@ export interface Preset {
__expanded?: boolean; // Private field to indicate if we have already expanded this preset.
__inheritedPresetCondition?: boolean; // Private field to indicate the fully evaluated inherited preset condition.
__file?: PresetsFile; // Private field to indicate the file where this preset was defined.
}
export interface ValueStrategy {
@ -560,7 +564,7 @@ function getVendorForConfigurePresetHelper(folder: string, preset: ConfigurePres
return preset.vendor || null;
}
async function getExpansionOptions(folder: string, workspaceFolder: string, sourceDir: string, preset: ConfigurePreset | BuildPreset | TestPreset) {
async function getExpansionOptions(workspaceFolder: string, sourceDir: string, preset: ConfigurePreset | BuildPreset | TestPreset) {
const generator = 'generator' in preset
? preset.generator
: ('__generator' in preset ? preset.__generator : undefined);
@ -587,11 +591,12 @@ async function getExpansionOptions(folder: string, workspaceFolder: string, sour
doNotSupportCommands: true
};
const presetsFile = getPresetsFile(folder);
const userPresetsFile = getUserPresetsFile(folder);
if (presetsFile && presetsFile.version >= 3 || userPresetsFile && userPresetsFile.version >= 3) {
if (preset.__file && preset.__file.version >= 3) {
expansionOpts.vars['hostSystemName'] = await util.getHostSystemNameMemo();
}
if (preset.__file && preset.__file.version >= 4) {
expansionOpts.vars['fileDir'] = path.dirname(preset.__file!.__path!);
}
return expansionOpts;
}
@ -643,19 +648,19 @@ async function expandCondition(condition: boolean | Condition | null | undefined
export async function expandConditionsForPresets(folder: string, sourceDir: string) {
for (const preset of configurePresets(folder)) {
const opts = await getExpansionOptions(folder, '${workspaceFolder}', sourceDir, preset);
const opts = await getExpansionOptions('${workspaceFolder}', sourceDir, preset);
if (preset.condition) {
preset.condition = await expandCondition(preset.condition, opts);
}
}
for (const preset of buildPresets(folder)) {
const opts = await getExpansionOptions(folder, '${workspaceFolder}', sourceDir, preset);
const opts = await getExpansionOptions('${workspaceFolder}', sourceDir, preset);
if (preset.condition) {
preset.condition = await expandCondition(preset.condition, opts);
}
}
for (const preset of testPresets(folder)) {
const opts = await getExpansionOptions(folder, '${workspaceFolder}', sourceDir, preset);
const opts = await getExpansionOptions('${workspaceFolder}', sourceDir, preset);
if (preset.condition) {
preset.condition = await expandCondition(preset.condition, opts);
}
@ -677,7 +682,7 @@ export async function expandConfigurePreset(folder: string, name: string, worksp
// Expand strings under the context of current preset
const expandedPreset: ConfigurePreset = { name };
const expansionOpts: ExpansionOptions = await getExpansionOptions(folder, workspaceFolder, sourceDir, preset);
const expansionOpts: ExpansionOptions = await getExpansionOptions(workspaceFolder, sourceDir, preset);
// Expand environment vars first since other fields may refer to them
if (preset.environment) {
@ -691,8 +696,7 @@ export async function expandConfigurePreset(folder: string, name: string, worksp
expansionOpts.envOverride = expandedPreset.environment;
const presetsFile = getPresetsFile(folder);
if (presetsFile && presetsFile.version >= 3) {
if (preset.__file && preset.__file.version >= 3) {
// For presets v3+ binaryDir and generator are optional, but cmake-tools needs a value. Default to something reasonable.
if (!preset.binaryDir) {
const defaultValue = '${sourceDir}/out/build/${presetName}';
@ -1179,7 +1183,7 @@ export async function expandBuildPreset(folder: string, name: string, workspaceF
// Expand strings under the context of current preset
const expandedPreset: BuildPreset = { name };
const expansionOpts: ExpansionOptions = await getExpansionOptions(folder, workspaceFolder, sourceDir, preset);
const expansionOpts: ExpansionOptions = await getExpansionOptions(workspaceFolder, sourceDir, preset);
// Expand environment vars first since other fields may refer to them
if (preset.environment) {
@ -1328,7 +1332,7 @@ export async function expandTestPreset(folder: string, name: string, workspaceFo
}
const expandedPreset: TestPreset = { name };
const expansionOpts: ExpansionOptions = await getExpansionOptions(folder, workspaceFolder, sourceDir, preset);
const expansionOpts: ExpansionOptions = await getExpansionOptions(workspaceFolder, sourceDir, preset);
// Expand environment vars first since other fields may refer to them
if (preset.environment) {

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

@ -26,12 +26,12 @@ type SetPresetsFileFunc = (folder: string, presets: preset.PresetsFile | undefin
export class PresetsController {
private _presetsWatcher: chokidar.FSWatcher | undefined;
private _userPresetsWatcher: chokidar.FSWatcher | undefined;
private _sourceDir: string = '';
private _sourceDirChangedSub: vscode.Disposable | undefined;
private _presetsFileExists = false;
private _userPresetsFileExists = false;
private _isChangingPresets = false;
private _referencedFiles: string[] = [];
private readonly _presetsChangedEmitter = new vscode.EventEmitter<preset.PresetsFile | undefined>();
private readonly _userPresetsChangedEmitter = new vscode.EventEmitter<preset.PresetsFile | undefined>();
@ -64,41 +64,11 @@ export class PresetsController {
presetsController._sourceDir = await expandSourceDir(cmakeTools.workspaceContext.config.sourceDirectory);
const watchPresetsChange = async () => {
// We explicitly read presets file here, instead of on the initialization of the file watcher. Otherwise
// there might be timing issues, since listeners are invoked async.
await presetsController.reapplyPresets();
// We explicitly read presets file here, instead of on the initialization of the file watcher. Otherwise
// there might be timing issues, since listeners are invoked async.
await presetsController.reapplyPresets();
if (presetsController._presetsWatcher) {
presetsController._presetsWatcher.close().then(() => {}, () => {});
}
if (presetsController._userPresetsWatcher) {
presetsController._userPresetsWatcher.close().then(() => {}, () => {});
}
presetsController._presetsWatcher = chokidar.watch(presetsController.presetsPath, { ignoreInitial: true })
.on('add', async () => {
await presetsController.reapplyPresets();
})
.on('change', async () => {
await presetsController.reapplyPresets();
})
.on('unlink', async () => {
await presetsController.reapplyPresets();
});
presetsController._userPresetsWatcher = chokidar.watch(presetsController.userPresetsPath, { ignoreInitial: true })
.on('add', async () => {
await presetsController.reapplyPresets();
})
.on('change', async () => {
await presetsController.reapplyPresets();
})
.on('unlink', async () => {
await presetsController.reapplyPresets();
});
};
await watchPresetsChange();
await presetsController.watchPresetsChange();
cmakeTools.workspaceContext.config.onChange('allowCommentsInPresetsFile', async () => {
await presetsController.reapplyPresets();
@ -119,7 +89,8 @@ export class PresetsController {
presetsController._sourceDir = await expandSourceDir(value);
if (presetsController._sourceDir !== oldSourceDir) {
await watchPresetsChange();
await presetsController.reapplyPresets();
await presetsController.watchPresetsChange();
}
});
@ -180,12 +151,15 @@ export class PresetsController {
preset.setOriginalUserPresetsFile(folder, presetsFile);
};
private async resetPresetsFile(file: string, setPresetsFile: SetPresetsFileFunc, setOriginalPresetsFile: SetPresetsFileFunc, fileExistCallback: (fileExists: boolean) => void) {
private async resetPresetsFile(file: string, setPresetsFile: SetPresetsFileFunc, setOriginalPresetsFile: SetPresetsFileFunc, fileExistCallback: (fileExists: boolean) => void, referencedFiles: Set<string>) {
const presetsFileBuffer = await this.readPresetsFile(file);
// There might be a better location for this, but for now this is the best one...
fileExistCallback(Boolean(presetsFileBuffer));
// Record the file as referenced, even if the file does not exist.
referencedFiles.add(file);
let presetsFile = await this.parsePresetsFile(presetsFileBuffer, file);
if (presetsFile) {
// Parse again so we automatically have a copy by value
@ -194,15 +168,22 @@ export class PresetsController {
setOriginalPresetsFile(this.folder.uri.fsPath, undefined);
}
presetsFile = await this.validatePresetsFile(presetsFile, file);
// Private fields must be set after validation, otherwise validation would fail.
this.populatePrivatePresetsFields(presetsFile, file);
await this.mergeIncludeFiles(presetsFile, presetsFile, file, referencedFiles);
setPresetsFile(this.folder.uri.fsPath, presetsFile);
}
// Need to reapply presets every time presets changed since the binary dir or cmake path could change
// (need to clean or reload driver)
async reapplyPresets() {
const referencedFiles: Set<string> = new Set();
// Reset all changes due to expansion since parents could change
await this.resetPresetsFile(this.presetsPath, this._setPresetsFile, this._setOriginalPresetsFile, exists => this._presetsFileExists = exists);
await this.resetPresetsFile(this.userPresetsPath, this._setUserPresetsFile, this._setOriginalUserPresetsFile, exists => this._userPresetsFileExists = exists);
await this.resetPresetsFile(this.presetsPath, this._setPresetsFile, this._setOriginalPresetsFile, exists => this._presetsFileExists = exists, referencedFiles);
await this.resetPresetsFile(this.userPresetsPath, this._setUserPresetsFile, this._setOriginalUserPresetsFile, exists => this._userPresetsFileExists = exists, referencedFiles);
this._referencedFiles = Array.from(referencedFiles);
this._cmakeTools.minCMakeVersion = preset.minCMakeVersion(this.folderFsPath);
@ -955,17 +936,89 @@ export class PresetsController {
return presetsFile;
}
private populatePrivatePresetsFields(presetsFile: preset.PresetsFile | undefined, file: string) {
if (!presetsFile) {
return;
}
presetsFile.__path = file;
const setFile = (presets?: preset.Preset[]) => {
if (presets) {
for (const preset of presets) {
preset.__file = presetsFile;
}
}
};
setFile(presetsFile.configurePresets);
setFile(presetsFile.buildPresets);
setFile(presetsFile.testPresets);
}
private async mergeIncludeFiles(rootPresetsFile: preset.PresetsFile | undefined, presetsFile: preset.PresetsFile | undefined, file: string, referencedFiles: Set<string>): Promise<void> {
if (!rootPresetsFile || !presetsFile || !presetsFile.include) {
return;
}
// Merge the includes in reverse order so that the final presets order is correct
for (let i = presetsFile.include.length - 1; i >= 0; i--) {
const fullIncludePath = path.normalize(path.resolve(path.dirname(file), presetsFile.include[i]));
// Do not include files more than once
if (referencedFiles.has(fullIncludePath)) {
continue;
}
// Record the file as referenced, even if the file does not exist.
referencedFiles.add(fullIncludePath);
const includeFileBuffer = await this.readPresetsFile(fullIncludePath);
if (!includeFileBuffer) {
log.error(localize('included.presets.file.not.found', 'Included presets file {0} cannot be found', fullIncludePath));
continue;
}
let includeFile = await this.parsePresetsFile(includeFileBuffer, fullIncludePath);
includeFile = await this.validatePresetsFile(includeFile, fullIncludePath);
if (!includeFile) {
continue;
}
// Private fields must be set after validation, otherwise validation would fail.
this.populatePrivatePresetsFields(includeFile, fullIncludePath);
if (includeFile.cmakeMinimumRequired) {
if (!rootPresetsFile.cmakeMinimumRequired || util.versionLess(rootPresetsFile.cmakeMinimumRequired, includeFile.cmakeMinimumRequired)) {
rootPresetsFile.cmakeMinimumRequired = includeFile.cmakeMinimumRequired;
}
}
if (includeFile.configurePresets) {
rootPresetsFile.configurePresets = includeFile.configurePresets.concat(rootPresetsFile.configurePresets || []);
}
if (includeFile.buildPresets) {
rootPresetsFile.buildPresets = includeFile.buildPresets.concat(rootPresetsFile.buildPresets || []);
}
if (includeFile.testPresets) {
rootPresetsFile.testPresets = includeFile.testPresets.concat(rootPresetsFile.testPresets || []);
}
// Recursively merge included files
await this.mergeIncludeFiles(rootPresetsFile, includeFile, fullIncludePath, referencedFiles);
}
}
private async validatePresetsFile(presetsFile: preset.PresetsFile | undefined, file: string) {
if (!presetsFile) {
return undefined;
}
let schemaFile;
if (presetsFile.version < 2) {
await this.showPresetsFileVersionError(file);
return undefined;
}
let schemaFile = 'schemas/CMakePresets-schema.json';
if (presetsFile.version >= 3) {
} else if (presetsFile.version === 2) {
schemaFile = 'schemas/CMakePresets-schema.json';
} else if (presetsFile.version === 3) {
schemaFile = 'schemas/CMakePresets-v3-schema.json';
} else {
schemaFile = 'schemas/CMakePresets-v4-schema.json';
}
const validator = await loadSchema(schemaFile);
const is_valid = validator(presetsFile);
@ -1028,12 +1081,20 @@ export class PresetsController {
return vscode.window.showTextDocument(vscode.Uri.file(presetsFilePath));
}
dispose() {
private async watchPresetsChange() {
if (this._presetsWatcher) {
this._presetsWatcher.close().then(() => {}, () => {});
}
if (this._userPresetsWatcher) {
this._userPresetsWatcher.close().then(() => {}, () => {});
this._presetsWatcher = chokidar.watch(this._referencedFiles, { ignoreInitial: true })
.on('add', this.reapplyPresets)
.on('change', this.reapplyPresets)
.on('unlink', this.reapplyPresets);
};
dispose() {
if (this._presetsWatcher) {
this._presetsWatcher.close().then(() => {}, () => {});
}
if (this._sourceDirChangedSub) {
this._sourceDirChangedSub.dispose();

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

@ -22,5 +22,8 @@
},
"include": [
"test/**/*.ts"
],
"exclude": [
"test/**/CMakeFiles/**/*.ts"
]
}

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

@ -1,5 +1,8 @@
{
"version": 2,
"version": 4,
"include": [
"include/IncludedPresets1.json"
],
"configurePresets": [
{
"name": "LinuxUser1",

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

@ -0,0 +1,19 @@
{
"version": 4,
"include": [
"IncludedPresets2.json"
],
"configurePresets": [
{
"name": "LinuxIncluded1",
"description": "Sets generator, build and install directory, vcpkg",
"generator": "Unix Makefiles",
"binaryDir": "${workspaceFolder}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
}
]
}

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

@ -0,0 +1,16 @@
{
"version": 4,
"configurePresets": [
{
"name": "LinuxIncluded2",
"description": "Sets generator, build and install directory, vcpkg",
"generator": "Unix Makefiles",
"binaryDir": "${workspaceFolder}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
}
]
}

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

@ -0,0 +1,66 @@
import { fs } from '@cmt/pr';
import {
clearExistingKitConfigurationFile,
DefaultEnvironment,
expect
} from '@test/util';
import * as path from 'path';
import * as vscode from 'vscode';
suite('Preset include functionality', async () => {
let testEnv: DefaultEnvironment;
let compdb_cp_path: string;
suiteSetup(async function (this: Mocha.Context) {
this.timeout(100000);
const build_loc = 'build';
const exe_res = 'output.txt';
// CMakePresets.json and CMakeUserPresets.json exist so will use presets by default
testEnv = new DefaultEnvironment('test/extension-tests/single-root-UI/project-folder', build_loc, exe_res);
compdb_cp_path = path.join(testEnv.projectFolder.location, 'compdb_cp.json');
await vscode.workspace.getConfiguration('cmake', vscode.workspace.workspaceFolders![0].uri).update('useCMakePresets', 'always');
await clearExistingKitConfigurationFile();
});
setup(async function (this: Mocha.Context) {
this.timeout(100000);
await vscode.commands.executeCommand('cmake.setConfigurePreset', 'Linux1');
await vscode.commands.executeCommand('cmake.setBuildPreset', '__defaultBuildPreset__');
await vscode.commands.executeCommand('cmake.setTestPreset', '__defaultTestPreset__');
testEnv.projectFolder.buildDirectory.clear();
});
teardown(async function (this: Mocha.Context) {
});
suiteTeardown(async () => {
await vscode.workspace.getConfiguration('cmake', vscode.workspace.workspaceFolders![0].uri).update('useCMakePresets', 'auto');
if (testEnv) {
testEnv.teardown();
}
if (await fs.exists(compdb_cp_path)) {
await fs.unlink(compdb_cp_path);
}
});
test('Configure and build included preset 1', async function (this: Mocha.Context) {
await vscode.commands.executeCommand('cmake.setConfigurePreset', 'LinuxIncluded1');
await vscode.commands.executeCommand('cmake.build');
const result = await testEnv.result.getResultAsJson();
expect(result['cookie']).to.eq('passed-cookie');
}).timeout(100000);
test('Configure and build included preset 2', async function (this: Mocha.Context) {
await vscode.commands.executeCommand('cmake.setConfigurePreset', 'LinuxIncluded2');
await vscode.commands.executeCommand('cmake.build');
const result = await testEnv.result.getResultAsJson();
expect(result['cookie']).to.eq('passed-cookie');
}).timeout(100000);
});