Skip to content

Commit

Permalink
Merge pull request #2126 from posit-dev/mm-r-in-positron
Browse files Browse the repository at this point in the history
  • Loading branch information
sagerb authored Dec 6, 2024
2 parents 020d962 + 5382977 commit 772a4a9
Show file tree
Hide file tree
Showing 25 changed files with 280 additions and 92 deletions.
4 changes: 3 additions & 1 deletion cmd/publisher/commands/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ func (cmd *DeployCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContext)
stateStore.Account.Name,
stateStore.ConfigName,
stateStore.SaveName)
publisher, err := publish.NewFromState(stateStore, events.NewCliEmitter(os.Stderr, ctx.Logger), ctx.Logger)

rExecutable := util.Path{}
publisher, err := publish.NewFromState(stateStore, rExecutable, events.NewCliEmitter(os.Stderr, ctx.Logger), ctx.Logger)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/publisher/commands/redeploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ func (cmd *RedeployCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContex
stateStore.Account.Name,
stateStore.ConfigName)

publisher, err := publish.NewFromState(stateStore, events.NewCliEmitter(os.Stderr, ctx.Logger), ctx.Logger)
rExecutable := util.Path{}
publisher, err := publish.NewFromState(stateStore, rExecutable, events.NewCliEmitter(os.Stderr, ctx.Logger), ctx.Logger)
if err != nil {
return err
}
Expand Down
74 changes: 74 additions & 0 deletions extensions/vscode/src/@types/positron.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (C) 2024 by Posit Software, PBC.

// This is the portion of the Positron API definition
// used by Publisher, here until it is published.

