Skip to content

Commit

Permalink
Add a setting to specify default repositories (#5547)
Browse files Browse the repository at this point in the history
This change adds a new setting that allows end users to select the
default repository used in R sessions. Prior to the change, we always
used `cran.rstudio.com` if no other repository was set; this remains the
default, but now there are several other choices:

<img width="623" alt="image"
src="https://github.com/user-attachments/assets/7f9b96a8-07db-4d53-add6-38d646653ad3">

For compatibility with shared RStudio configurations, the `auto` setting
also checks for a `repos.conf` file in well-known XDG locations. You can
read more about the expected format of this file here:
https://solutions.posit.co/envs-pkgs/rsw_defaults/#repos.conf

Addresses #5509

> [!NOTE]
>
> This change depends on a change from `ark`
(posit-dev/ark#645) and should not be merged
until that change is.

### QA Notes

- These repositories are _defaults_ and shouldn't override anything
you've already set in e.g. `.Rprofile` or `Rprofile.site`.
- It is not strictly necessary to restart Positron to apply changes to
the new option; you only need to shut down your R session (not restart
it) and create a new one. We say "restart Positron" because it's easier
for people to understand.
- The XDG locations searched include both Positron and RStudio
directories. On macOS, for example, `repos.conf` can be placed in
`~/Library/Preferences/positron/repos.conf`.
  • Loading branch information
jmcphers authored Dec 2, 2024
1 parent 1492e58 commit 58d2069
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 122 deletions.
23 changes: 21 additions & 2 deletions 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 All @@ -667,7 +686,7 @@
},
"positron": {
"binaryDependencies": {
"ark": "0.1.156"
"ark": "0.1.157"
},
"minimumRVersion": "4.2.0",
"minimumRenvVersion": "1.0.9"
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)",
"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
};
/* 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

0 comments on commit 58d2069

Please sign in to comment.