Skip to content
Closed
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