Skip to content

Commit 20bc096

Browse files
committed
Support 3.14 Tasks .vscode
* [UPDATE] : .vscode/tasks.json : - modification for Coverage full (shell syntax) - modification for Reinstall guidata dev (shell syntax) - add reinstall_dev.py * [UPDATE] : run_with_env.py (minor modification for path) * [UPDATE] : pyproject.toml add exclusion for python venv named : venv* and .venv* * [FIX] : build_inplace.bat and build_wheels.bat in script (work with native python)
1 parent 0f7061b commit 20bc096

File tree

6 files changed

+235
-51
lines changed

6 files changed

+235
-51
lines changed

.vscode/tasks.json

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,15 +251,12 @@
251251
{
252252
"label": "🔁 Reinstall guidata dev",
253253
"type": "shell",
254-
"windows": {
255-
"command": "pip uninstall -y guidata; Remove-Item -Recurse -Force .venv/Lib/site-packages/guidata -ErrorAction SilentlyContinue; pip install -e ../guidata",
256-
},
257-
"linux": {
258-
"command": "pip uninstall -y guidata && rm -rf .venv/lib/python*/site-packages/guidata && pip install -e ../guidata",
259-
},
260-
"osx": {
261-
"command": "pip uninstall -y guidata && rm -rf .venv/lib/python*/site-packages/guidata && pip install -e ../guidata",
262-
},
254+
"command": "${config:python.defaultInterpreterPath}",
255+
"args": [
256+
"scripts/run_with_env.py",
257+
"${config:python.defaultInterpreterPath}",
258+
"${workspaceFolder}/scripts/reinstall_dev.py",
259+
],
263260
"options": {
264261
"cwd": "${workspaceFolder}",
265262
"statusbar": {
@@ -521,7 +518,7 @@
521518
"label": "📊 Coverage full",
522519
"type": "shell",
523520
"windows": {
524-
"command": "${command:python.interpreterPath} -m coverage combine && ${command:python.interpreterPath} -m coverage html && start htmlcov\\index.html",
521+
"command": "${command:python.interpreterPath} -m coverage combine; if ($?) { ${command:python.interpreterPath} -m coverage html; if ($?) { start htmlcov\\index.html } }",
525522
},
526523
"linux": {
527524
"command": "${command:python.interpreterPath} -m coverage combine && ${command:python.interpreterPath} -m coverage html && xdg-open htmlcov/index.html",

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ classifiers = [
2626
"Programming Language :: Python :: 3.11",
2727
"Programming Language :: Python :: 3.12",
2828
"Programming Language :: Python :: 3.13",
29+
"Programming Language :: Python :: 3.14",
2930
"Topic :: Scientific/Engineering",
3031
"Topic :: Scientific/Engineering :: Image Processing",
3132
"Topic :: Scientific/Engineering :: Human Machine Interfaces",
@@ -57,7 +58,7 @@ plotpy-benchmarks = "plotpy.tests.benchmarks:run"
5758

5859
[project.optional-dependencies]
5960
qt = ["PyQt5>5.15.5"]
60-
dev = ["build", "babel", "Coverage", "Cython>=3.0", "pylint", "ruff", "pre-commit"]
61+
dev = ["build", "babel", "Coverage", "Cython>=3.0", "pylint", "ruff", "pre-commit", "setuptools", "wheel"]
6162
doc = [
6263
"sphinx",
6364
"myst_parser",
@@ -81,7 +82,7 @@ addopts = "plotpy --import-mode=importlib"
8182
# addopts = "plotpy --import-mode=importlib --show-windows" # Disable offscreen mode
8283

8384
[tool.ruff]
84-
exclude = [".git", ".vscode", "build", "dist"]
85+
exclude = [".git", ".vscode", "build", "dist","venv*",".venv*"]
8586
line-length = 88 # Same as Black.
8687
indent-width = 4 # Same as Black.
8788
target-version = "py39" # Assume Python 3.9.

scripts/build_inplace.bat

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,57 @@ REM Copyright (c) 2020 Pierre Raybaut
88
REM (see PythonQwt LICENSE file for more details)
99
REM ======================================================
1010
setlocal enabledelayedexpansion
11-
call %~dp0utils GetScriptPath SCRIPTPATH
11+
set FUNC=%~dp0utils.bat
12+
call %FUNC% GetScriptPath SCRIPTPATH
1213
call %FUNC% GetModName MODNAME
13-
call %FUNC% SetPythonPath
14+
REM call %FUNC% SetPythonPath
15+
16+
REM Go to project root
17+
cd /d "%~dp0.."
1418

1519
if exist MANIFEST ( del /q MANIFEST )
20+
21+
set "BUILD_DONE="
22+
1623
:: Iterate over all directories in the grandparent directory
1724
:: (WinPython base directories)
1825
call %FUNC% GetPythonExeGrandParentDir DIR0
19-
for /D %%d in ("%DIR0%*") do (
20-
:: Get the directory name without the path
21-
for %%n in (%%d) do set "DIRNAME=%%~nxn"
22-
23-
:: Check if the directory ends with "-PyQt6" or "-PySide6"
24-
if not "!DIRNAME:~-6!"=="-PyQt6" (
25-
if not "!DIRNAME:~-8!"=="-PySide6" (
26-
set WINPYDIRBASE=%%d
27-
call !WINPYDIRBASE!\scripts\env.bat
28-
echo ******************************************************************************
29-
echo Building %MODNAME% from "%%d"
30-
echo ******************************************************************************
31-
python setup.py build_ext --inplace
32-
echo ----
26+
if defined DIR0 (
27+
for /D %%d in ("%DIR0%*") do (
28+
:: Get the directory name without the path
29+
for %%n in (%%d) do set "DIRNAME=%%~nxn"
30+
31+
:: Check if the directory ends with "-PyQt6" or "-PySide6"
32+
if not "!DIRNAME:~-6!"=="-PyQt6" (
33+
if not "!DIRNAME:~-8!"=="-PySide6" (
34+
if exist "%%d\scripts\env.bat" (
35+
set WINPYDIRBASE=%%d
36+
set OLD_PATH=!PATH!
37+
call !WINPYDIRBASE!\scripts\env.bat
38+
echo ******************************************************************************
39+
echo Building %MODNAME% from "%%d"
40+
echo ******************************************************************************
41+
python setup.py build_ext --inplace
42+
echo ----
43+
set PATH=!OLD_PATH!
44+
set BUILD_DONE=1
45+
)
46+
)
3347
)
3448
)
3549
)
50+
51+
REM Fallback: run in current environment if no WinPython build occurred
52+
if not defined BUILD_DONE (
53+
echo ******************************************************************************
54+
echo Building %MODNAME% in current environment
55+
echo ******************************************************************************
56+
if defined PYTHON (
57+
"%PYTHON%" setup.py build_ext --inplace
58+
) else (
59+
python setup.py build_ext --inplace
60+
)
61+
)
62+
63+
3664
call %FUNC% EndOfScript

scripts/build_wheels.bat

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,52 @@ REM Copyright (c) 2020 Pierre Raybaut
88
REM (see PythonQwt LICENSE file for more details)
99
REM ======================================================
1010
setlocal enabledelayedexpansion
11-
call %~dp0utils GetScriptPath SCRIPTPATH
11+
set FUNC=%~dp0utils.bat
12+
call %FUNC% GetScriptPath SCRIPTPATH
1213
call %FUNC% GetModName MODNAME
13-
call %FUNC% SetPythonPath
14+
15+
REM Go to project root
16+
cd /d "%~dp0.."
1417

1518
if exist MANIFEST ( del /q MANIFEST )
16-
:: Iterate over all directories in the grandparent directory
17-
:: (WinPython base directories)
18-
call %FUNC% GetPythonExeGrandParentDir DIR0
19-
for /D %%d in ("%DIR0%*") do (
20-
:: Get the directory name without the path
21-
for %%n in (%%d) do set "DIRNAME=%%~nxn"
2219

23-
:: Check if the directory ends with "-PyQt6" or "-PySide6"
24-
if not "!DIRNAME:~-6!"=="-PyQt6" (
25-
if not "!DIRNAME:~-8!"=="-PySide6" (
26-
set WINPYDIRBASE=%%d
27-
set OLD_PATH=!PATH!
28-
call !WINPYDIRBASE!\scripts\env.bat
29-
echo ******************************************************************************
30-
echo Building %MODNAME% from "%%d"
31-
echo ******************************************************************************
32-
python -m build
33-
echo ----
34-
set PATH=!OLD_PATH!
20+
set "BUILD_DONE="
21+
22+
REM Try to detect WinPython environment
23+
call %FUNC% GetPythonExeGrandParentDir DIR0
24+
if defined DIR0 (
25+
for /D %%d in ("%DIR0%*") do (
26+
for %%n in (%%d) do set "DIRNAME=%%~nxn"
27+
REM Check if directory seems to be a WinPython directory (heuristic)
28+
if not "!DIRNAME:~-6!"=="-PyQt6" (
29+
if not "!DIRNAME:~-8!"=="-PySide6" (
30+
if exist "%%d\scripts\env.bat" (
31+
set WINPYDIRBASE=%%d
32+
set OLD_PATH=!PATH!
33+
call !WINPYDIRBASE!\scripts\env.bat
34+
echo ******************************************************************************
35+
echo Building %MODNAME% from "%%d"
36+
echo ******************************************************************************
37+
python -m build
38+
echo ----
39+
set PATH=!OLD_PATH!
40+
set BUILD_DONE=1
41+
)
42+
)
3543
)
3644
)
3745
)
46+
47+
REM Fallback: run in current environment if no WinPython build occurred
48+
if not defined BUILD_DONE (
49+
echo ******************************************************************************
50+
echo Building %MODNAME% in current environment
51+
echo ******************************************************************************
52+
if defined PYTHON (
53+
"%PYTHON%" -m build
54+
) else (
55+
python -m build
56+
)
57+
)
58+
3859
call %FUNC% EndOfScript

scripts/reinstall_dev.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# script/reinstall_dev.py
2+
"""
3+
Reinstall multiple local libraries in editable mode for development.
4+
5+
Workflow:
6+
1) Try to uninstall all target libraries in one command (ignore errors if some are not installed).
7+
2) Reinstall each library in editable mode from a sibling folder: ../<library>.
8+
9+
This script uses the same Python interpreter that runs it (sys.executable),
10+
so pip operations happen in the same environment (e.g., your active venv).
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import os
16+
import shutil
17+
import subprocess
18+
import sys
19+
import sysconfig
20+
from typing import Iterable
21+
22+
# Absolute path to the Python executable that runs this script.
23+
# Using sys.executable ensures pip targets the same environment (e.g., your venv).
24+
PY = sys.executable
25+
26+
27+
def run(cmd: list[str]) -> None:
28+
"""
29+
Echo and execute a subprocess command.
30+
Raises:
31+
subprocess.CalledProcessError: if the command returns a non-zero exit code.
32+
"""
33+
print("$", " ".join(cmd), flush=True)
34+
subprocess.check_call(cmd)
35+
36+
37+
def uninstall_many(packages: Iterable[str]) -> None:
38+
"""
39+
Attempt to uninstall multiple packages at once.
40+
If uninstall fails (e.g., a package is not installed), we keep going.
41+
42+
Args:
43+
packages: An iterable of package names (pip distribution names).
44+
"""
45+
pkgs = list(packages)
46+
if not pkgs:
47+
return
48+
try:
49+
run([PY, "-m", "pip", "uninstall", "-y", *pkgs])
50+
except subprocess.CalledProcessError as e:
51+
# Continue even if the uninstall step fails (for one or more packages)
52+
print(f"[WARN] Uninstall returned {e.returncode} — continuing...", flush=True)
53+
54+
55+
def install_editable_many(packages: Iterable[str]) -> None:
56+
"""
57+
Install each package in editable mode from ../<package_dir>.
58+
59+
Assumes your project layout has sibling folders one level up, e.g.:
60+
../guidata
61+
62+
Args:
63+
packages: An iterable of package names (also used as directory names).
64+
"""
65+
for pkg in packages:
66+
run([PY, "-m", "pip", "install", "-e", f"../{pkg}"])
67+
68+
69+
def remove_residual_dirs(packages: Iterable[str]) -> None:
70+
"""
71+
Force remove residual package directories from site-packages.
72+
This mimics: rm -rf .venv/Lib/site-packages/<pkg>
73+
to ensure a clean slate before reinstalling.
74+
"""
75+
# Locates site-packages, e.g. .venv/Lib/site-packages
76+
site_packages = sysconfig.get_path("purelib")
77+
78+
for pkg in packages:
79+
target = os.path.join(site_packages, pkg)
80+
if os.path.isdir(target):
81+
print(f"Removing residual directory: {target}", flush=True)
82+
try:
83+
shutil.rmtree(target)
84+
except OSError as e:
85+
print(f"[WARN] Failed to remove {target}: {e}", flush=True)
86+
87+
88+
def reinstall_packages(packages: list[str]) -> None:
89+
"""
90+
High-level orchestration for many packages:
91+
- Uninstall all of them (ignore failures)
92+
- Force remove residual directories from site-packages
93+
- Install each in editable mode
94+
"""
95+
# 1) Uninstall (ignore if not installed)
96+
uninstall_many(packages)
97+
98+
# 2) Force remove residual directories (essential for clean reinstall)
99+
remove_residual_dirs(packages)
100+
101+
# 3) Editable installs
102+
install_editable_many(packages)
103+
104+
105+
if __name__ == "__main__":
106+
# ⭐ Fixed, editable local libraries to manage (edit as needed)
107+
PACKAGES = ["guidata"]
108+
109+
print("🏃 Reinstalling editable packages:", ", ".join(PACKAGES))
110+
reinstall_packages(PACKAGES)

scripts/run_with_env.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212

1313
def load_env_file(env_path: str | None = None) -> None:
1414
"""Load environment variables from a .env file."""
15+
# Set a flag to indicate that the environment has been loaded by this script
16+
# This prevents batch scripts (like utils.bat) from reloading .env and overwriting variables
17+
1518
if env_path is None:
16-
# Get ".eenv" file from the current directory
19+
# Get ".env" file from the current directory
1720
env_path = Path.cwd() / ".env"
1821
if not Path(env_path).is_file():
1922
raise FileNotFoundError(f"Environment file not found: {env_path}")
@@ -24,8 +27,32 @@ def load_env_file(env_path: str | None = None) -> None:
2427
if not line or line.startswith("#") or "=" not in line:
2528
continue
2629
key, value = line.split("=", 1)
27-
os.environ[key.strip()] = value.strip()
28-
print(f" Loaded variable: {key.strip()}={value.strip()}")
30+
value = os.path.expandvars(value.strip())
31+
32+
# Handle PATH variable specifically:
33+
# 1. Convert relative paths to absolute paths
34+
# 2. Normalize path separators
35+
if key.strip().upper() == "PATH":
36+
paths = value.split(os.pathsep)
37+
abs_paths = []
38+
for p in paths:
39+
p = p.strip()
40+
if not p:
41+
continue
42+
# Check if it looks like a relative path component
43+
# (not starting with drive or root)
44+
# Note: This simple check assumes standard usage in .env
45+
if not os.path.isabs(p) and not p.startswith("%"):
46+
try:
47+
# Resolve relative to .env file directory
48+
p = str((Path(env_path).parent / p).resolve())
49+
except Exception:
50+
pass # Keep as is if resolution fails
51+
abs_paths.append(os.path.normpath(p))
52+
value = os.pathsep.join(abs_paths)
53+
54+
os.environ[key.strip()] = value
55+
print(f" Loaded variable: {key.strip()}={value}")
2956

3057

3158
def execute_command(command: list[str]) -> int:

0 commit comments

Comments
 (0)