Skip to content
112 changes: 85 additions & 27 deletions src/foundry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,50 @@ import * as vscode from 'vscode';
import {getConfigValue} from './utils';
import {parse as parseToml} from 'smol-toml';

export async function forgeBuild(activeTextEditor: vscode.TextEditor) {
const build = forgeBuildTask(activeTextEditor.document.uri.fsPath);
const buildExecution = await vscode.tasks.executeTask(build);
try {
await completed(buildExecution);
} catch (e) {
const action = await vscode.window.showErrorMessage(
'Failed to build project.',
'Open Settings',
'Help'
);
if (action === 'Open Settings') {
vscode.commands.executeCommand(
'workbench.action.openSettings',
'forge-path'
);
}
if (action === 'Help') {
vscode.commands.executeCommand(
'vscode.open',
vscode.Uri.parse(
'https://docs.runtimeverification.com/simbolik/overview/troubleshooting#failed-to-build-project'
)
);
}
throw new Error('Failed to build project');
}
}

function completed(tastkExecution: vscode.TaskExecution): Promise<void> {
return new Promise((resolve, reject) => {
const disposable = vscode.tasks.onDidEndTaskProcess(e => {
if ((e.execution as any)._id !== (tastkExecution as any)._id) return;
if (e.exitCode !== 0) {
reject();
} else {
resolve();
}
disposable.dispose();
});
});
}

export
function forgeBuildTask(file: string) {
export function forgeBuildTask(file: string) {
const incrementalBuild = getConfigValue('incremental-build', false);
const forgePath = getConfigValue('forge-path', 'forge');
const cwd = file.substring(0, file.lastIndexOf('/'));
Expand All @@ -19,34 +60,35 @@ function forgeBuildTask(file: string) {
new vscode.ShellExecution(forgePath, ['build'], {
cwd,
env: {
'FOUNDRY_OPTIMIZER': 'false',
'FOUNDRY_BUILD_INFO': 'true',
'FOUNDRY_EXTRA_OUTPUT': '["storageLayout", "evm.bytecode.generatedSources"]',
'FOUNDRY_BYTECODE_HASH': 'ipfs',
'FOUNDRY_CBOR_METADATA': 'true',
'FOUNDRY_CACHE': incrementalBuild ? 'true' : 'false',
}
FOUNDRY_OPTIMIZER: 'false',
FOUNDRY_BUILD_INFO: 'true',
FOUNDRY_EXTRA_OUTPUT:
'["storageLayout", "evm.bytecode.generatedSources"]',
FOUNDRY_BYTECODE_HASH: 'ipfs',
FOUNDRY_CBOR_METADATA: 'true',
FOUNDRY_CACHE: incrementalBuild ? 'true' : 'false',
},
})
);
task.isBackground = true;
task.presentationOptions.reveal = vscode.TaskRevealKind.Always;
return task;
}

export
async function loadBuildInfo(file: string): Promise<string> {
export async function loadBuildInfo(file: string): Promise<string> {
const root = await foundryRoot(file);
const buildInfo = await forgeBuildInfo(root);
return buildInfo;
}

export
async function foundryRoot(file: string) {
export async function foundryRoot(file: string) {
// Find the root of the project, which is the directory containing the foundry.toml file
let root = file;
let stat;
try {
stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`));
stat = await vscode.workspace.fs.stat(
vscode.Uri.file(`${root}/foundry.toml`)
);
} catch (e) {
stat = false;
}
Expand All @@ -57,21 +99,25 @@ async function foundryRoot(file: string) {
}
root = root.substring(0, lastSlash);
try {
stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`));
stat = await vscode.workspace.fs.stat(
vscode.Uri.file(`${root}/foundry.toml`)
);
} catch (e) {
stat = false;
}
}
return root;
}

export
type FoundryConfig = { 'profile'?: { [profile: string]: { [key: string]: string } } };
export type FoundryConfig = {
profile?: {[profile: string]: {[key: string]: string}};
};

export
async function foundryConfig(root: string): Promise<FoundryConfig> {
export async function foundryConfig(root: string): Promise<FoundryConfig> {
const configPath = `${root}/foundry.toml`;
const config = await vscode.workspace.fs.readFile(vscode.Uri.file(configPath));
const config = await vscode.workspace.fs.readFile(
vscode.Uri.file(configPath)
);
return parseToml(config.toString());
}

Expand All @@ -83,31 +129,43 @@ async function forgeBuildInfo(root: string): Promise<string> {
const buildInfoDir = `${root}/${out}/build-info`;

// Get list of build-info files
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(buildInfoDir));
const buildInfoFiles = files.filter(([file, type]) => type === vscode.FileType.File && file.endsWith('.json'));
const files = await vscode.workspace.fs.readDirectory(
vscode.Uri.file(buildInfoDir)
);
const buildInfoFiles = files.filter(
([file, type]) => type === vscode.FileType.File && file.endsWith('.json')
);

if (buildInfoFiles.length === 0) {
vscode.window.showErrorMessage('No build-info files found');
return '';
}

// Retrieve file stats and sort by creation timestamp
const sortedFiles = await getSortedFilesByCreationTime(buildInfoDir, buildInfoFiles);
const sortedFiles = await getSortedFilesByCreationTime(
buildInfoDir,
buildInfoFiles
);

// Read the youngest build-info file
const youngestBuildInfo = await vscode.workspace.fs.readFile(sortedFiles[0].uri);
const youngestBuildInfo = await vscode.workspace.fs.readFile(
sortedFiles[0].uri
);
return youngestBuildInfo.toString();
}

