diff --git a/test-tools/wamr-ide/VSCode-Extension/package.json b/test-tools/wamr-ide/VSCode-Extension/package.json index 45504523..61d1387c 100644 --- a/test-tools/wamr-ide/VSCode-Extension/package.json +++ b/test-tools/wamr-ide/VSCode-Extension/package.json @@ -129,7 +129,7 @@ "program": "./resource/debug/windows/bin/lldb-vscode.exe" }, "osx": { - "program": "./resource/debug/osx/bin/lldb-vscode" + "program": "./resource/debug/darwin/bin/lldb-vscode" }, "linux": { "program": "./resource/debug/linux/bin/lldb-vscode" @@ -237,7 +237,9 @@ "@types/glob": "^7.1.3", "@types/mocha": "^8.2.2", "@types/node": "14.x", + "@types/request": "^2.48.8", "@types/vscode": "^1.54.0", + "@types/yauzl": "^2.10.0", "@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/parser": "^4.26.0", "eslint": "^7.32.0", @@ -248,6 +250,8 @@ "vscode-test": "^1.5.2" }, "dependencies": { - "@vscode/webview-ui-toolkit": "^0.8.4" + "@vscode/webview-ui-toolkit": "^0.8.4", + "request": "^2.88.2", + "yauzl": "^2.10.0" } } diff --git a/test-tools/wamr-ide/VSCode-Extension/resource/debug/osx/.placeholder b/test-tools/wamr-ide/VSCode-Extension/resource/debug/darwin/.placeholder similarity index 100% rename from test-tools/wamr-ide/VSCode-Extension/resource/debug/osx/.placeholder rename to test-tools/wamr-ide/VSCode-Extension/resource/debug/darwin/.placeholder diff --git a/test-tools/wamr-ide/VSCode-Extension/src/decorationProvider.ts b/test-tools/wamr-ide/VSCode-Extension/src/decorationProvider.ts index db636913..e9687f69 100644 --- a/test-tools/wamr-ide/VSCode-Extension/src/decorationProvider.ts +++ b/test-tools/wamr-ide/VSCode-Extension/src/decorationProvider.ts @@ -4,7 +4,7 @@ */ import * as vscode from 'vscode'; -import { ReadFromFile } from './utilities/directoryUtilities'; +import { readFromFile } from './utilities/directoryUtilities'; import * as path from 'path'; import * as os from 'os'; @@ -63,8 +63,8 @@ export class DecorationProvider implements vscode.FileDecorationProvider { prjConfigDir = path.join(currentPrjDir, '.wamr'); configFilePath = path.join(prjConfigDir, 'compilation_config.json'); - if (ReadFromFile(configFilePath) !== '') { - configData = JSON.parse(ReadFromFile(configFilePath)); + if (readFromFile(configFilePath) !== '') { + configData = JSON.parse(readFromFile(configFilePath)); includePathArr = configData['include_paths']; excludeFileArr = configData['exclude_files']; diff --git a/test-tools/wamr-ide/VSCode-Extension/src/extension.ts b/test-tools/wamr-ide/VSCode-Extension/src/extension.ts index 57f2aa09..7eb8b34a 100644 --- a/test-tools/wamr-ide/VSCode-Extension/src/extension.ts +++ b/test-tools/wamr-ide/VSCode-Extension/src/extension.ts @@ -12,12 +12,13 @@ import { WasmTaskProvider } from './taskProvider'; import { TargetConfigPanel } from './view/TargetConfigPanel'; import { NewProjectPanel } from './view/NewProjectPanel'; import { - CheckIfDirectoryExist, - WriteIntoFile, - ReadFromFile, + checkIfDirectoryExists, + writeIntoFile, + readFromFile, } from './utilities/directoryUtilities'; import { decorationProvider } from './decorationProvider'; import { WasmDebugConfigurationProvider } from './debugConfigurationProvider'; +import { isLLDBInstalled, promptInstallLLDB } from './utilities/lldbUtilities'; let wasmTaskProvider: WasmTaskProvider; let wasmDebugConfigProvider: WasmDebugConfigurationProvider; @@ -213,7 +214,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } }); - } else if (!CheckIfDirectoryExist(curWorkspace as string)) { + } else if (!checkIfDirectoryExists(curWorkspace as string)) { vscode.window .showWarningMessage( 'Invalid workspace:', @@ -369,7 +370,7 @@ export async function activate(context: vscode.ExtensionContext) { let disposableDebug = vscode.commands.registerCommand( 'wamride.debug', - () => { + async () => { if (!isWasmProject) { vscode.window.showErrorMessage('debug failed', { modal: true, @@ -378,6 +379,15 @@ export async function activate(context: vscode.ExtensionContext) { return; } + /* we should check again whether the user installed lldb, as this can be skipped during activation */ + try { + if (!isLLDBInstalled(context)) { + await promptInstallLLDB(context); + } + } catch (e) { + vscode.window.showWarningMessage((e as Error).message); + } + /* refuse to debug if build process failed */ if (!checkIfBuildSuccess()) { vscode.window.showErrorMessage('Debug failed', { @@ -582,7 +592,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } }); - } else if (!CheckIfDirectoryExist(curWorkspace as string)) { + } else if (!checkIfDirectoryExists(curWorkspace as string)) { vscode.window .showWarningMessage( 'Invalid workspace:', @@ -686,6 +696,14 @@ export async function activate(context: vscode.ExtensionContext) { disposableToggleExcludeFile, disposableDebug ); + + try { + if (!isLLDBInstalled(context)) { + await promptInstallLLDB(context); + } + } catch (e) { + vscode.window.showWarningMessage((e as Error).message); + } } function openWindoWithSituation(uri: vscode.Uri) { @@ -736,13 +754,13 @@ export function writeIntoConfigFile( let prjConfigDir = path.join(currentPrjDir, '.wamr'); let configFilePath = path.join(prjConfigDir, 'compilation_config.json'); - WriteIntoFile(configFilePath, jsonStr); + writeIntoFile(configFilePath, jsonStr); } export function readFromConfigFile(): string { let prjConfigDir = path.join(currentPrjDir, '.wamr'); let configFilePath = path.join(prjConfigDir, 'compilation_config.json'); - return ReadFromFile(configFilePath); + return readFromFile(configFilePath); } /** @@ -854,7 +872,7 @@ function generateCMakeFile( .concat('\n', strSrcList) .concat('\n', strIncludeList); - WriteIntoFile(cmakeFilePath, fullStr); + writeIntoFile(cmakeFilePath, fullStr); } function getAllSrcFiles(_path: string) { diff --git a/test-tools/wamr-ide/VSCode-Extension/src/utilities/directoryUtilities.ts b/test-tools/wamr-ide/VSCode-Extension/src/utilities/directoryUtilities.ts index cff54c7f..348e5730 100644 --- a/test-tools/wamr-ide/VSCode-Extension/src/utilities/directoryUtilities.ts +++ b/test-tools/wamr-ide/VSCode-Extension/src/utilities/directoryUtilities.ts @@ -7,12 +7,14 @@ import fileSystem = require('fs'); import vscode = require('vscode'); import path = require('path'); import os = require('os'); +import request = require('request'); +import yauzl = require('yauzl'); /** * * @param path destination path */ -export function CreateDirectory( +export function createDirectory( dest: string, mode: string | number | null | undefined = undefined ): boolean { @@ -30,7 +32,7 @@ export function CreateDirectory( } let parent = path.dirname(dest); - if (!CreateDirectory(parent, mode)) { + if (!createDirectory(parent, mode)) { return false; } @@ -42,7 +44,7 @@ export function CreateDirectory( } } -export function CopyFiles(src: string, dest: string, flags?: number): boolean { +export function copyFiles(src: string, dest: string, flags?: number): boolean { try { fileSystem.copyFileSync(src, dest); return true; @@ -52,7 +54,7 @@ export function CopyFiles(src: string, dest: string, flags?: number): boolean { } } -export function WriteIntoFile(path: string, data: string): void { +export function writeIntoFile(path: string, data: string): void { try { fileSystem.writeFileSync(path, data, null); } catch (err) { @@ -60,7 +62,7 @@ export function WriteIntoFile(path: string, data: string): void { } } -export function ReadFromFile(path: string): string { +export function readFromFile(path: string): string { try { let data = fileSystem.readFileSync(path, { encoding: 'utf-8' }); return data as string; @@ -70,7 +72,7 @@ export function ReadFromFile(path: string): string { } } -export function WriteIntoFileAsync( +export function writeIntoFileAsync( path: string, data: string, callback: fileSystem.NoParamCallback @@ -83,7 +85,7 @@ export function WriteIntoFileAsync( } } -export function CheckIfDirectoryExist(path: string): boolean { +export function checkIfPathExists(path: string): boolean { try { if (fileSystem.existsSync(path)) { return true; @@ -96,6 +98,22 @@ export function CheckIfDirectoryExist(path: string): boolean { } } +export function checkIfDirectoryExists(path: string): boolean { + const doesPathExist = checkIfPathExists(path); + if (doesPathExist) { + return fileSystem.lstatSync(path).isDirectory(); + } + return false; +} + +export function checkIfFileExists(path: string): boolean { + const doesPathExist = checkIfPathExists(path); + if (doesPathExist) { + return fileSystem.lstatSync(path).isFile(); + } + return false; +} + export function checkFolderName(folderName: string) { let invalidCharacterArr: string[] = []; var valid = true; @@ -118,3 +136,54 @@ export function checkFolderName(folderName: string) { return valid; } + +export function downloadFile(url: string, destinationPath: string): Promise { + return new Promise((resolve, reject) => { + const file = fileSystem.createWriteStream(destinationPath); + const stream = request(url, undefined, (error, response, body) => { + if (response.statusCode !== 200) { + reject(new Error(`Download from ${url} failed with ${response.statusMessage}`)); + } + }).pipe(file); + stream.on("close", resolve); + stream.on("error", reject); + }); +} + +export function unzipFile(sourcePath: string, getDestinationFileName: (entryName: string) => string): Promise { + return new Promise((resolve, reject) => { + const unzippedFilePaths: string[] = []; + yauzl.open(sourcePath, { lazyEntries: true }, function(error, zipfile) { + if (error) { + reject(error); + return; + } + zipfile.readEntry(); + zipfile.on("entry", function(entry) { + // This entry is a directory so skip it + if (/\/$/.test(entry.fileName)) { + zipfile.readEntry(); + return; + } + + zipfile.openReadStream(entry, function(error, readStream) { + if (error) { + reject(error); + return; + } + readStream.on("end", () => zipfile.readEntry()); + const destinationFileName = getDestinationFileName(entry.fileName); + fileSystem.mkdirSync(path.dirname(destinationFileName), { recursive: true }); + + const file = fileSystem.createWriteStream(destinationFileName); + readStream.pipe(file).on("error", reject); + unzippedFilePaths.push(destinationFileName); + }); + }); + zipfile.on("end", function() { + zipfile.close(); + resolve(unzippedFilePaths); + }); + }); + }); +} \ No newline at end of file diff --git a/test-tools/wamr-ide/VSCode-Extension/src/utilities/lldbUtilities.ts b/test-tools/wamr-ide/VSCode-Extension/src/utilities/lldbUtilities.ts new file mode 100644 index 00000000..50d468b9 --- /dev/null +++ b/test-tools/wamr-ide/VSCode-Extension/src/utilities/lldbUtilities.ts @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 Amazon.com Inc. or its affiliates. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +import * as vscode from 'vscode'; +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import { checkIfFileExists, downloadFile, unzipFile } from './directoryUtilities'; + +const LLDB_RESOURCE_DIR = "resource/debug"; +const LLDB_OS_DOWNLOAD_URL_SUFFIX_MAP: Partial> = { + "linux": "x86_64-ubuntu-22.04", + "darwin": "universal-macos-latest" +}; + +const WAMR_LLDB_NOT_SUPPORTED_ERROR = new Error("WAMR LLDB is not supported on this platform"); + +function getLLDBUnzipFilePath(destinationFolder: string, filename: string) { + const dirs = filename.split("/"); + if (dirs[0] === "inst") { + dirs.shift(); + } + + return path.join(destinationFolder, ...dirs); +} + +function getLLDBDownloadUrl(context: vscode.ExtensionContext): string { + const wamrVersion = require(path.join(context.extensionPath, "package.json")).version; + const lldbOsUrlSuffix = LLDB_OS_DOWNLOAD_URL_SUFFIX_MAP[os.platform()]; + + if (!lldbOsUrlSuffix) { + throw WAMR_LLDB_NOT_SUPPORTED_ERROR; + } + + return `https://github.com/bytecodealliance/wasm-micro-runtime/releases/download/WAMR-${wamrVersion}/wamr-lldb-${wamrVersion}-${lldbOsUrlSuffix}.zip`; +} + +export function isLLDBInstalled(context: vscode.ExtensionContext): boolean { + const extensionPath = context.extensionPath; + const lldbOSDir = os.platform(); + const lldbBinaryPath = path.join(extensionPath, LLDB_RESOURCE_DIR, lldbOSDir, "bin", "lldb"); + return checkIfFileExists(lldbBinaryPath); +} + +export async function promptInstallLLDB(context: vscode.ExtensionContext) { + const extensionPath = context.extensionPath; + const setupPrompt = "setup"; + const skipPrompt = "skip"; + const response = await vscode.window.showWarningMessage('No LLDB instance found. Setup now?', setupPrompt, skipPrompt); + + if (response === skipPrompt) { + return; + } + + const downloadUrl = getLLDBDownloadUrl(context); + const destinationDir = os.platform(); + + if (!downloadUrl) { + throw WAMR_LLDB_NOT_SUPPORTED_ERROR; + } + + const lldbDestinationFolder = path.join(extensionPath, LLDB_RESOURCE_DIR, destinationDir); + const lldbZipPath = path.join(lldbDestinationFolder, "bundle.zip"); + + vscode.window.showInformationMessage(`Downloading LLDB...`); + + await downloadFile(downloadUrl, lldbZipPath); + + vscode.window.showInformationMessage(`LLDB downloaded to ${lldbZipPath}. Installing...`); + + const lldbFiles = await unzipFile(lldbZipPath, filename => getLLDBUnzipFilePath(lldbDestinationFolder, filename)); + // Allow execution of lldb + lldbFiles.forEach(file => fs.chmodSync(file, "0775")); + + vscode.window.showInformationMessage(`LLDB installed at ${lldbDestinationFolder}`); + + // Remove the bundle.zip + fs.unlink(lldbZipPath, () => {}); +} + + diff --git a/test-tools/wamr-ide/VSCode-Extension/src/view/NewProjectPanel.ts b/test-tools/wamr-ide/VSCode-Extension/src/view/NewProjectPanel.ts index a7536564..29f1e054 100644 --- a/test-tools/wamr-ide/VSCode-Extension/src/view/NewProjectPanel.ts +++ b/test-tools/wamr-ide/VSCode-Extension/src/view/NewProjectPanel.ts @@ -8,8 +8,8 @@ import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; import { - CreateDirectory, - CopyFiles, + createDirectory, + copyFiles, checkFolderName, } from '../utilities/directoryUtilities'; import { getUri } from '../utilities/getUri'; @@ -84,16 +84,16 @@ export class NewProjectPanel { } } - CreateDirectory(path.join(ROOT_PATH, '.wamr')); - CreateDirectory(path.join(ROOT_PATH, 'include')); - CreateDirectory(path.join(ROOT_PATH, 'src')); + createDirectory(path.join(ROOT_PATH, '.wamr')); + createDirectory(path.join(ROOT_PATH, 'include')); + createDirectory(path.join(ROOT_PATH, 'src')); - CopyFiles( + copyFiles( path.join(EXT_PATH, 'resource/scripts/CMakeLists.txt'), path.join(ROOT_PATH, '.wamr/CMakeLists.txt') ); - CopyFiles( + copyFiles( path.join(EXT_PATH, 'resource/scripts/project.cmake'), path.join(ROOT_PATH, '.wamr/project.cmake') );