Skip to content

Commit a49f248

Browse files
committed
Add --prebuild option to securebuild for pre-build commands
Added --prebuild command-line argument to guidata.utils.securebuild that executes custom commands in the temporary build directory before running the package build. This enables compiling translations or performing other pre-build tasks in the clean clone environment. Key features: - Replaces 'python' with sys.executable to avoid Windows PATH issues - Converts relative PYTHONPATH entries to absolute paths for correct module resolution when running from temporary directory
1 parent bd96589 commit a49f248

2 files changed

Lines changed: 67 additions & 6 deletions

File tree

doc/release_notes/release_3.14.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
✨ New features:
66

7+
* **Secure build utility**: Added `--prebuild` option to run commands before package build
8+
* New `--prebuild` command-line argument for `guidata.utils.securebuild`
9+
* Executes custom commands in the temporary build directory before `python -m build`
10+
* Useful for compiling translations or other pre-build tasks in the clean clone environment
11+
* Automatically replaces `python` with the current interpreter to avoid PATH issues on Windows
12+
* Converts relative PYTHONPATH entries to absolute paths for correct module resolution
13+
* Usage: `python -m guidata.utils.securebuild --prebuild "python -m guidata.utils.translations compile --name myapp --directory ."`
14+
715
* New `cleanup-doc` command for Sphinx documentation translation files
816
* Added `cleanup_doc_translations()` function to clean up `.po` files in `doc/locale/` directories
917
* Removes `POT-Creation-Date` and `Last-Translator` headers from all Sphinx-generated translation files

guidata/utils/securebuild.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,36 @@
1717

1818
from __future__ import annotations
1919

20+
import argparse
21+
import os
2022
import shutil
2123
import subprocess
2224
import sys
2325
import tempfile
2426
from pathlib import Path
2527

2628

27-
def run_secure_build(root_path: str | None = None) -> None:
29+
def run_secure_build(
30+
root_path: str | None = None, prebuild_command: str | None = None
31+
) -> None:
2832
"""Run a secure build of the Python package.
2933
3034
Args:
3135
root_path: Path to the root directory of the Git repository. If None,
3236
the function will take the current working directory as the root.
37+
prebuild_command: Optional command to run before building. This is useful
38+
for compiling translations or other pre-build tasks. The command is
39+
executed in the temporary build directory.
3340
3441
This function performs the following steps:
3542
3643
1. Creates a temporary directory to hold the build files.
3744
2. Creates a tar archive of the current HEAD of the Git repository.
3845
3. Extracts the contents of the tar archive into the temporary directory.
3946
4. Checks for the presence of `pyproject.toml` in the extracted files.
40-
5. Runs the build process using `build` to create source distribution and wheels.
41-
6. Copies the generated files to the `dist/` directory in repository root.
47+
5. Runs the optional prebuild command if provided.
48+
6. Runs the build process using `build` to create source distribution and wheels.
49+
7. Copies the generated files to the `dist/` directory in repository root.
4250
4351
Raises:
4452
RuntimeError: If the current directory is not a Git repository and no
@@ -91,15 +99,42 @@ def run_secure_build(root_path: str | None = None) -> None:
9199
for path in sorted(build_root.iterdir()):
92100
print(f" - {path.name}")
93101

94-
# Step 4 : run the package build
102+
# Step 4 : run the optional prebuild command
103+
if prebuild_command:
104+
# Replace 'python' with the current interpreter to avoid PATH issues
105+
if prebuild_command.startswith("python "):
106+
prebuild_command = f'"{sys.executable}" {prebuild_command[7:]}'
107+
elif prebuild_command.startswith("python3 "):
108+
prebuild_command = f'"{sys.executable}" {prebuild_command[8:]}'
109+
110+
# Prepare environment with absolute PYTHONPATH (relative paths break in
111+
# temp directory)
112+
env = os.environ.copy()
113+
if "PYTHONPATH" in env:
114+
pythonpath_entries = env["PYTHONPATH"].split(os.pathsep)
115+
abs_entries = [
116+
str(Path(entry).resolve()) for entry in pythonpath_entries
117+
]
118+
env["PYTHONPATH"] = os.pathsep.join(abs_entries)
119+
120+
print(f"🔧 Running prebuild command: {prebuild_command}")
121+
subprocess.run(
122+
prebuild_command,
123+
cwd=build_root,
124+
check=True,
125+
shell=True,
126+
env=env,
127+
)
128+
129+
# Step 5 : run the package build
95130
print("🔨 Building the package...")
96131
subprocess.run(
97132
[sys.executable, "-m", "build", "--sdist", "--wheel"],
98133
cwd=build_root,
99134
check=True,
100135
)
101136

102-
# Step 5 : copy the artifacts to dist/
137+
# Step 6 : copy the artifacts to dist/
103138
print(f"📦 Copying built packages to {dist_dir}...")
104139
build_dist = build_root / "dist"
105140
dist_dir.mkdir(exist_ok=True)
@@ -110,4 +145,22 @@ def run_secure_build(root_path: str | None = None) -> None:
110145

111146

112147
if __name__ == "__main__":
113-
run_secure_build()
148+
parser = argparse.ArgumentParser(
149+
description="Securely build Python packages from a Git repository."
150+
)
151+
parser.add_argument(
152+
"--prebuild",
153+
type=str,
154+
default=None,
155+
help="Command to run before building (e.g., to compile translations). "
156+
"The command is executed in the temporary build directory.",
157+
)
158+
parser.add_argument(
159+
"root_path",
160+
nargs="?",
161+
default=None,
162+
help="Path to the root directory of the Git repository. "
163+
"If not provided, the current working directory is used.",
164+
)
165+
args = parser.parse_args()
166+
run_secure_build(root_path=args.root_path, prebuild_command=args.prebuild)

0 commit comments

Comments
 (0)