async function getSortedFilesByCreationTime(buildInfoDir: string, buildInfoFiles: [string, vscode.FileType][]): Promise<{ file: string, uri: vscode.Uri, ctime: number }[]> {
async function getSortedFilesByCreationTime(
buildInfoDir: string,
buildInfoFiles: [string, vscode.FileType][]
): Promise<{file: string; uri: vscode.Uri; ctime: number}[]> {
const filesWithStats = await Promise.all(
buildInfoFiles.map(async ([file]) => {
const fileUri = vscode.Uri.file(`${buildInfoDir}/${file}`);
const fileStat = await vscode.workspace.fs.stat(fileUri);
return { file, uri: fileUri, ctime: fileStat.ctime };
return {file, uri: fileUri, ctime: fileStat.ctime};
})
);

// Sort files by creation time (ctime)
return filesWithStats.sort((a, b) => b.ctime - a.ctime);
}
}
43 changes: 16 additions & 27 deletions src/startDebugging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
VariableDeclaration,
} from '@solidity-parser/parser/dist/src/ast-types';
import * as vscode from 'vscode';
import { getConfigValue } from './utils';
import { Supervisor } from './supevervisor';
import { forgeBuildTask, foundryRoot, loadBuildInfo } from './foundry';
import {getConfigValue} from './utils';
import {Supervisor} from './supevervisor';
import {forgeBuild, foundryRoot, loadBuildInfo} from './foundry';

export async function startDebugging(
this: Supervisor,
Expand All @@ -23,6 +23,17 @@ export async function startDebugging(
throw new Error('No active text editor.');
}

const autobuild = getConfigValue('autobuild', true);

if (autobuild) {
try {
await forgeBuild(activeTextEditor);
} catch (e) {
if (e instanceof Error) vscode.window.showErrorMessage(e.message);
return;
}
}

const workspaceFolder = vscode.workspace.getWorkspaceFolder(
activeTextEditor.document.uri
);
Expand Down Expand Up @@ -61,18 +72,9 @@ export async function startDebugging(
const debugConfigName = `${contractName}.${methodSignature}`;
const anvilPort = getConfigValue('anvil-port', '8545');
const rpcUrl = `http://localhost:${anvilPort}`;
const autobuild = getConfigValue('autobuild', true);
if (autobuild) {
const build = forgeBuildTask(activeTextEditor.document.uri.fsPath);
const buildExecution = await vscode.tasks.executeTask(build);
try {
await completed(buildExecution);
} catch (e) {
vscode.window.showErrorMessage('Failed to build project.');
}
}
const myFoundryRoot = await foundryRoot(activeTextEditor.document.uri.fsPath);
const buildInfo = await loadBuildInfo(activeTextEditor.document.uri.fsPath);

const myDebugConfig = debugConfig(
debugConfigName,
file,
Expand All @@ -84,26 +86,13 @@ export async function startDebugging(
buildInfo,
myFoundryRoot
);

const session = await vscode.debug.startDebugging(
workspaceFolder,
myDebugConfig
);
}

function completed(tastkExecution: vscode.TaskExecution): Promise<void> {
return new Promise((resolve, reject) => {
const disposable = vscode.tasks.onDidEndTaskProcess(e => {
if ((e.execution as any)._id !== (tastkExecution as any)._id) return;
if (e.exitCode !== 0) {
reject();
} else {
resolve();
}
disposable.dispose();
});
});
}

function debugConfig(
name: string,
file: string,
Expand Down
15 changes: 12 additions & 3 deletions src/supevervisor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,28 @@ export class Supervisor {
this._anvil?.terminate();
this._anvil = undefined;
const action = await vscode.window.showErrorMessage(
'Anvil terminated unexpectedly',
'Anvil terminated unexpectedly.',
'Open Settings',
'Try Again'
'Try Again',
'Help'
);
if (action === 'Open Settings') {
vscode.commands.executeCommand(
'workbench.action.openSettings',
'simbolik.anvil-port'
'anvil-path'
);
this.anvil();
}
if (action === 'Try Again') {
this.anvil();
}
if (action === 'Help') {
vscode.commands.executeCommand(
'vscode.open',
vscode.Uri.parse('https://docs.runtimeverification.com/simbolik/overview/troubleshooting#anvil-terminated-unexpectedly')
);
this.anvil();
}
}
});
}
Expand Down