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

Add a setting to specify default repositories #5547

Merged
merged 6 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
21 changes: 20 additions & 1 deletion extensions/positron-r/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,24 @@
"type": "boolean",
"default": false,
"description": "%r.configuration.taskHyperlinks.description%"
},
"positron.r.defaultRepositories": {
"scope": "window",
"type": "string",
"enum": [
"auto",
"rstudio",
"posit-ppm",
"none"
],
"enumDescriptions": [
"%r.configuration.defaultRepositories.auto.description%",
"%r.configuration.defaultRepositories.rstudio.description%",
"%r.configuration.defaultRepositories.posit-ppm.description%",
"%r.configuration.defaultRepositories.none.description%"
],
"default": "auto",
"markdownDescription": "%r.configuration.defaultRepositories.description%"
}
}
}
Expand Down Expand Up @@ -656,7 +674,8 @@
"split2": "^4.2.0",
"vscode-languageclient": "^9.0.1",
"web-tree-sitter": "^0.20.8",
"which": "^3.0.0"
"which": "^3.0.0",
"xdg-portable": "^10.6.0"
},
"peerDependencies": {
"@vscode/windows-registry": "^1.0.0"
Expand Down
7 changes: 6 additions & 1 deletion extensions/positron-r/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,10 @@
"r.configuration.pipe.native.description": "Native pipe available in R >= 4.1",
"r.configuration.pipe.magrittr.description": "Pipe operator from the magrittr package, re-exported by many other packages",
"r.configuration.diagnostics.enable.description": "Enable R diagnostics globally",
"r.configuration.taskHyperlinks.description": "Turn on experimental support for hyperlinks in package development tasks"
"r.configuration.taskHyperlinks.description": "Turn on experimental support for hyperlinks in package development tasks",
"r.configuration.defaultRepositories.description": "The default repositories to use for R package installation, if no repository is otherwise specified in R startup scripts (restart Positron to apply).\n\nThe default repositories will be set as the `repos` option in R.",
"r.configuration.defaultRepositories.auto.description": "Automatically choose a default repository, or use a repos.conf file if it exists.",
"r.configuration.defaultRepositories.rstudio.description": "Use the RStudio CRAN mirror (cran.rstudio.com)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking this chance to have a side discussion:

In my .Rprofile, I have

...
repos = c(
    P3M = "https://p3m.dev/cran/latest",
    CRAN = "https://cloud.r-project.org"
)
...

In terms of what we feature, is there a reason to push cran.rstudio.com over cloud.r-project.org? I guess I've implicitly preferred the latter from some super vague reasons around rstudio->posit rebrand and avoiding overt branding.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we necessarily want to push cran.rstudio.com (Posit also sponsors cloud.r-project.org), I just used it to match ark's existing behavior. Might be worth providing a cloud.r-project.org option too, let's see what folks say.

"r.configuration.defaultRepositories.posit-ppm.description": "Use the Posit public package manager (packagemanager.posit.co)",
"r.configuration.defaultRepositories.none.description": "Do not set a default repository or change the value of the 'repos' option"
}
169 changes: 169 additions & 0 deletions extensions/positron-r/src/kernel-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import * as positron from 'positron';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';

import { JupyterKernelSpec } from './jupyter-adapter';
import { getArkKernelPath } from './kernel';
import { getPandocPath } from './pandoc';
import { EXTENSION_ROOT_DIR } from './constants';

