Add dryrun cache support. Add more watchers. Other fixes and improvements.

This commit is contained in:
Andreea Isac 2020-07-16 10:01:09 -07:00
Родитель ac5434a411
Коммит caad7117d5
7 изменённых файлов: 147 добавлений и 65 удалений

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

@ -234,11 +234,19 @@
"makefile.buildLog": {
"type": "string",
"description": "The path to the build log that is read to bypass a dry-run",
"default": "./build.log",
"scope": "resource"
},
"makefile.extensionLog": {
"type": "string",
"description": "The path to an output file storing all content from the Makefile output channel",
"default": "./extension.log",
"scope": "resource"
},
"makefile.dryrunCache": {
"type": "string",
"description": "The path to a cache file storing the output of the last dry-run make command",
"default": "./dryrunCache.log",
"scope": "resource"
},
"makefile.dryRunSwitches": {

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

@ -2,6 +2,7 @@
import * as child_process from 'child_process';
import * as extension from './extension';
import * as fs from 'fs';
import * as logger from './logger';
import * as make from './make';
import * as parser from './parser';
@ -132,18 +133,11 @@ export function setBuildLog(path: string): void { buildLog = path; }
// identifying less possible binaries to debug or not providing any makefile targets (other than the 'all' default).
function readBuildLog(): void {
let workspaceConfiguration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("makefile");
buildLog = workspaceConfiguration.get<string>("buildLog");
if (buildLog) {
logger.message('Found build log path setting "' + buildLog + '"');
if (!path.isAbsolute(buildLog)) {
buildLog = path.join(vscode.workspace.rootPath || "", buildLog);
logger.message('Resolving build log path to "' + buildLog + '"');
}
if (!util.checkFileExistsSync(buildLog)) {
logger.message("Build log not found. Remove the build log setting or provide a build log file on disk at the given location.");
}
// how to get default from package.json to avoid problem with 'undefined' type?
buildLog = util.resolvePathToRoot(workspaceConfiguration.get<string>("buildLog", "./build.log"));
logger.message('Build log defined at "' + buildLog + '"');
if (!util.checkFileExistsSync(buildLog)) {
logger.message("Build log not found on disk.");
}
}
@ -161,8 +155,8 @@ export function readLoggingLevel(): void {
}
}
let extensionLog: string | undefined;
export function getExtensionLog(): string | undefined { return extensionLog; }
let extensionLog: string;
export function getExtensionLog(): string { return extensionLog; }
export function setExtensionLog(path: string): void { extensionLog = path; }
// Read from settings the path to a log file capturing all the "Makefile Tools" output channel content.
@ -175,15 +169,23 @@ export function setExtensionLog(path: string): void { extensionLog = path; }
// are going to be appended to this file.
export function readExtensionLog(): void {
let workspaceConfiguration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("makefile");
extensionLog = workspaceConfiguration.get<string>("extensionLog");
// how to get default from package.json to avoid problem with 'undefined' type?
extensionLog = util.resolvePathToRoot(workspaceConfiguration.get<string>("extensionLog", "./dryrun.log"));
logger.message('Writing extension log at {0}', extensionLog);
}
if (extensionLog) {
logger.message('Found extension log path setting "' + extensionLog + '"');
if (!path.isAbsolute(extensionLog)) {
extensionLog = path.join(vscode.workspace.rootPath || "", extensionLog);
logger.message('Resolving extension log path to "' + extensionLog + '"');
}
}
let dryrunCache: string;
export function getDryrunCache(): string { return dryrunCache; }
export function setDryrunCache(path: string): void { dryrunCache = path; }
// Read from settings the path to a cache file containing the output of the last dry-run make command.
// This file is recreated when opening a project, when changing the build configuration or the build target
// and when the settings watcher detects a change of any properties that may impact the dryrun output.
export function readDryrunCache(): void {
let workspaceConfiguration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("makefile");
// how to get default from package.json to avoid problem with 'undefined' type?
dryrunCache = util.resolvePathToRoot(workspaceConfiguration.get<string>("dryrunCache", "./dryrunCache.log"));
logger.message("Dry-run output cached at {0}", dryrunCache);
}
let dryRunSwitches: string[] | undefined;
@ -471,10 +473,17 @@ export function startListeningToSettingsChanged(): void {
export function stopListeningToSettingsChanged(): void {
ignoreSettingsChanged = true;
}
// Triggers IntelliSense config provider updates after relevant changes
// are made in settings or in the makefiles.
// To avoid unnecessary dry-runs, these updates are not performed with every document save
// but after leaving the focus of the document.
let configProviderDirty: boolean = false;
// Initialization from settings (or backup default rules), done at activation time
export function initFromStateAndSettings(): void {
readLoggingLevel();
readExtensionLog();
readDryrunCache();
readMakePath();
readMakefilePath();
readBuildLog();
@ -485,6 +494,32 @@ export function initFromStateAndSettings(): void {
readCurrentLaunchConfiguration();
readDebugConfig();
// Verify the dirty state of the IntelliSense config provider and update accordingly.
// The makefile.configureOnEdit setting can be set to false when this behavior is inconvenient.
vscode.window.onDidChangeActiveTextEditor(e => {
if (configProviderDirty) {
logger.message("Updating the Intellisense config provider...");
getCommandForConfiguration(currentMakefileConfiguration);
getBuildLogForConfiguration(currentMakefileConfiguration);
make.parseBuildOrDryRun();
configProviderDirty = false;
}
});
// Modifying any makefile should trigger an IntelliSense config provider update,
// so make the dirty state true.
// TODO: limit to makefiles relevant to this project, instead of any random makefile anywhere.
// We can't listen only to the makefile pointed to by makefile.makefilePath,
// because that is only the entry point and can refer to other relevant makefiles.
// TODO: don't trigger an update for any dummy save, verify how the content changed.
vscode.workspace.onDidSaveTextDocument(e => {
if (e.uri.fsPath.toLowerCase().endsWith("makefile")) {
configProviderDirty = true;
}
});
// Watch for Makefile Tools setting updates that can change the IntelliSense config provider dirty state
vscode.workspace.onDidChangeConfiguration(e => {
if (vscode.workspace.workspaceFolders && !ignoreSettingsChanged &&
e.affectsConfiguration('makefile', vscode.workspace.workspaceFolders[0].uri)) {
@ -492,7 +527,6 @@ export function initFromStateAndSettings(): void {
// A subset of these should also trigger an IntelliSense config provider update.
// Avoid unnecessary updates (for example, when settings are modified via the extension quickPick).
let workspaceConfiguration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("makefile");
let updateConfigProvider: boolean = false; // to trigger IntelliSense config provider refresh
let updatedLaunchConfigurations : LaunchConfiguration[] | undefined = workspaceConfiguration.get<LaunchConfiguration[]>("launchConfigurations");
if (!util.areEqual(updatedLaunchConfigurations, launchConfigurations)) {
@ -510,34 +544,41 @@ export function initFromStateAndSettings(): void {
readDebugConfig();
}
let updatedBuildLog : string | undefined = workspaceConfiguration.get<string>("buildLog");
if (updatedBuildLog !== buildLog) {
updateConfigProvider = true;
let updatedBuildLog : string | undefined = workspaceConfiguration.get<string>("buildLog", "./build.log");
if (util.resolvePathToRoot(updatedBuildLog) !== buildLog) {
configProviderDirty = true;
logger.message("makefile.buildLog setting changed.");
readBuildLog();
}
let updatedExtensionLog : string | undefined = workspaceConfiguration.get<string>("extensionLog");
if (updatedExtensionLog !== extensionLog) {
let updatedExtensionLog : string | undefined = workspaceConfiguration.get<string>("extensionLog", "./extension.log");
if (util.resolvePathToRoot(updatedExtensionLog) !== extensionLog) {
// No IntelliSense update needed.
logger.message("makefile.extensionLog setting changed.");
readExtensionLog();
}
let updatedDryrunCache : string | undefined = workspaceConfiguration.get<string>("dryrunCache", "./dryrunCache.log");
if (util.resolvePathToRoot(updatedDryrunCache) !== dryrunCache) {
configProviderDirty = true;
logger.message("makefile.dryrunCache setting changed.");
readDryrunCache();
}
let updatedMakePath : string | undefined = workspaceConfiguration.get<string>("makePath");
if (updatedMakePath !== makePath) {
// Not very likely, but it is safe to consider that a different make tool
// may produce a different dry-run output with potential impact on IntelliSense,
// so trigger an update.
logger.message("makefile.makePath setting changed.");
updateConfigProvider = true;
configProviderDirty = true;
readMakePath();
}
let updatedMakefilePath : string | undefined = workspaceConfiguration.get<string>("makefilePath");
if (updatedMakefilePath !== makefilePath) {
logger.message("makefile.makefilePath setting changed.");
updateConfigProvider = true;
configProviderDirty = true;
readMakefilePath();
}
@ -546,7 +587,7 @@ export function initFromStateAndSettings(): void {
// todo: skip over updating the IntelliSense configuration provider if the current makefile configuration
// is not among the subobjects that suffered modifications.
logger.message("makefile.configurations setting changed.");
updateConfigProvider = true;
configProviderDirty = true;
readMakefileConfigurations();
}
@ -554,20 +595,10 @@ export function initFromStateAndSettings(): void {
if (!util.areEqual(updatedDryRunSwitches, dryRunSwitches)) {
// A change in makefile.dryRunSwitches should trigger an IntelliSense update
// only if the extension is not currently reading from a build log.
updateConfigProvider = !buildLog || !util.checkFileExistsSync(buildLog);
configProviderDirty = !buildLog || !util.checkFileExistsSync(buildLog);
logger.message("makefile.dryRunSwitches setting changed.");
readDryRunSwitches();
}
if (updateConfigProvider) {
// The source for the parsing process can either be a build log or the dry-run output of make tool,
// but there are some rules of defaults and/or overrides that may be impacted by any of the above settings,
// so recalculate.
logger.message("Some of the changes detected in settings are triggering udpates");
getCommandForConfiguration(currentMakefileConfiguration);
getBuildLogForConfiguration(currentMakefileConfiguration);
make.parseBuildOrDryRun();
}
}
});
}
@ -631,10 +662,18 @@ export function parseLaunchConfigurations(source: string): string[] {
}
export function parseLaunchConfigurationsFromBuildLog(): string[] | undefined {
let buildLogContent: string | undefined = configurationBuildLog ? util.readFile(configurationBuildLog) : undefined;
if (buildLogContent) {
logger.message('Parsing the provided build log "' + configurationBuildLog + '" for launch configurations...');
return parseLaunchConfigurations(buildLogContent);
// A build log has priority over a dry-run cache.
let content: string | undefined = configurationBuildLog ? util.readFile(configurationBuildLog) : undefined;
let file: string | undefined = configurationBuildLog;
if (!content) {
content = dryrunCache ? util.readFile(dryrunCache) : undefined;
file = dryrunCache;
}
if (content) {
logger.message(`Parsing launch configurations from: ${file}`);
return parseLaunchConfigurations(content);
}
return undefined;
@ -674,12 +713,14 @@ export async function setNewLaunchConfiguration(): Promise<void> {
let closing : any = (retCode: number, signal: string): void => {
if (retCode !== 0) {
logger.message("The verbose make dry-run command for parsing binaries launch configuration failed.");
logger.message("The make dry-run command failed. Launch configurations may be missing from the Quick Pick selection.");
logger.message(stderrStr);
util.reportDryRunError();
}
//logger.message("The dry-run output for parsing the binaries launch configuration");
//logger.message(stdoutStr);
fs.writeFileSync(dryrunCache, stdoutStr);
let launchConfigurationNames: string[] = parseLaunchConfigurations(stdoutStr);
selectLaunchConfiguration(launchConfigurationNames);
};
@ -691,11 +732,19 @@ export async function setNewLaunchConfiguration(): Promise<void> {
}
}
export function parseTargetsFromBuildLog(): string[] | undefined {
let buildLogContent: string | undefined = configurationBuildLog ? util.readFile(configurationBuildLog) : undefined;
if (buildLogContent) {
logger.message('Parsing the provided build log "' + configurationBuildLog + '" for targets...');
let makefileTargets: string[] = parser.parseTargets(buildLogContent);
export function parseTargetsFromBuildLogOrCache(): string[] | undefined {
// A build log has priority over a dry-run cache.
let content: string | undefined = configurationBuildLog ? util.readFile(configurationBuildLog) : undefined;
let file: string | undefined = configurationBuildLog;
if (!content) {
content = dryrunCache ? util.readFile(dryrunCache) : undefined;
file = dryrunCache;
}
if (content) {
logger.message(`Parsing targets from: ${file}`);
let makefileTargets: string[] = parser.parseTargets(content);
makefileTargets = makefileTargets.sort();
return makefileTargets;
}
@ -707,7 +756,8 @@ export function parseTargetsFromBuildLog(): string[] | undefined {
export async function setNewTarget(): Promise<void> {
// If a build log is specified in makefile.configurations.buildLog or makefile.buildLog settings,
// (and if it exists on disk) it must be parsed instead of invoking a dry-run make command.
let makefileTargets: string[] | undefined = parseTargetsFromBuildLog();
// Also, an existing dry-run cache should avoid calling make for this (finding all the existing targets).
let makefileTargets: string[] | undefined = parseTargetsFromBuildLogOrCache();
if (makefileTargets) {
selectTarget(makefileTargets);
return;
@ -743,11 +793,13 @@ export async function setNewTarget(): Promise<void> {
let closing : any = (retCode: number, signal: string): void => {
if (retCode !== 0) {
logger.message("The verbose make dry-run command for parsing targets failed.");
logger.message("The make dry-run command failed. Makefile build targets may be missing from the Quick Pick selection.");
logger.message(stderrStr);
util.reportDryRunError();
}
// Don't log stdoutStr in this case, because -p output is too verbose to be useful in any logger area
fs.writeFileSync(dryrunCache, stdoutStr);
makefileTargets = parser.parseTargets(stdoutStr);
makefileTargets = makefileTargets.sort();
selectTarget(makefileTargets);
@ -809,7 +861,7 @@ export function setLaunchConfigurationByName (launchConfigurationName: string) :
extension.extension.extensionContext.workspaceState.update("launchConfiguration", launchConfigurationName);
statusBar.setLaunchConfiguration(launchConfigurationName);
} else {
logger.message("A problem occured while analyzing launch configuration name {0}. Current launch configuration is unset.", launchConfigurationName);
logger.message(`A problem occured while analyzing launch configuration name ${launchConfigurationName}. Current launch configuration is unset.`);
extension.extension.extensionContext.workspaceState.update("launchConfiguration", undefined);
statusBar.setLaunchConfiguration("No launch configuration set");
}

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

@ -162,7 +162,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
// Delete the extension log file, if exists
let extensionLog : string | undefined = configuration.getExtensionLog();
if (extensionLog) {
if (extensionLog && util.checkFileExistsSync(extensionLog)) {
fs.unlinkSync(extensionLog);
}

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

@ -9,6 +9,7 @@ let makeOutputChannel: vscode.OutputChannel | undefined;
function getOutputChannel(): vscode.OutputChannel {
if (!makeOutputChannel) {
makeOutputChannel = vscode.window.createOutputChannel("Makefile tools");
makeOutputChannel.show();
}
return makeOutputChannel;
@ -23,7 +24,6 @@ export function message(message: string, loggingLevel?: string): void {
}
let channel: vscode.OutputChannel = getOutputChannel();
channel.show();
channel.appendLine(message);
let extensionLog : string | undefined = configuration.getExtensionLog();
@ -43,7 +43,6 @@ export function messageNoCR(message: string, loggingLevel?: string): void {
}
let channel: vscode.OutputChannel = getOutputChannel();
channel.show();
channel.append(message);
let extensionLog : string | undefined = configuration.getExtensionLog();

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

@ -2,6 +2,7 @@
import * as configuration from './configuration';
import * as ext from './extension';
import * as fs from 'fs';
import * as logger from './logger';
import * as util from './util';
import * as vscode from 'vscode';
@ -100,12 +101,14 @@ export async function parseBuildOrDryRun(): Promise<void> {
};
let closing : any = (retCode: number, signal: string): void => {
let dryrunCache: string = configuration.getDryrunCache();
if (retCode !== 0) {
logger.message("The make dry-run command failed.");
logger.message("The make dry-run command failed. IntelliSense may work only partially or not at all.");
logger.message(stderrStr);
util.reportDryRunError();
}
console.log("Make dry-run output to parse is:\n" + stdoutStr);
fs.writeFileSync(dryrunCache, stdoutStr);
ext.updateProvider(stdoutStr);
};

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

@ -65,7 +65,7 @@ suite('Fake dryrun parsing', /*async*/() => {
/*await*/ configuration.prepareConfigurationsQuickPick();
/*await*/ configuration.setConfigurationByName("InterestingSmallMakefile_windows_configDebug");
/*await*/ configuration.parseTargetsFromBuildLog();
/*await*/ configuration.parseTargetsFromBuildLogOrCache();
/*await*/ configuration.setTargetByName("execute_Arch3");
make.prepareBuildCurrentTarget();
@ -124,7 +124,7 @@ suite('Fake dryrun parsing', /*async*/() => {
/*await*/ configuration.prepareConfigurationsQuickPick();
/*await*/ configuration.setConfigurationByName(process.platform === "linux" ? "8cc_linux" : "8cc_mingw");
/*await*/ configuration.parseTargetsFromBuildLog();
/*await*/ configuration.parseTargetsFromBuildLogOrCache();
/*await*/ configuration.setTargetByName("all");
make.prepareBuildCurrentTarget();
@ -186,7 +186,7 @@ suite('Fake dryrun parsing', /*async*/() => {
///*await*/ configuration.prepareConfigurationsQuickPick();
/*await*/ configuration.setConfigurationByName(process.platform === "linux" ? "Fido_linux" : "Fido_mingw");
/*await*/ configuration.parseTargetsFromBuildLog();
/*await*/ configuration.parseTargetsFromBuildLogOrCache();
/*await*/ configuration.setTargetByName("bin/foo.o");
make.prepareBuildCurrentTarget();
@ -248,7 +248,7 @@ suite('Fake dryrun parsing', /*async*/() => {
// /*await*/ configuration.prepareConfigurationsQuickPick();
/*await*/ configuration.setConfigurationByName(process.platform === "linux" ? "tinyvm_linux_pedantic" : "tinyvm_mingw_pedantic");
/*await*/ configuration.parseTargetsFromBuildLog();
/*await*/ configuration.parseTargetsFromBuildLogOrCache();
/*await*/ configuration.setTargetByName("tvmi");
make.prepareBuildCurrentTarget();

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

@ -2,7 +2,10 @@
import * as fs from 'fs';
import * as child_process from 'child_process';
import * as configuration from './configuration';
import * as logger from './logger';
import * as path from 'path';
import * as vscode from 'vscode';
// TODO: c++20, c++latest
export type StandardVersion = 'c89' | 'c99' | 'c11' | 'c++98' | 'c++03' | 'c++11' | 'c++14' | 'c++17' | undefined;
@ -247,3 +250,20 @@ export function areEqual(setting1: any, setting2: any): boolean {
return true;
}
export function reportDryRunError(): void {
logger.message(`You can see the detailed dry-run output at ${configuration.getDryrunCache()}`);
logger.message("Make sure that the extension is invoking the same make command as in your development prompt environment.");
logger.message("You may need to define or tweak a custom makefile configuration in settings via 'makefile.configurations' like described here: [link]");
logger.message("If you are not able to fix the dry-run, open a GitHub issue in Makefile Tools repo: "
+ "https://github.com/microsoft/vscode-makefile-tools/issues");
}
// Helper to make paths absolute until the extension handles variables expansion.
export function resolvePathToRoot(relPath: string): string {
if (!path.isAbsolute(relPath)) {
return path.join(vscode.workspace.rootPath || "", relPath);
}
return relPath;
}