Skip to content

Commit 3bf51ac

Browse files
committed
qt-python: Introduce a new command for PySide6 installation
This commit adds the `qt-python.installPySide6` command. It first checks the installation status of PySide6 and then shows a picker for the user to select which version they would like to install. The first item allows installation from PyPI. Picker can also include local installation sources under `<Qt installation root>/QtForPython`. This UI construction is similar to that of Qt Creator. If no virtual environment is selected, a popup will appear guiding the user to create or select a virtual environment first.
1 parent 95cc2de commit 3bf51ac

File tree

12 files changed

+590
-16
lines changed

12 files changed

+590
-16
lines changed

qt-python/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"title": "Qt Python Configuration",
4141
"properties": {}
4242
},
43+
"commands": [
44+
{
45+
"command": "qt-python.installPySide6",
46+
"title": "%qt-python.command.installPySide6.title%",
47+
"category": "Qt-Python"
48+
}
49+
],
50+
"menus": {
51+
"commandPalette": [
52+
{
53+
"command": "qt-python.installPySide6",
54+
"when": "workspaceFolderCount > 0"
55+
}
56+
]
57+
},
4358
"taskDefinitions": [
4459
{
4560
"type": "pyside",

qt-python/package.nls.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{}
1+
{
2+
"qt-python.command.installPySide6.title": "Install PySide6"
3+
}

qt-python/src/builder.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (C) 2025 The Qt Company Ltd.
2+
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3+
4+
import * as path from 'path';
5+
import * as childProcess from 'child_process';
6+
7+
import { IsWindows } from 'qt-lib';
8+
import { PySideEnv } from './env';
9+
10+
export interface PySideCommandBuildOptions {
11+
useVenv?: boolean;
12+
cwd?: string;
13+
}
14+
15+
export class PySideCommandBuilder {
16+
private readonly _shellPath: string;
17+
private readonly _shellArgs: string[];
18+
private readonly _venvActivationCommand: string;
19+
20+
constructor(
21+
private readonly _env: PySideEnv,
22+
private readonly _options?: PySideCommandBuildOptions
23+
) {
24+
this._shellPath = resolveShellPath();
25+
this._shellArgs = [IsWindows ? '/c' : '-c'];
26+
this._venvActivationCommand = resolveVenvActivationCommand(this._env);
27+
}
28+
29+
get shellPath(): string {
30+
return this._shellPath;
31+
}
32+
33+
get shellArgs(): string[] {
34+
return this._shellArgs;
35+
}
36+
37+
get venvActivationCommand(): string {
38+
return this._venvActivationCommand;
39+
}
40+
41+
public build(command: string) {
42+
const all: string[] = [command];
43+
44+
if (this._options?.cwd) {
45+
all.unshift(`cd ${enclosePath(this._options.cwd)}`);
46+
}
47+
48+
if (this._options?.useVenv && this._venvActivationCommand) {
49+
all.unshift(this._venvActivationCommand);
50+
}
51+
52+
return all.join(' && ');
53+
}
54+
}
55+
56+
// helpers
57+
function resolveShellPath(): string {
58+
if (IsWindows) {
59+
return process.env.ComSpec ?? 'C:\\Windows\\System32\\cmd.exe';
60+
}
61+
62+
const result = childProcess.spawnSync('command -v bash', { shell: true });
63+
const found = result.stdout.toString().trim();
64+
return result.status === 0 && found ? found : '/bin/bash';
65+
}
66+
67+
function resolveVenvActivationCommand(env: PySideEnv): string {
68+
const bin = env.venvBinPath;
69+
if (!bin) {
70+
return '';
71+
}
72+
73+
const script = enclosePath(
74+
path.join(bin, IsWindows ? 'activate.bat' : 'activate')
75+
);
76+
77+
return IsWindows ? script : `source ${script}`;
78+
}
79+
80+
function enclosePath(s: string) {
81+
return IsWindows ? `"${s}"` : `'${s}'`;
82+
}

qt-python/src/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ export const EXTENSION_ID = 'qt-python';
77
export const MS_PYTHON_ID = 'ms-python.python';
88
export const LOG_NAME = 'qt-python';
99

10+
export const COMMAND_PREFIX = 'qt-python';
11+
export const COMMAND_INSTALL_PYSIDE = 'installPySide6'; // contributes > commands
12+
export const COMMAND_PYTHON_CREATE_ENV = 'python.createEnvironment';
13+
export const COMMAND_PYTHON_SELECT_PYTHON = 'python.setInterpreter';
14+
1015
export const TASK_TYPE = 'pyside'; // contributes > taskDefinitions
1116
export const TASK_SOURCE = 'PySide';
1217

@@ -21,3 +26,4 @@ export const TOML_KEY_PROJECT_NAME = 'project.name';
2126
export const TOML_KEY_PROJECT_FILES = 'tool.pyside6-project.files';
2227

2328
export const PYSIDE_PROJECT_TOOL = 'pyside6-project';
29+
export const MAINT_WHEEL_DIR_NAME = 'QtForPython';

qt-python/src/env.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,22 @@ export class PySideEnv {
1414
this._pyEnv = pyenv;
1515
}
1616

17+
public isVenv(): boolean {
18+
return this._pyEnv?.environment?.type === 'VirtualEnvironment';
19+
}
20+
21+
get venvName(): string | undefined {
22+
return this._pyEnv?.environment?.name;
23+
}
24+
1725
get venvBinPath(): string {
1826
const root = this._pyEnv?.executable.sysPrefix ?? '';
1927
return root
2028
? utils.toForwardSlash(path.join(root, consts.VENV_BIN_DIR))
2129
: '';
2230
}
31+
32+
get interpreterPath(): string | undefined {
33+
return this._pyEnv?.executable.uri?.fsPath;
34+
}
2335
}

qt-python/src/extension.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { PySideTaskProvider } from './task';
1818
import { PySideDebugConfigProvider } from './debug';
1919
import { PySideProjectManager } from './project-manager';
2020
import * as consts from '@/constants';
21+
import { onInstallPySide6Command } from './installer';
2122

2223
const logger = createLogger('extension');
2324

@@ -34,6 +35,8 @@ export async function activate(context: vscode.ExtensionContext) {
3435
await initDependency();
3536
await initCoreApi();
3637
await initPythonSupport(context);
38+
initCommands(context);
39+
3740
logger.info(`Activated: ${consts.EXTENSION_ID}`);
3841
telemetry.sendEvent('activated');
3942
} catch (e) {
@@ -74,6 +77,22 @@ async function initPythonSupport(context: vscode.ExtensionContext) {
7477
);
7578
}
7679

80+
function initCommands(context: vscode.ExtensionContext) {
81+
function register(c: string, callback: (...args: unknown[]) => unknown) {
82+
return vscode.commands.registerCommand(
83+
`${consts.COMMAND_PREFIX}.${c}`,
84+
async () => {
85+
telemetry.sendAction(c);
86+
await callback();
87+
}
88+
);
89+
}
90+
91+
context.subscriptions.push(
92+
register(consts.COMMAND_INSTALL_PYSIDE, onInstallPySide6Command)
93+
);
94+
}
95+
7796
const onPyApiEnvChanged = async (e: PyApiEnvChanged) => {
7897
const folder = e.resource;
7998
if (folder) {

0 commit comments

Comments
 (0)