diff --git a/src/build-tool/colcon.ts b/src/build-tool/colcon.ts index 7dcb55f2..63b1c29b 100644 --- a/src/build-tool/colcon.ts +++ b/src/build-tool/colcon.ts @@ -10,7 +10,14 @@ import * as common from "./common"; import * as rosShell from "./ros-shell"; function makeColcon(command: string, verb: string, args: string[], category?: string): vscode.Task { - const task = rosShell.make({type: command, command, args: [verb, '--symlink-install', '--event-handlers', 'console_cohesion+', '--base-paths', extension.baseDir, `--cmake-args`, `-DCMAKE_BUILD_TYPE=RelWithDebInfo`,...args]}, + let installType = '--symlink-install'; + if (process.platform === "win32") { + + // Use Merge Install on Windows to support adminless builds and deployment. + installType = '--merge-install'; + } + + const task = rosShell.make({type: command, command, args: [verb, installType, '--event-handlers', 'console_cohesion+', '--base-paths', extension.baseDir, `--cmake-args`, `-DCMAKE_BUILD_TYPE=RelWithDebInfo`,...args]}, category) task.problemMatchers = ["$catkin-gcc"]; diff --git a/src/debugger/configuration/resolvers/ros1/launch.ts b/src/debugger/configuration/resolvers/ros1/launch.ts index 2fdba2b9..04672e88 100644 --- a/src/debugger/configuration/resolvers/ros1/launch.ts +++ b/src/debugger/configuration/resolvers/ros1/launch.ts @@ -45,7 +45,7 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { // Manage the status of the ROS core, starting one if not present // The ROS core will continue to run until the VSCode window is closed if (await rosApi.getCoreStatus() == false) { - console.log("ROS Core is not active, attempting to start automatically"); + extension.outputChannel.appendLine("ROS Core is not active, attempting to start automatically"); rosApi.startCore(); // Wait for the core to start up to a timeout @@ -58,7 +58,7 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { await delay(interval_ms); } - console.log("Waited " + timeWaited + " for ROS Core to start"); + extension.outputChannel.appendLine("Waited " + timeWaited + " for ROS Core to start"); if (timeWaited >= timeout_ms) { throw new Error('Timed out (' + timeWaited / 1000 + ' seconds) waiting for ROS Core to start. Start ROSCore manually to avoid this error.'); diff --git a/src/debugger/configuration/resolvers/ros2/launch.ts b/src/debugger/configuration/resolvers/ros2/launch.ts index 4842b063..1ce4c443 100644 --- a/src/debugger/configuration/resolvers/ros2/launch.ts +++ b/src/debugger/configuration/resolvers/ros2/launch.ts @@ -3,6 +3,7 @@ import * as child_process from "child_process"; import * as fs from "fs"; +import * as fsp from "fs/promises"; import * as yaml from "js-yaml"; import * as os from "os"; import * as path from "path"; @@ -32,6 +33,11 @@ interface ILaunchRequest { attachDebugger?: string[]; // If specified, Scripts or executables to debug; otherwise attaches to everything not ignored } +interface ICppEnvConfig { + name: string; + value: string; +} + function getExtensionFilePath(extensionFile: string): string { return path.resolve(extension.extPath, extensionFile); } @@ -39,18 +45,21 @@ function getExtensionFilePath(extensionFile: string): string { export class LaunchResolver implements vscode.DebugConfigurationProvider { // tslint:disable-next-line: max-line-length public async resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, config: requests.ILaunchRequest, token?: vscode.CancellationToken) { - if (!path.isAbsolute(config.target)) { - throw new Error("Launch request requires an absolute path as target."); + await fsp.access(config.target, fs.constants.R_OK); + + if (path.extname(config.target) == ".xml" || path.extname(config.target) == ".yaml") { + throw new Error("Support for '.xml' or '.yaml' launch files coming soon (see https://github.com/ms-iot/vscode-ros/issues/805)."); } - else if (path.extname(config.target) !== ".py" && path.extname(config.target) !== ".xml") { - throw new Error("Launch request requires an extension '.py' or '.xml' as target."); + + if (path.extname(config.target) !== ".py") { + throw new Error("Launch request requires an extension '.py'."); } const delay = ms => new Promise(res => setTimeout(res, ms)); // Manage the status of the ROS2 Daemon, starting one if not present if (await rosApi.getCoreStatus() == false) { - console.log("ROS Daemon is not active, attempting to start automatically"); + extension.outputChannel.appendLine("ROS Daemon is not active, attempting to start automatically"); rosApi.startCore(); // Wait for the core to start up to a timeout @@ -63,11 +72,7 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { await delay(interval_ms); } - console.log("Waited " + timeWaited + " for ROS2 Daemon to start"); - - if (timeWaited >= timeout_ms) { - throw new Error('Timed out (' + timeWaited / 1000 + ' seconds) waiting for ROS2 Daemon to start. Start ROS2 Daemon manually to avoid this error.'); - } + extension.outputChannel.appendLine("Waited " + timeWaited + " for ROS2 Daemon to start. Proceeding without the Daemon."); } const rosExecOptions: child_process.ExecOptions = { @@ -77,8 +82,8 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { }, }; - console.log("Executing dumper with the following environment:"); - console.log(rosExecOptions.env); + extension.outputChannel.appendLine("Executing dumper with the following environment:"); + extension.outputChannel.appendLine(JSON.stringify(rosExecOptions.env)); let ros2_launch_dumper = getExtensionFilePath(path.join("assets", "scripts", "ros2_launch_dumper.py")); @@ -97,7 +102,7 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { if (result.stderr) { // Having stderr output is not nessesarily a problem, but it is useful for debugging - console.log(`ROS2 launch processor produced stderr output:\r\n ${result.stderr}`); + extension.outputChannel.appendLine(`ROS2 launch processor produced stderr output:\r\n ${result.stderr}`); } if (result.stdout.length == 0) { @@ -188,50 +193,84 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { return null; } + private createPythonLaunchConfig(request: ILaunchRequest, stopOnEntry: boolean): IPythonLaunchConfiguration { + const pythonLaunchConfig: IPythonLaunchConfiguration = { + name: request.nodeName, + type: "python", + request: "launch", + program: request.executable, + args: request.arguments, + env: request.env, + stopOnEntry: stopOnEntry, + justMyCode: false, + }; + + return pythonLaunchConfig; + } + + private createCppLaunchConfig(request: ILaunchRequest, stopOnEntry: boolean): ICppvsdbgLaunchConfiguration { + const envConfigs: ICppEnvConfig[] = []; + for (const key in request.env) { + if (request.env.hasOwnProperty(key)) { + envConfigs.push({ + name: key, + value: request.env[key], + }); + } + } + + const cppvsdbgLaunchConfig: ICppvsdbgLaunchConfiguration = { + name: request.nodeName, + type: "cppvsdbg", + request: "launch", + cwd: ".", + program: request.executable, + args: request.arguments, + environment: envConfigs, + stopAtEntry: stopOnEntry, + symbolSearchPath: request.symbolSearchPath, + sourceFileMap: request.sourceFileMap + + }; + + return cppvsdbgLaunchConfig; + } + private async executeLaunchRequest(request: ILaunchRequest, stopOnEntry: boolean) { let debugConfig: ICppvsdbgLaunchConfiguration | ICppdbgLaunchConfiguration | IPythonLaunchConfiguration; if (os.platform() === "win32") { - if (request.executable.toLowerCase().endsWith(".py")) { - const pythonLaunchConfig: IPythonLaunchConfiguration = { - name: request.nodeName, - type: "python", - request: "launch", - program: request.executable, - args: request.arguments, - env: request.env, - stopOnEntry: stopOnEntry, - justMyCode: false, - }; - debugConfig = pythonLaunchConfig; - } else if (request.executable.toLowerCase().endsWith(".exe")) { - interface ICppEnvConfig { - name: string; - value: string; - } - const envConfigs: ICppEnvConfig[] = []; - for (const key in request.env) { - if (request.env.hasOwnProperty(key)) { - envConfigs.push({ - name: key, - value: request.env[key], - }); - } + let nodePath = path.parse(request.executable); + + if (nodePath.ext.toLowerCase() === ".exe") { + + // On Windows, colcon will compile Python scripts, C# and Rust programs to .exe. + // Discriminate between different runtimes by introspection. + + // Python + // rosnode.py will be compiled into install\rosnode\Lib\rosnode\rosnode.exe + // rosnode.py will also be copied into install\rosnode\Lib\site-packages\rosnode.py + + let pythonPath = path.join(nodePath.dir, "..", "site-packages", nodePath.name, nodePath.name + ".py"); + + try { + await fsp.access(pythonPath, fs.constants.R_OK); + + // If the python file is available, then treat it as python and fall through. + request.executable = pythonPath; + debugConfig = this.createPythonLaunchConfig(request, stopOnEntry); + } catch { + // The python file is not available then this must be... + + // C#? Todo + + // Rust? Todo + + // C++ + debugConfig = this.createCppLaunchConfig(request, stopOnEntry); } - const cppvsdbgLaunchConfig: ICppvsdbgLaunchConfiguration = { - name: request.nodeName, - type: "cppvsdbg", - request: "launch", - cwd: ".", - program: request.executable, - args: request.arguments, - environment: envConfigs, - stopAtEntry: stopOnEntry, - symbolSearchPath: request.symbolSearchPath, - sourceFileMap: request.sourceFileMap - - }; - debugConfig = cppvsdbgLaunchConfig; + } else if (nodePath.ext.toLowerCase() === ".py") { + debugConfig = this.createPythonLaunchConfig(request, stopOnEntry); } if (!debugConfig) { @@ -242,19 +281,8 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { throw (new Error(`Failed to start debug session!`)); } } else { - try { - // this should be guaranteed by roslaunch - fs.accessSync(request.executable, fs.constants.X_OK); - } catch (errNotExecutable) { - throw (new Error(`Error! ${request.executable} is not executable!`)); - } - - try { - // need to be readable to check shebang line - fs.accessSync(request.executable, fs.constants.R_OK); - } catch (errNotReadable) { - throw (new Error(`Error! ${request.executable} is not readable!`)); - } + // this should be guaranteed by roslaunch + await fsp.access(request.executable, fs.constants.X_OK | fs.constants.R_OK); const fileStream = fs.createReadStream(request.executable); const rl = readline.createInterface({ @@ -275,51 +303,9 @@ export class LaunchResolver implements vscode.DebugConfigurationProvider { // look for Python in shebang line if (line.startsWith("#!") && line.toLowerCase().indexOf("python") !== -1) { - const pythonLaunchConfig: IPythonLaunchConfiguration = { - name: request.nodeName, - type: "python", - request: "launch", - program: request.executable, - args: request.arguments, - env: request.env, - stopOnEntry: stopOnEntry, - justMyCode: false, - }; - debugConfig = pythonLaunchConfig; + debugConfig = this.createPythonLaunchConfig(request, stopOnEntry); } else { - interface ICppEnvConfig { - name: string; - value: string; - } - const envConfigs: ICppEnvConfig[] = []; - for (const key in request.env) { - if (request.env.hasOwnProperty(key)) { - envConfigs.push({ - name: key, - value: request.env[key], - }); - } - } - const cppdbgLaunchConfig: ICppdbgLaunchConfiguration = { - name: request.nodeName, - type: "cppdbg", - request: "launch", - cwd: ".", - program: request.executable, - args: request.arguments, - environment: envConfigs, - stopAtEntry: stopOnEntry, - additionalSOLibSearchPath: request.additionalSOLibSearchPath, - sourceFileMap: request.sourceFileMap, - setupCommands: [ - { - text: "-enable-pretty-printing", - description: "Enable pretty-printing for gdb", - ignoreFailures: true - } - ] - }; - debugConfig = cppdbgLaunchConfig; + debugConfig = this.createCppLaunchConfig(request, stopOnEntry); } if (!debugConfig) { diff --git a/src/extension.ts b/src/extension.ts index f995c528..5b7ea7c3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -34,6 +34,7 @@ export function setBaseDir(dir: string) { * The sourced ROS environment. */ export let env: any; +export let processingWorkspace = false; export let extPath: string; export let outputChannel: vscode.OutputChannel; @@ -109,13 +110,87 @@ export async function activate(context: vscode.ExtensionContext) { config = updatedConfig; })); - await sourceRosAndWorkspace().then(() => - { - vscode.window.registerWebviewPanelSerializer('urdfPreview', URDFPreviewManager.INSTANCE); + vscode.window.registerWebviewPanelSerializer('urdfPreview', URDFPreviewManager.INSTANCE); + + vscode.commands.registerCommand(Commands.CreateTerminal, () => { + ensureErrorMessageOnException(() => { + ros_utils.createTerminal(context); + }); + }); + + vscode.commands.registerCommand(Commands.GetDebugSettings, () => { + ensureErrorMessageOnException(() => { + return debug_utils.getDebugSettings(context); + }); + }); + + vscode.commands.registerCommand(Commands.ShowCoreStatus, () => { + ensureErrorMessageOnException(() => { + rosApi.showCoreMonitor(); + }); + }); + + vscode.commands.registerCommand(Commands.StartRosCore, () => { + ensureErrorMessageOnException(() => { + rosApi.startCore(); + }); + }); + + vscode.commands.registerCommand(Commands.TerminateRosCore, () => { + ensureErrorMessageOnException(() => { + rosApi.stopCore(); + }); + }); + + vscode.commands.registerCommand(Commands.UpdateCppProperties, () => { + ensureErrorMessageOnException(() => { + return ros_build_utils.updateCppProperties(context); + }); + }); + + vscode.commands.registerCommand(Commands.UpdatePythonPath, () => { + ensureErrorMessageOnException(() => { + ros_build_utils.updatePythonPath(context); + }); + }); + + vscode.commands.registerCommand(Commands.Rosrun, () => { + ensureErrorMessageOnException(() => { + return ros_cli.rosrun(context); + }); + }); + + vscode.commands.registerCommand(Commands.Roslaunch, () => { + ensureErrorMessageOnException(() => { + return ros_cli.roslaunch(context); + }); + }); + + vscode.commands.registerCommand(Commands.Rostest, () => { + ensureErrorMessageOnException(() => { + return ros_cli.rostest(context); + }); + }); + + vscode.commands.registerCommand(Commands.PreviewURDF, () => { + ensureErrorMessageOnException(() => { + URDFPreviewManager.INSTANCE.preview(vscode.window.activeTextEditor.document.uri); + }); }); + vscode.commands.registerCommand(Commands.Rosdep, () => { + ensureErrorMessageOnException(() => { + rosApi.rosdep(); + }); + }); + + reporter.sendTelemetryActivate(); + + // Activate the workspace environment if possible. + await activateEnvironment(context); + return { getBaseDir: () => baseDir, getEnv: () => env, @@ -143,11 +218,20 @@ async function ensureErrorMessageOnException(callback: (...args: any[]) => any) * Activates components which require a ROS env. */ async function activateEnvironment(context: vscode.ExtensionContext) { + + if (processingWorkspace) { + return; + } + + processingWorkspace = true; + // Clear existing disposables. while (subscriptions.length > 0) { subscriptions.pop().dispose(); } + await sourceRosAndWorkspace(); + if (typeof env.ROS_DISTRO === "undefined") { return; } @@ -172,65 +256,6 @@ async function activateEnvironment(context: vscode.ExtensionContext) { debug_manager.registerRosDebugManager(context); - // register plugin commands - subscriptions.push( - vscode.commands.registerCommand(Commands.CreateTerminal, () => { - ensureErrorMessageOnException(() => { - ros_utils.createTerminal(context); - }); - }), - vscode.commands.registerCommand(Commands.GetDebugSettings, () => { - ensureErrorMessageOnException(() => { - return debug_utils.getDebugSettings(context); - }); - }), - vscode.commands.registerCommand(Commands.ShowCoreStatus, () => { - ensureErrorMessageOnException(() => { - rosApi.showCoreMonitor(); - }); - }), - vscode.commands.registerCommand(Commands.StartRosCore, () => { - ensureErrorMessageOnException(() => { - rosApi.startCore(); - }); - }), - vscode.commands.registerCommand(Commands.TerminateRosCore, () => { - ensureErrorMessageOnException(() => { - rosApi.stopCore(); - }); - }), - vscode.commands.registerCommand(Commands.UpdateCppProperties, () => { - ensureErrorMessageOnException(() => { - return ros_build_utils.updateCppProperties(context); - }); - }), - vscode.commands.registerCommand(Commands.UpdatePythonPath, () => { - ensureErrorMessageOnException(() => { - ros_build_utils.updatePythonPath(context); - }); - }), - vscode.commands.registerCommand(Commands.Rosrun, () => { - ensureErrorMessageOnException(() => { - return ros_cli.rosrun(context); - }); - }), - vscode.commands.registerCommand(Commands.Roslaunch, () => { - ensureErrorMessageOnException(() => { - return ros_cli.roslaunch(context); - }); - }), - vscode.commands.registerCommand(Commands.Rostest, () => { - ensureErrorMessageOnException(() => { - return ros_cli.rostest(context); - }); - }), - vscode.commands.registerCommand(Commands.PreviewURDF, () => { - ensureErrorMessageOnException(() => { - URDFPreviewManager.INSTANCE.preview(vscode.window.activeTextEditor.document.uri); - }); - }), - ); - // Register commands dependent on a workspace if (buildToolDetected) { subscriptions.push( @@ -239,11 +264,6 @@ async function activateEnvironment(context: vscode.ExtensionContext) { return buildtool.BuildTool.createPackage(context); }); }), - vscode.commands.registerCommand(Commands.Rosdep, () => { - ensureErrorMessageOnException(() => { - rosApi.rosdep(); - }); - }), vscode.tasks.onDidEndTask((event: vscode.TaskEndEvent) => { if (buildtool.isROSBuildTask(event.execution.task)) { sourceRosAndWorkspace(); @@ -256,9 +276,6 @@ async function activateEnvironment(context: vscode.ExtensionContext) { vscode.commands.registerCommand(Commands.CreateCatkinPackage, () => { vscode.window.showErrorMessage(`${Commands.CreateCatkinPackage} requires a ROS workspace to be opened`); }), - vscode.commands.registerCommand(Commands.Rosdep, () => { - vscode.window.showErrorMessage(`${Commands.Rosdep} requires a ROS workspace to be opened`); - }), ); } @@ -266,13 +283,18 @@ async function activateEnvironment(context: vscode.ExtensionContext) { if (buildToolDetected) { ros_build_utils.createConfigFiles(); } + + processingWorkspace = false; } /** * Loads the ROS environment, and prompts the user to select a distro if required. */ async function sourceRosAndWorkspace(): Promise { - env = undefined; + + // Processing a new environment can take time which introduces a race condition. + // Wait to atomicly switch by composing a new environment block then switching at the end. + let newEnv = undefined; const kWorkspaceConfigTimeout = 30000; // ms @@ -287,7 +309,7 @@ async function sourceRosAndWorkspace(): Promise { let isolateEnvironment = config.get("isolateEnvironment", ""); if (!isolateEnvironment) { // Capture the host environment unless specifically isolated - env = process.env; + newEnv = process.env; } @@ -300,11 +322,11 @@ async function sourceRosAndWorkspace(): Promise { // Try to support cases where the setup script doesn't make sense on different environments, such as host vs container. if (await pfs.exists(rosSetupScript)){ try { - env = await ros_utils.sourceSetupFile(rosSetupScript, env); + newEnv = await ros_utils.sourceSetupFile(rosSetupScript, newEnv); attemptWorkspaceDiscovery = false; } catch (err) { - vscode.window.showErrorMessage(`A workspace setup script was provided in the configuration, but could not source "${rosSetupScript}". Attempting standard discovery.`); + vscode.window.showErrorMessage(`A ROS setup script was provided, but could not source "${rosSetupScript}". Attempting standard discovery.`); } } } @@ -343,12 +365,12 @@ async function sourceRosAndWorkspace(): Promise { name: "setup", ext: setupScriptExt, }); - env = await ros_utils.sourceSetupFile(setupScript, env); + newEnv = await ros_utils.sourceSetupFile(setupScript, newEnv); } catch (err) { vscode.window.showErrorMessage(`Could not source ROS setup script at "${setupScript}".`); } } else if (process.env.ROS_DISTRO) { - env = process.env; + newEnv = process.env; } } // Source the workspace setup over the top. @@ -367,14 +389,16 @@ async function sourceRosAndWorkspace(): Promise { ext: setupScriptExt, }); - if (env && typeof env.ROS_DISTRO !== "undefined" && await pfs.exists(wsSetupScript)) { + if (newEnv && typeof newEnv.ROS_DISTRO !== "undefined" && await pfs.exists(wsSetupScript)) { try { - env = await ros_utils.sourceSetupFile(wsSetupScript, env); + newEnv = await ros_utils.sourceSetupFile(wsSetupScript, newEnv); } catch (_err) { vscode.window.showErrorMessage("Failed to source the workspace setup file."); } } + env = newEnv; + // Notify listeners the environment has changed. onEnvChanged.fire(); } diff --git a/src/ros/ros1/core-monitor/main.ts b/src/ros/ros1/core-monitor/main.ts index e3c72b72..2c8aac27 100644 --- a/src/ros/ros1/core-monitor/main.ts +++ b/src/ros/ros1/core-monitor/main.ts @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as extension from "../../../extension"; function removeAllChildElements(e) { while (e.firstChild) { @@ -131,7 +132,7 @@ function initializeCoreMonitor() { removeAllChildElements(servicesElement); if (message.status) { - console.log("ROS online"); + extension.outputChannel.appendLine("ROS online"); coreStatus.textContent = "online"; let parameters = JSON.parse(message.parameters); @@ -152,7 +153,7 @@ function initializeCoreMonitor() { servicesElement.appendChild(generateServicesTable(systemState.services)); } else { - console.log("ROS offline"); + extension.outputChannel.appendLine("ROS offline"); coreStatus.textContent = "offline"; } }); diff --git a/src/ros/ros2/daemon.ts b/src/ros/ros2/daemon.ts index 7aed20cc..80e0593d 100644 --- a/src/ros/ros2/daemon.ts +++ b/src/ros/ros2/daemon.ts @@ -7,6 +7,7 @@ import * as vscode from "vscode"; import * as extension from "../../extension"; import * as ros2_monitor from "./ros2-monitor" +import { env } from "../../extension"; /** * start the ROS2 daemon. @@ -14,7 +15,8 @@ import * as ros2_monitor from "./ros2-monitor" export async function startDaemon() { const command: string = "ros2 daemon start"; const exec = util.promisify(child_process.exec); - await exec(command, { env: this.env }); + extension.outputChannel.appendLine("Attempting to start daemon with " + command); + await exec(command, { env: env }); } /** @@ -23,7 +25,7 @@ export async function startDaemon() { export async function stopDaemon() { const command: string = "ros2 daemon stop"; const exec = util.promisify(child_process.exec); - await exec(command, { env: this.env }); + await exec(command, { env: env }); } /** @@ -60,7 +62,7 @@ export class StatusBarItem { const result = await this.ros2cli.getNodeNamesAndNamespaces(); status = true; } catch (error) { - // do nothing. + // Do nothing } finally { const statusIcon = status ? "$(check)" : "$(x)"; let ros = "ROS"; diff --git a/src/ros/utils.ts b/src/ros/utils.ts index 737d0405..f5a03110 100644 --- a/src/ros/utils.ts +++ b/src/ros/utils.ts @@ -19,8 +19,9 @@ export function sourceSetupFile(filename: string, env?: any): Promise { exportEnvCommand = `cmd /c "\"${filename}\" && set"`; } else { - exportEnvCommand = `bash -c "source '${filename}' && env"`; - console.log ("executing " + exportEnvCommand); + // Force login shell, so ROS sources correctly in containers. + exportEnvCommand = `bash --login -c "source '${filename}' && env"`; + extension.outputChannel.appendLine("Sourcing Environment using: " + exportEnvCommand); } let processOptions: child_process.ExecOptions = { @@ -57,7 +58,7 @@ export function xacro(filename: string): Promise { if (process.platform === "win32") { xacroCommand = `cmd /c "xacro "${filename}""`; } else { - xacroCommand = `bash -c "xacro '${filename}' && env"`; + xacroCommand = `bash --login -c "xacro '${filename}' && env"`; } child_process.exec(xacroCommand, processOptions, (error, stdout, _stderr) => { diff --git a/src/urdfPreview/preview.ts b/src/urdfPreview/preview.ts index cbc8dbe4..4a19d8c0 100644 --- a/src/urdfPreview/preview.ts +++ b/src/urdfPreview/preview.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import { rosApi } from '../ros/ros'; import { xacro } from '../ros/utils'; import { Disposable, window } from 'vscode'; +import * as extension from "../extension"; export default class URDFPreview { @@ -111,39 +112,40 @@ export default class URDFPreview } var packageMap = await rosApi.getPackages(); - - // replace package://(x) with fully resolved paths - var pattern = /package:\/\/(.*?)\//g; - var match; - while (match = pattern.exec(urdfText)) { - var packagePath = await packageMap[match[1]](); - if (packagePath.charAt(0) === '/') { - // inside of mesh re \source, the loader attempts to concatinate the base uri with the new path. It first checks to see if the - // base path has a /, if not it adds it. - // We are attempting to use a protocol handler as the base path - which causes this to fail. - // basepath - vscode-webview-resource: - // full path - /home/test/ros - // vscode-webview-resource://home/test/ros. - // It should be vscode-webview-resource:/home/test/ros. - // So remove the first char. - - packagePath = packagePath.substr(1); + if (packageMap != null) { + // replace package://(x) with fully resolved paths + var pattern = /package:\/\/(.*?)\//g; + var match; + while (match = pattern.exec(urdfText)) { + var packagePath = await packageMap[match[1]](); + if (packagePath.charAt(0) === '/') { + // inside of mesh re \source, the loader attempts to concatinate the base uri with the new path. It first checks to see if the + // base path has a /, if not it adds it. + // We are attempting to use a protocol handler as the base path - which causes this to fail. + // basepath - vscode-webview-resource: + // full path - /home/test/ros + // vscode-webview-resource://home/test/ros. + // It should be vscode-webview-resource:/home/test/ros. + // So remove the first char. + + packagePath = packagePath.substr(1); + } + let normPath = path.normalize(packagePath); + let vsPath = vscode.Uri.file(normPath); + let newUri = this._webview.webview.asWebviewUri(vsPath); + let hackThePath = newUri.toString().replace('https:', ''); + + // HACKHACK - the RosWebTools will alwayse prefix the paths with a '/' if we don't pass a prefix. + // to workaround this without changing RWT, we are stripping off the known protocol, and passing the + // resulting path into RWT with that known prefix as an option. Internally it will see that there is a prefix + // and combine them. + urdfText = urdfText.replace('package://' + match[1], hackThePath); } - let normPath = path.normalize(packagePath); - let vsPath = vscode.Uri.file(normPath); - let newUri = this._webview.webview.asWebviewUri(vsPath); - let hackThePath = newUri.toString().replace('https:', ''); - - // HACKHACK - the RosWebTools will alwayse prefix the paths with a '/' if we don't pass a prefix. - // to workaround this without changing RWT, we are stripping off the known protocol, and passing the - // resulting path into RWT with that known prefix as an option. Internally it will see that there is a prefix - // and combine them. - urdfText = urdfText.replace('package://' + match[1], hackThePath); } var previewFile = this._resource.toString(); - console.log("URDF previewing: " + previewFile); + extension.outputChannel.appendLine("URDF previewing: " + previewFile); this._webview.webview.postMessage({ command: 'previewFile', previewFile: previewFile}); this._webview.webview.postMessage({ command: 'urdf', urdf: urdfText });