/**
* Create a new Jupyter kernel spec.
*
* @param rHomePath The R_HOME path for the R version
* @param runtimeName The (display) name of the runtime
* @param sessionMode The mode in which to create the session
*
* @returns A JupyterKernelSpec definining the kernel's path, arguments, and
* metadata.
*/
export function createJupyterKernelSpec(
rHomePath: string,
runtimeName: string,
sessionMode: positron.LanguageRuntimeSessionMode): JupyterKernelSpec {

// Path to the kernel executable
const kernelPath = getArkKernelPath();
if (!kernelPath) {
throw new Error('Unable to find R kernel');
}

// Check the R kernel log level setting
const config = vscode.workspace.getConfiguration('positron.r');
const logLevel = config.get<string>('kernel.logLevel') ?? 'warn';
const logLevelForeign = config.get<string>('kernel.logLevelExternal') ?? 'warn';
const userEnv = config.get<object>('kernel.env') ?? {};
const profile = config.get<string>('kernel.profile');


/* eslint-disable */
const env = <Record<string, string>>{
'RUST_BACKTRACE': '1',
'RUST_LOG': logLevelForeign + ',ark=' + logLevel,
'R_HOME': rHomePath,
...userEnv
};
Comment on lines +46 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor observation: we are using a little interface for env vars elsewhere in positron-r (but I don't think this matters that much and I see this code pre-existed, it's just moving in this PR):

export interface EnvVar {
[key: string]: string;
}

export async function prepCliEnvVars(session?: RSession): Promise<EnvVar> {

export async function getEnvVars(envVars: string[], session?: RSession): Promise<EnvVar[]> {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! Indeed I didn't touch this (just moved it to keep the file from becoming too unwieldy) but that'd be an improvement.

/* eslint-enable */

if (profile) {
env['ARK_PROFILE'] = profile;
}

if (process.platform === 'linux') {
// Workaround for
// https://github.com/posit-dev/positron/issues/1619#issuecomment-1971552522
env['LD_LIBRARY_PATH'] = rHomePath + '/lib';
} else if (process.platform === 'darwin') {
// Workaround for
// https://github.com/posit-dev/positron/issues/3732
env['DYLD_LIBRARY_PATH'] = rHomePath + '/lib';
}

// Inject the path to the Pandoc executable into the environment; R packages
// that use Pandoc for rendering will need this.
//
// On MacOS, the binary path lives alongside the app bundle; on other
// platforms, it's a couple of directories up from the app root.
const pandocPath = getPandocPath();
if (pandocPath) {
env['RSTUDIO_PANDOC'] = pandocPath;
}

// R script to run on session startup
const startupFile = path.join(EXTENSION_ROOT_DIR, 'resources', 'scripts', 'startup.R');

const argv = [
kernelPath,
'--connection_file', '{connection_file}',
'--log', '{log_file}',
'--startup-file', `${startupFile}`,
'--session-mode', `${sessionMode}`,
];

// Only create profile if requested in configuration
if (profile) {
argv.push(...[
'--profile', '{profile_file}',
]);
}

// Set the default repositories
const defaultRepos = config.get<string>('defaultRepositories') ?? 'auto';
if (defaultRepos === 'auto') {
const reposConf = findReposConf();
if (reposConf) {
// If there's a `repos.conf` file in a well-known directory, use
// that.
argv.push(...['--repos-conf', reposConf]);
} else if (vscode.env.uiKind === vscode.UIKind.Web) {
// No repos.conf; if we're web mode use Posit's Public Package
// Manager
argv.push(...['--default-repos', 'posit-ppm']);
}
// In all other cases when `auto` is set, we don't specify
// `--default-repos` at all, and let Ark choose an appropriate
// repository (usually `cran.rstudio.com)
} else {
// The remaining options map directly to Ark's `--default-repos`
// command line option
argv.push(...['--default-repos', defaultRepos]);
}

argv.push(...[
// The arguments after `--` are passed verbatim to R
'--',
'--interactive',
]);

// Create a kernel spec for this R installation
const kernelSpec: JupyterKernelSpec = {
'argv': argv,
'display_name': runtimeName, // eslint-disable-line
'language': 'R',
'env': env,
};

// Unless the user has chosen to restore the workspace, pass the
// `--no-restore-data` flag to R.
if (!config.get<boolean>('restoreWorkspace')) {
kernelSpec.argv.push('--no-restore-data');
}

// If the user has supplied extra arguments to R, pass them along.
const extraArgs = config.get<Array<string>>('extraArguments');
const quietMode = config.get<boolean>('quietMode');
if (quietMode && extraArgs?.indexOf('--quiet') === -1) {
extraArgs?.push('--quiet');
}
if (extraArgs) {
kernelSpec.argv.push(...extraArgs);
}

return kernelSpec;
}

/**
* Attempt to find a `repos.conf` file in Positron or RStudio XDG
* configuration directories.
*
* Returns the path to the file if found, or `undefined` if no
*/
function findReposConf(): string | undefined {
const xdg = require('xdg-portable/cjs');
const configDirs: Array<string> = xdg.configDirs();
for (const product of ['rstudio', 'positron']) {
for (const configDir of configDirs) {
const reposConf = path.join(configDir, product, 'repos.conf');
if (fs.existsSync(reposConf)) {
return reposConf;
}
}
}
return;
}
2 changes: 1 addition & 1 deletion extensions/positron-r/src/kernel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2023 Posit Software, PBC. All rights reserved.
* Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

Expand Down
3 changes: 2 additions & 1 deletion extensions/positron-r/src/runtime-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import * as positron from 'positron';
import * as vscode from 'vscode';
import { findCurrentRBinary, makeMetadata, rRuntimeDiscoverer } from './provider';
import { RInstallation, RMetadataExtra } from './r-installation';
import { RSession, createJupyterKernelExtra, createJupyterKernelSpec } from './session';
import { RSession, createJupyterKernelExtra } from './session';
import { createJupyterKernelSpec } from './kernel-spec';

export class RRuntimeManager implements positron.LanguageRuntimeManager {

Expand Down
117 changes: 0 additions & 117 deletions extensions/positron-r/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@

import * as positron from 'positron';
import * as vscode from 'vscode';
import * as path from 'path';
import PQueue from 'p-queue';

import { JupyterAdapterApi, JupyterKernelSpec, JupyterLanguageRuntimeSession, JupyterKernelExtra } from './jupyter-adapter';
import { ArkLsp, LspState } from './lsp';
import { delay, whenTimeout, timeout } from './util';
import { ArkAttachOnStartup, ArkDelayStartup } from './startup';
import { RHtmlWidget, getResourceRoots } from './htmlwidgets';
import { getArkKernelPath } from './kernel';
import { randomUUID } from 'crypto';
import { handleRCode } from './hyperlink';
import { RSessionManager } from './session-manager';
import { EXTENSION_ROOT_DIR } from './constants';
import { getPandocPath } from './pandoc';

interface RPackageInstallation {
packageName: string;
Expand Down Expand Up @@ -793,119 +789,6 @@ export function createJupyterKernelExtra(): JupyterKernelExtra {
};
}

/**
* Create a new Jupyter kernel spec.
*
* @param rHomePath The R_HOME path for the R version
* @param runtimeName The (display) name of the runtime
* @param sessionMode The mode in which to create the session
*
* @returns A JupyterKernelSpec definining the kernel's path, arguments, and
* metadata.
*/
export function createJupyterKernelSpec(
rHomePath: string,
runtimeName: string,
sessionMode: positron.LanguageRuntimeSessionMode): JupyterKernelSpec {

// Path to the kernel executable
const kernelPath = getArkKernelPath();
if (!kernelPath) {
throw new Error('Unable to find R kernel');
}

// Check the R kernel log level setting
const config = vscode.workspace.getConfiguration('positron.r');
const logLevel = config.get<string>('kernel.logLevel') ?? 'warn';
const logLevelForeign = config.get<string>('kernel.logLevelExternal') ?? 'warn';
const userEnv = config.get<object>('kernel.env') ?? {};
const profile = config.get<string>('kernel.profile');


/* eslint-disable */
const env = <Record<string, string>>{
'RUST_BACKTRACE': '1',
'RUST_LOG': logLevelForeign + ',ark=' + logLevel,
'R_HOME': rHomePath,
...userEnv
};
/* eslint-enable */

if (profile) {
env['ARK_PROFILE'] = profile;
}

if (process.platform === 'linux') {
// Workaround for
// https://github.com/posit-dev/positron/issues/1619#issuecomment-1971552522
env['LD_LIBRARY_PATH'] = rHomePath + '/lib';
} else if (process.platform === 'darwin') {
// Workaround for
// https://github.com/posit-dev/positron/issues/3732
env['DYLD_LIBRARY_PATH'] = rHomePath + '/lib';
}

// Inject the path to the Pandoc executable into the environment; R packages
// that use Pandoc for rendering will need this.
//
// On MacOS, the binary path lives alongside the app bundle; on other
// platforms, it's a couple of directories up from the app root.
const pandocPath = getPandocPath();
if (pandocPath) {
env['RSTUDIO_PANDOC'] = pandocPath;
}

// R script to run on session startup
const startupFile = path.join(EXTENSION_ROOT_DIR, 'resources', 'scripts', 'startup.R');

const argv = [
kernelPath,
'--connection_file', '{connection_file}',
'--log', '{log_file}',
'--startup-file', `${startupFile}`,
'--session-mode', `${sessionMode}`,
];

// Only create profile if requested in configuration
if (profile) {
argv.push(...[
'--profile', '{profile_file}',
]);
}

argv.push(...[
// The arguments after `--` are passed verbatim to R
'--',
'--interactive',
]);

// Create a kernel spec for this R installation
const kernelSpec: JupyterKernelSpec = {
'argv': argv,
'display_name': runtimeName, // eslint-disable-line
'language': 'R',
'env': env,
};

// Unless the user has chosen to restore the workspace, pass the
// `--no-restore-data` flag to R.
if (!config.get<boolean>('restoreWorkspace')) {
kernelSpec.argv.push('--no-restore-data');
}

// If the user has supplied extra arguments to R, pass them along.
const extraArgs = config.get<Array<string>>('extraArguments');
const quietMode = config.get<boolean>('quietMode');
if (quietMode && extraArgs?.indexOf('--quiet') === -1) {
extraArgs?.push('--quiet');
}
if (extraArgs) {
kernelSpec.argv.push(...extraArgs);
}

return kernelSpec;
}

export async function checkInstalled(pkgName: string,
pkgVersion?: string,
session?: RSession): Promise<boolean> {
Expand Down
Loading
Loading