Skip to content

feat: package manager permissions when using API #218

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

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
28 changes: 27 additions & 1 deletion build/azure-pipeline.pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,32 @@ extends:
chmod +x $(Build.SourcesDirectory)/python-env-tools/bin
displayName: Make Directory for python-env-tool binary

- bash: |
if [ "$(vsceTarget)" == "win32-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-pc-windows-msvc"
elif [ "$(vsceTarget)" == "win32-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-pc-windows-msvc"
elif [ "$(vsceTarget)" == "linux-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
elif [ "$(vsceTarget)" == "linux-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu"
elif [ "$(vsceTarget)" == "linux-armhf" ]; then
echo "##vso[task.setvariable variable=buildTarget]armv7-unknown-linux-gnueabihf"
elif [ "$(vsceTarget)" == "darwin-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-apple-darwin"
elif [ "$(vsceTarget)" == "darwin-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-apple-darwin"
elif [ "$(vsceTarget)" == "alpine-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
elif [ "$(vsceTarget)" == "alpine-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu"
elif [ "$(vsceTarget)" == "web" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
else
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
fi
displayName: Set buildTarget variable

- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
Expand All @@ -98,7 +124,7 @@ extends:
buildVersionToDownload: 'latest'
branchName: 'refs/heads/main'
targetPath: '$(Build.SourcesDirectory)/python-env-tools/bin'
artifactName: 'bin-$(vsceTarget)'
artifactName: 'bin-$(buildTarget)'
itemPattern: |
pet.exe
pet
Expand Down
28 changes: 27 additions & 1 deletion build/azure-pipeline.stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,32 @@ extends:
chmod +x $(Build.SourcesDirectory)/python-env-tools/bin
displayName: Make Directory for python-env-tool binary

- bash: |
if [ "$(vsceTarget)" == "win32-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-pc-windows-msvc"
elif [ "$(vsceTarget)" == "win32-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-pc-windows-msvc"
elif [ "$(vsceTarget)" == "linux-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
elif [ "$(vsceTarget)" == "linux-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu"
elif [ "$(vsceTarget)" == "linux-armhf" ]; then
echo "##vso[task.setvariable variable=buildTarget]armv7-unknown-linux-gnueabihf"
elif [ "$(vsceTarget)" == "darwin-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-apple-darwin"
elif [ "$(vsceTarget)" == "darwin-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-apple-darwin"
elif [ "$(vsceTarget)" == "alpine-x64" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
elif [ "$(vsceTarget)" == "alpine-arm64" ]; then
echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu"
elif [ "$(vsceTarget)" == "web" ]; then
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
else
echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl"
fi
displayName: Set buildTarget variable

- task: DownloadPipelineArtifact@2
inputs:
buildType: 'specific'
Expand All @@ -88,7 +114,7 @@ extends:
buildVersionToDownload: 'latestFromBranch'
branchName: 'refs/heads/release/2024.18'
targetPath: '$(Build.SourcesDirectory)/python-env-tools/bin'
artifactName: 'bin-$(vsceTarget)'
artifactName: 'bin-$(buildTarget)'
itemPattern: |
pet.exe
pet
Expand Down
13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@
"title": "%python-envs.copyProjectPath.title%",
"category": "Python Envs",
"icon": "$(copy)"
},
{
"command": "python-envs.permissions",
"title": "%python-envs.permissions.title%",
"category": "Python Envs",
"icon": "$(shield)"
},
{
"command": "python-envs.resetPermissions",
"title": "%python-envs.resetPermissions.title%",
"category": "Python Envs",
"icon": "$(sync)"
}
],
"menus": {
Expand Down Expand Up @@ -418,9 +430,17 @@
},
{
"command": "python-envs.refreshAllManagers",
"when": "view == env-managers"
},
{
"command": "python-envs.permissions",
"group": "navigation",
"when": "view == env-managers"
},
{
"command": "python-envs.resetPermissions",
"when": "view == env-managers"
},
{
"command": "python-envs.terminal.activate",
"group": "navigation",
Expand Down
4 changes: 3 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@
"python-envs.runAsTask.title": "Run as Task",
"python-envs.terminal.activate.title": "Activate Environment in Current Terminal",
"python-envs.terminal.deactivate.title": "Deactivate Environment in Current Terminal",
"python-envs.uninstallPackage.title": "Uninstall Package"
"python-envs.uninstallPackage.title": "Uninstall Package",
"python-envs.permissions.title": "Package Manager Permissions",
"python-envs.resetPermissions.title": "Reset Package Manager Permissions"
}
7 changes: 6 additions & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,11 @@ export interface PackageInstallOptions {
* Upgrade the packages if it is already installed.
*/
upgrade?: boolean;

/**
* Show option to skip package installation
*/
showSkipOption?: boolean;
}

export interface PythonProcess {
Expand Down Expand Up @@ -918,7 +923,7 @@ export interface PythonPackageManagementApi {
* @param environment The Python Environment from which packages are to be uninstalled.
* @param packages The packages to uninstall.
*/
uninstallPackages(environment: PythonEnvironment, packages: PackageInfo[] | string[]): Promise<void>;
uninstallPackages(environment: PythonEnvironment, packages: string[]): Promise<void>;
}

export interface PythonPackageManagerApi
Expand Down
5 changes: 5 additions & 0 deletions src/common/command.api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { commands } from 'vscode';
import { Disposable } from 'vscode-jsonrpc';

export function executeCommand<T = unknown>(command: string, ...rest: any[]): Thenable<T> {
return commands.executeCommand(command, ...rest);
}

export function registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable {
return commands.registerCommand(command, callback, thisArg);
}
11 changes: 11 additions & 0 deletions src/common/extension.apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@ export function getExtension<T = any>(extensionId: string): Extension<T> | undef
export function allExtensions(): readonly Extension<any>[] {
return extensions.all;
}

export function allExternalExtensions(): readonly Extension<any>[] {
return allExtensions().filter((extension) => {
try {
return extension.packageJSON.publisher !== 'vscode';
} catch {
// No publisher
return false;
}
});
}
7 changes: 7 additions & 0 deletions src/common/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,10 @@ export namespace EnvViewStrings {
export const selectedGlobalTooltip = l10n.t('This environment is selected for non-workspace files');
export const selectedWorkspaceTooltip = l10n.t('This environment is selected for workspace files');
}

export namespace PermissionsCommon {
export const allow = l10n.t('Allow');
export const deny = l10n.t('Deny');
export const ask = l10n.t('Ask');
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure "Ask" is easily understandable in this context. I wonder if we should do something like "Always Ask" or "Confirm each time" at the tradeoff of having a slightly longer button title

Copy link
Member

Choose a reason for hiding this comment

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

agreed- I also wanted this button to be explicit it was a long-term choice

export const setPermissions = l10n.t('Set Permissions');
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export const setPermissions = l10n.t('Set Permissions');
export const setPermissions = l10n.t('Update Permissions');

}
66 changes: 46 additions & 20 deletions src/common/utils/frameUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Uri } from 'vscode';
import { ENVS_EXTENSION_ID, PYTHON_EXTENSION_ID } from '../constants';
import { parseStack } from '../errors/utils';
import { allExtensions, getExtension } from '../extension.apis';

import { normalizePath } from './pathUtils';
interface FrameData {
filePath: string;
functionName: string;
Expand All @@ -15,38 +16,63 @@ function getFrameData(): FrameData[] {
}));
}

function getPathFromFrame(frame: FrameData): string {
if (frame.filePath && frame.filePath.startsWith('file://')) {
return Uri.parse(frame.filePath).fsPath;
}
return frame.filePath;
}

export function getCallingExtension(): string {
const pythonExts = [ENVS_EXTENSION_ID, PYTHON_EXTENSION_ID];

const extensions = allExtensions();
const otherExts = extensions.filter((ext) => !pythonExts.includes(ext.id));
const frames = getFrameData().filter((frame) => !!frame.filePath);
const frames = getFrameData();
const filePaths: string[] = [];

for (const frame of frames) {
const filename = frame.filePath;
if (filename) {
const ext = otherExts.find((ext) => filename.includes(ext.id));
if (ext) {
return ext.id;
}
if (!frame || !frame.filePath) {
continue;
}
const filePath = normalizePath(getPathFromFrame(frame));
if (!filePath) {
continue;
}

if (filePath.toLowerCase().endsWith('extensionhostprocess.js')) {
continue;
}

if (filePath.startsWith('node:')) {
continue;
}

filePaths.push(filePath);

const ext = otherExts.find((ext) => filePath.includes(ext.id));
if (ext) {
return ext.id;
}
}

// `ms-python.vscode-python-envs` extension in Development mode
const candidates = frames.filter((frame) => otherExts.some((s) => frame.filePath.includes(s.extensionPath)));
const envsExtPath = getExtension(ENVS_EXTENSION_ID)?.extensionPath;
if (!envsExtPath) {
const candidates = filePaths.filter((filePath) =>
otherExts.some((s) => filePath.includes(normalizePath(s.extensionPath))),
);
const envExt = getExtension(ENVS_EXTENSION_ID);

if (!envExt) {
throw new Error('Something went wrong with feature registration');
}

if (candidates.length === 0 && frames.every((frame) => frame.filePath.startsWith(envsExtPath))) {
const envsExtPath = normalizePath(envExt.extensionPath);
if (candidates.length === 0 && filePaths.every((filePath) => filePath.startsWith(envsExtPath))) {
return PYTHON_EXTENSION_ID;
}

// 3rd party extension in Development mode
const candidateExt = otherExts.find((ext) => candidates[0].filePath.includes(ext.extensionPath));
if (candidateExt) {
return candidateExt.id;
} else if (candidates.length > 0) {
// 3rd party extension in Development mode
const candidateExt = otherExts.find((ext) => candidates[0].includes(ext.extensionPath));
if (candidateExt) {
return candidateExt.id;
}
}

throw new Error('Unable to determine calling extension id, registration failed');
Expand Down
15 changes: 7 additions & 8 deletions src/common/utils/pathUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as path from 'path';
import { isWindows } from '../../managers/common/utils';

export function areSamePaths(a: string, b: string): boolean {
return path.resolve(a) === path.resolve(b);
}

export function isParentPath(parent: string, child: string): boolean {
const relative = path.relative(path.resolve(parent), path.resolve(child));
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
export function normalizePath(path: string): string {
const path1 = path.replace(/\\/g, '/');
if (isWindows()) {
return path1.toLowerCase();
}
return path1;
}
32 changes: 31 additions & 1 deletion src/common/window.apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
InputBox,
InputBoxOptions,
LogOutputChannel,
MessageItem,
MessageOptions,
OpenDialogOptions,
OutputChannel,
Progress,
Expand Down Expand Up @@ -284,10 +286,38 @@ export async function showInputBoxWithButtons(
}
}

export function showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
export function showWarningMessage<T extends string>(message: string, ...items: T[]): Thenable<T | undefined>;
export function showWarningMessage<T extends string>(
message: string,
options: MessageOptions,
...items: T[]
): Thenable<T | undefined>;
export function showWarningMessage<T extends MessageItem>(message: string, ...items: T[]): Thenable<T | undefined>;
export function showWarningMessage<T extends MessageItem>(
message: string,
options: MessageOptions,
...items: T[]
): Thenable<T | undefined>;
export function showWarningMessage(message: string, ...items: any[]): Thenable<string | undefined> {
return window.showWarningMessage(message, ...items);
}

export function showInformationMessage<T extends string>(message: string, ...items: T[]): Thenable<T | undefined>;
export function showInformationMessage<T extends string>(
message: string,
options: MessageOptions,
...items: T[]
): Thenable<T | undefined>;
export function showInformationMessage<T extends MessageItem>(message: string, ...items: T[]): Thenable<T | undefined>;
export function showInformationMessage<T extends MessageItem>(
message: string,
options: MessageOptions,
...items: T[]
): Thenable<T | undefined>;
export function showInformationMessage(message: string, ...items: any[]): Thenable<string | undefined> {
return window.showInformationMessage(message, ...items);
}

export function showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable<string | undefined> {
return window.showInputBox(options, token);
}
Expand Down
Loading