Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R executable selection #1143

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@
}
],
"commands": [
{
"category": "R",
"command": "r.setExecutable",
"title": "Select executable"
},
{
"command": "r.workspaceViewer.refreshEntry",
"title": "Manual Refresh",
Expand Down Expand Up @@ -1317,35 +1322,53 @@
"type": "object",
"title": "R",
"properties": {
"r.executable.virtual.activateEnvironment": {
"type": "boolean",
"default": true,
"description": "Whether to automatically activate a conda environment containing a selected executable. *Recommended to keep enabled*",
"scope": "machine-overridable"
},
"r.executable.virtual.condaPath": {
"type": "string",
"default": "",
"description": "Path to conda activation script. This will be `conda.sh` on unix or the `Activate` file on windows.",
"scope": "machine-overridable"
},
"r.rpath.windows": {
"type": "string",
"default": "",
"description": "Path to an R executable to launch R background processes (Windows). Must be \"vanilla\" R, not radian etc.!"
"description": "Path to an R executable to launch R background processes (Windows). Must be \"vanilla\" R, not radian etc.!",
"scope": "machine-overridable"
},
"r.rpath.mac": {
"type": "string",
"default": "",
"description": "Path to an R executable to launch R background processes (macOS). Must be \"vanilla\" R, not radian etc.!"
"description": "Path to an R executable to launch R background processes (macOS). Must be \"vanilla\" R, not radian etc.!",
"scope": "machine-overridable"
},
"r.rpath.linux": {
"type": "string",
"default": "",
"description": "Path to an R executable to launch R background processes (Linux). Must be \"vanilla\" R, not radian etc.!"
"description": "Path to an R executable to launch R background processes (Linux). Must be \"vanilla\" R, not radian etc.!",
"scope": "machine-overridable"
},
"r.rterm.windows": {
"type": "string",
"default": "",
"description": "R path for interactive terminals (Windows). Can also be radian etc."
"description": "R path for interactive terminals (Windows). Can also be radian etc.",
"scope": "machine-overridable"
},
"r.rterm.mac": {
"type": "string",
"default": "",
"description": "R path for interactive terminals (macOS). Can also be radian etc."
"description": "R path for interactive terminals (macOS). Can also be radian etc.",
"scope": "machine-overridable"
},
"r.rterm.linux": {
"type": "string",
"default": "",
"description": "R path for interactive terminals (Linux). Can also be radian etc."
"description": "R path for interactive terminals (Linux). Can also be radian etc.",
"scope": "machine-overridable"
},
"r.rterm.option": {
"type": "array",
Expand Down Expand Up @@ -2044,4 +2067,4 @@
"vsls": "^1.0.4753",
"winreg": "^1.2.4"
}
}
}
2 changes: 1 addition & 1 deletion src/cppProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function platformChoose<A, B, C>(win32: A, darwin: B, other: C): A | B | C {

// See: https://code.visualstudio.com/docs/cpp/c-cpp-properties-schema-reference
async function generateCppPropertiesProc(workspaceFolder: string) {
const rPath = await getRpath();
const rPath = getRpath();
if (!rPath) {
return;
}
Expand Down
147 changes: 147 additions & 0 deletions src/executables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import path = require('path');
import * as fs from 'fs-extra';
import * as vscode from 'vscode';
import * as cp from 'child_process';

import { ExecutableStatusItem, ExecutableQuickPick } from './ui';
import { isVirtual, RExecutableService, RExecutableType, WorkspaceExecutableEvent } from './service';
import { extensionContext } from '../extension';
import { activateCondaEnvironment, condaPrefixPath } from './virtual';

// super class that manages relevant sub classes
export class RExecutableManager {
private readonly executableService: RExecutableService;
private statusBar: ExecutableStatusItem;
private quickPick: ExecutableQuickPick;

private constructor(service: RExecutableService) {
this.executableService = service;
this.statusBar = new ExecutableStatusItem(this.executableService);
this.quickPick = new ExecutableQuickPick(this.executableService);
extensionContext.subscriptions.push(
this.onDidChangeActiveExecutable(() => {
this.reload();
}),
vscode.window.onDidChangeActiveTextEditor((e: vscode.TextEditor | undefined) => {
if (e?.document) {
this.reload();
}
}),
this.executableService,
this.statusBar
);
this.reload();
}

static async initialize(): Promise<RExecutableManager> {
const executableService = await RExecutableService.initialize();
return new this(executableService);
}

public get executableQuickPick(): ExecutableQuickPick {
return this.quickPick;
}

public get languageStatusItem(): ExecutableStatusItem {
return this.statusBar;
}

public get activeExecutablePath(): string | undefined {
return this.executableService.activeExecutable?.rBin;
}

/**
* Get the associated R executable for a given working directory path
* @param workingDir
* @returns
*/
public getExecutablePath(workingDir: string): string | undefined {
return this.executableService.getWorkspaceExecutable(workingDir)?.rBin;
}

public get activeExecutable(): RExecutableType | undefined {
return this.executableService.activeExecutable;
}

public get onDidChangeActiveExecutable(): vscode.Event<RExecutableType | undefined> {
return this.executableService.onDidChangeActiveExecutable;
}

public get onDidChangeWorkspaceExecutable(): vscode.Event<WorkspaceExecutableEvent> {
return this.executableService.onDidChangeWorkspaceExecutable;
}

/**
* @description
* Orders a refresh of the executable manager, causing a refresh of the language status bar item and
* activates a conda environment if present.
* @memberof RExecutableManager
*/
public reload(): void {
this.statusBar.refresh();
const loading = this.activateEnvironment();
void this.statusBar.makeBusy(loading);
}

/**
* Activates a Conda environment, but only if the currently active executable is virtual
* and has no obtained environmental variable. If determined that activation is not necessary,
* a resolved promise will be returned.
*/
private async activateEnvironment(): Promise<void> {
if (!this.activeExecutable ||
!isVirtual(this.activeExecutable) ||
!!this.activeExecutable.envVar
) {
return Promise.resolve();
}
await activateCondaEnvironment(this.activeExecutable);
}

}

/**
* Is the folder of a given executable a valid R installation?
*
* A path is valid if the folder contains the R executable and an Rcmd file.
* @param execPath
* @returns boolean
*/
export function validateRExecutablePath(execPath: string): boolean {
try {
const basename = process.platform === 'win32' ? 'R.exe' : 'R';
fs.accessSync(execPath, fs.constants.X_OK && fs.constants.R_OK);
return (path.basename(execPath) === basename);
} catch (error) {
return false;
}
}


/**
* @description
* Takes an options object, and modifies the env values to allow for the injection
* of conda env values, and modify R binary paths for various rterms (e.g. radian)
* @export
* @template T
* @param {T} opts
* @param {RExecutableType} executable
* @returns {*} {T}
*/
export function modifyEnvVars<T extends vscode.TerminalOptions | cp.CommonOptions>(opts: T, executable: RExecutableType): T {
const envVars: Record<string, string> = {
R_BINARY: executable.rBin
};
const pathEnv: string = (opts?.env as Record<string, string>)?.PATH ?? process.env?.PATH;
if (isVirtual(executable) && executable.envVar) {
pathEnv ?
envVars['PATH'] = `${executable.envVar}:${pathEnv}`
:
envVars['PATH'] = executable.envVar;
envVars['CONDA_PREFIX'] = condaPrefixPath(executable.rBin);
envVars['CONDA_DEFAULT_ENV'] = executable.name ?? 'base';
envVars['CONDA_PROMPT_MODIFIER'] = `(${envVars['CONDA_DEFAULT_ENV']})`;
}
opts['env'] = envVars;
return opts;
}
98 changes: 98 additions & 0 deletions src/executables/service/class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { condaName, getRDetailsFromMetaHistory, isCondaInstallation } from '../virtual';
import { getRDetailsFromPath } from './locator';
import { RExecutableRegistry } from './registry';
import { RExecutableType } from './types';

export function isVirtual(executable: AbstractRExecutable): executable is VirtualRExecutable {
return executable instanceof VirtualRExecutable;
}

/**
* Creates and caches instances of RExecutableType
* based on the provided executable path.
*/
export class RExecutableFactory {
private readonly registry: RExecutableRegistry;

constructor(registry: RExecutableRegistry) {
this.registry = registry;
}

public create(executablePath: string): RExecutableType {
const cachedExec = [...this.registry.executables.values()].find((v) => v.rBin === executablePath);
if (cachedExec) {
return cachedExec;
} else {
let executable: AbstractRExecutable;
if (isCondaInstallation(executablePath)) {
executable = new VirtualRExecutable(executablePath);
} else {
executable = new RExecutable(executablePath);
}
this.registry.addExecutable(executable);
return executable;
}
}
}

export abstract class AbstractRExecutable {
protected _rBin!: string;
protected _rVersion!: string;
protected _rArch!: string;
public get rBin(): string {
return this._rBin;
}

public get rVersion(): string {
return this._rVersion;
}

public get rArch(): string {
return this._rArch;
}
public abstract tooltip: string;
}


export class RExecutable extends AbstractRExecutable {
constructor(executablePath: string) {
super();
const details = getRDetailsFromPath(executablePath);
this._rBin = executablePath;
this._rVersion = details.version;
this._rArch = details.arch;
}

public get tooltip(): string {
if (this.rVersion && this.rArch) {
return `R ${this.rVersion} ${this.rArch}`;
}
return `$(error) R`;
}
}

export class VirtualRExecutable extends AbstractRExecutable {
private _name: string;
public envVar!: string;

constructor(executablePath: string) {
super();
this._name = condaName(executablePath);
const details = getRDetailsFromMetaHistory(executablePath);
this._rVersion = details?.version ?? '';
this._rArch = details?.arch ?? '';
this._rBin = executablePath;
}

public get name(): string {
return this._name;
}

public get tooltip(): string {
if (this.rVersion && this.rArch) {
return `R ${this.rVersion} ${this.rArch} ('${this.name}')`;
}
return `$(error) '${this.name}'`;
}

}
Loading