declare module "positron" {
export interface PositronApi {
version: string;
runtime: runtime;
}

/**
* LanguageRuntimeMetadata contains information about a language runtime that is known
* before the runtime is started.
*/
export interface LanguageRuntimeMetadata {
/** The path to the runtime. */
runtimePath: string;

/** A unique identifier for this runtime; takes the form of a GUID */
runtimeId: string;

/**
* The fully qualified name of the runtime displayed to the user; e.g. "R 4.2 (64-bit)".
* Should be unique across languages.
*/
runtimeName: string;

/**
* A language specific runtime name displayed to the user; e.g. "4.2 (64-bit)".
* Should be unique within a single language.
*/
runtimeShortName: string;

/** The version of the runtime itself (e.g. kernel or extension version) as a string; e.g. "0.1" */
runtimeVersion: string;

/** The runtime's source or origin; e.g. PyEnv, System, Homebrew, Conda, etc. */
runtimeSource: string;

/** The free-form, user-friendly name of the language this runtime can execute; e.g. "R" */
languageName: string;

/**
* The Visual Studio Code Language ID of the language this runtime can execute; e.g. "r"
*
* See here for a list of known language IDs:
* https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
*/
languageId: string;

/** The version of the language; e.g. "4.2" */
languageVersion: string;

/** The Base64-encoded icon SVG for the language. */
base64EncodedIconSvg: string | undefined;

/** Whether the runtime should start up automatically or wait until explicitly requested */
// startupBehavior: LanguageRuntimeStartupBehavior;

/** Where sessions will be located; used as a hint to control session restoration */
// sessionLocation: LanguageRuntimeSessionLocation;

/**
* Extra data supplied by the runtime provider; not read by Positron but supplied
* when creating a new session from the metadata.
*/
extraRuntimeData: any;
}

export interface runtime {
getPreferredRuntime(languageId: string): Thenable<LanguageRuntimeMetadata>;
}
}
2 changes: 2 additions & 0 deletions extensions/vscode/src/api/resources/Configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,14 @@ export class Configurations {
inspect(
dir: string,
python?: string,
r?: string,
params?: { entrypoint?: string; recursive?: boolean },
) {
return this.client.post<ConfigurationInspectionResult[]>(
"/inspect",
{
python,
r,
},
{
params: {
Expand Down
2 changes: 2 additions & 0 deletions extensions/vscode/src/api/resources/ContentRecords.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ export class ContentRecords {
insecure: boolean,
dir: string,
secrets?: Record<string, string>,
r?: string,
) {
const data = {
account: accountName,
config: configName,
secrets: secrets,
insecure: insecure,
r: r,
};
const encodedTarget = encodeURIComponent(targetName);
return this.client.post<{ localId: string }>(
Expand Down
4 changes: 2 additions & 2 deletions extensions/vscode/src/api/resources/Packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ export class Packages {
// 200 - success
// 400 - bad request
// 500 - internal server error
createRRequirementsFile(dir: string, saveName?: string) {
createRRequirementsFile(dir: string, saveName?: string, r?: string) {
return this.client.post<void>(
"packages/r/scan",
{ saveName },
{ saveName, r },
{ params: { dir } },
);
}
Expand Down
8 changes: 6 additions & 2 deletions extensions/vscode/src/entrypointTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { Utils as uriUtils } from "vscode-uri";

import { useApi } from "src/api";
import { Contexts } from "src/constants";
import { getPythonInterpreterPath } from "src/utils/config";
import {
getPythonInterpreterPath,
getRInterpreterPath,
} from "src/utils/vscode";
import { isActiveDocument, relativeDir } from "src/utils/files";
import { hasKnownContentType } from "src/utils/inspect";
import { getSummaryStringFromError } from "src/utils/errors";
Expand Down Expand Up @@ -43,8 +46,9 @@ async function isDocumentEntrypoint(
try {
const api = await useApi();
const python = await getPythonInterpreterPath();
const r = await getRInterpreterPath();

const response = await api.configurations.inspect(dir, python, {
const response = await api.configurations.inspect(dir, python, r, {
entrypoint: uriUtils.basename(document.uri),
});

Expand Down
7 changes: 6 additions & 1 deletion extensions/vscode/src/multiStepInputs/newDeployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import {
ContentType,
FileAction,
} from "src/api";
import { getPythonInterpreterPath } from "src/utils/config";
import {
getPythonInterpreterPath,
getRInterpreterPath,
} from "src/utils/vscode";
import {
getMessageFromError,
getSummaryStringFromError,
Expand Down Expand Up @@ -121,12 +124,14 @@ export async function newDeployment(

try {
const python = await getPythonInterpreterPath();
const r = await getRInterpreterPath();
const relEntryPointDir = path.dirname(relEntryPoint);
const relEntryPointFile = path.basename(relEntryPoint);

const inspectResponse = await api.configurations.inspect(
relEntryPointDir,
python,
r,
{
entrypoint: relEntryPointFile,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
isConfigurationError,
useApi,
} from "src/api";
import { getPythonInterpreterPath } from "src/utils/config";
import {
getPythonInterpreterPath,
getRInterpreterPath,
} from "src/utils/vscode";
import { getSummaryStringFromError } from "src/utils/errors";
import {
MultiStepInput,
Expand Down Expand Up @@ -175,9 +178,12 @@ export async function selectNewOrExistingConfig(
const getConfigurationInspections = async () => {
try {
const python = await getPythonInterpreterPath();
const r = await getRInterpreterPath();

const inspectResponse = await api.configurations.inspect(
activeDeployment.projectDir,
python,
r,
);
inspectionResults = filterInspectionResultsToType(
inspectResponse.data,
Expand Down
44 changes: 0 additions & 44 deletions extensions/vscode/src/utils/config.ts

This file was deleted.

4 changes: 4 additions & 0 deletions extensions/vscode/src/utils/throttle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ export const throttleWithLastPending = async (
}
}
};

export function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
109 changes: 109 additions & 0 deletions extensions/vscode/src/utils/vscode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (C) 2024 by Posit Software, PBC.

import { Uri, commands, workspace } from "vscode";
import { fileExists, isDir } from "./files";
import { delay } from "./throttle";
import { substituteVariables } from "./variables";
import { LanguageRuntimeMetadata, PositronApi } from "positron";

export async function getPythonInterpreterPath(): Promise<string | undefined> {
const workspaceFolder = workspace.workspaceFolders?.[0];
if (workspaceFolder === undefined) {
return undefined;
}
let configuredPython: string | undefined;
try {
configuredPython = await commands.executeCommand<string>(
"python.interpreterPath",
{ workspaceFolder: workspaceFolder },
);
} catch (error: unknown) {
console.error(
"getPythonInterpreterPath was unable to execute command. Error = ",
error,
);
}
if (configuredPython === undefined) {
return undefined;
}
let python = substituteVariables(configuredPython, true);
const pythonUri = Uri.file(python);

if (await isDir(pythonUri)) {
// Configured python can be a directory such as a virtual environment.
const names = [
"bin/python",
"bin/python3",
"Scripts/python.exe",
"Scripts/python3.exe",
];
for (const name of names) {
const candidate = Uri.joinPath(pythonUri, name);
if (await fileExists(candidate)) {
python = candidate.fsPath;
}
}
}
console.log("Python interpreter path:", python);
return python;
}

declare global {
function acquirePositronApi(): PositronApi;
}

let positronApi: PositronApi | null | undefined;

function getPositronApi(): PositronApi | null {
if (positronApi === undefined) {
try {
positronApi = acquirePositronApi();
} catch {
positronApi = null;
}
}
return positronApi;
}

export async function getRInterpreterPath(): Promise<string | undefined> {
const api = getPositronApi();

if (api) {
let runtime: LanguageRuntimeMetadata | undefined;

// Small number of retries, because getPreferredRuntime
// has its own internal retry logic.
const retries = 3;
const retryInterval = 1000;

for (let i = 0; i < retries + 1; i++) {
try {
runtime = await api.runtime.getPreferredRuntime("r");
break;
} catch (error: unknown) {
// Delay and retry
console.error(
"getPreferredRuntime returned an error; retrying. ",
error,
);
await delay(retryInterval);
}
}

if (runtime) {
const interpreter = runtime.runtimePath;
console.log("Using selected R interpreter", interpreter);
return interpreter;
} else {
console.log(
"Using default R interpreter because getPreferredRuntime did not return one",
);
}
}
// We don't know the interpreter path.
// The backend will run R from PATH.
console.log(
"Using default R interpreter because the Positron API is not available",
);
return undefined;
}
Loading

0 comments on commit 772a4a9

Please sign in to comment.