You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

210 lines
5.9 KiB

#!/usr/bin/env python
"""
Build script for pyngspice using MinGW-w64.
Single-command build that handles PATH setup, editable install,
and optional venv synchronization for pyTesla integration.
Usage:
python build_mingw.py # Build and install (editable)
python build_mingw.py --clean # Clean build directory first
python build_mingw.py --sync # Sync .pth files to other venvs
python build_mingw.py --verify # Just verify the install works
python build_mingw.py --debug # Build with debug logging
Run from cmd.exe, NOT Git Bash (to avoid MSYS path mangling).
"""
import argparse
import glob
import os
import shutil
import subprocess
import sys
from pathlib import Path
# ============================================================================
# Configuration
# ============================================================================
REPO_ROOT = Path(__file__).parent.resolve()
MINGW_BIN = r"C:\mingw64\bin"
CMAKE_BIN = r"C:\Program Files\CMake\bin"
PACKAGE_DIR = REPO_ROOT / "pyngspice"
BUILD_DIR = REPO_ROOT / "build"
# Other venvs that need access to pyngspice (e.g., pyTesla's venv)
# Add paths here as needed:
SYNC_VENVS = [
# r"C:\git\pytesla\.venv",
]
def get_clean_path():
"""Build a clean PATH that avoids Git MSYS conflicts."""
python_dir = Path(sys.executable).parent
python_scripts = python_dir / "Scripts"
path_parts = [
str(MINGW_BIN),
str(python_dir),
str(python_scripts),
str(CMAKE_BIN),
r"C:\Windows\System32",
r"C:\Windows",
]
return ";".join(p for p in path_parts if os.path.isdir(p))
def get_build_env(**extra):
"""Get environment variables for the build."""
env = os.environ.copy()
env["PATH"] = get_clean_path()
env["CC"] = "gcc"
env["CXX"] = "g++"
env.update(extra)
return env
def clean():
"""Remove build directory and old .pyd files."""
if BUILD_DIR.exists():
print(f"Removing {BUILD_DIR}...")
shutil.rmtree(BUILD_DIR, ignore_errors=True)
# Remove old .pyd files from package directory
for pyd in PACKAGE_DIR.glob("*.pyd"):
print(f"Removing {pyd}...")
pyd.unlink()
# Uninstall any previous install
subprocess.run(
[sys.executable, "-m", "pip", "uninstall", "-y", "pyngspice"],
capture_output=True,
)
print("Clean complete.")
def build(debug=False):
"""Build and install pyngspice in editable mode."""
env = get_build_env()
# Verify MinGW is available
gcc = shutil.which("gcc", path=env["PATH"])
if not gcc:
print(f"ERROR: gcc not found. Ensure MinGW is installed at {MINGW_BIN}")
sys.exit(1)
result = subprocess.run(["gcc", "--version"], capture_output=True, text=True, env=env)
print(f"Using GCC: {result.stdout.splitlines()[0]}")
# Build command
cmd = [
sys.executable, "-m", "pip", "install",
"--no-cache-dir",
"-e", ".",
]
if debug:
# Pass debug flag via environment (picked up by scikit-build-core)
env["CMAKE_ARGS"] = "-DCMAKE_BUILD_TYPE=Debug"
print(f"\nBuilding pyngspice...")
print(f" Command: {' '.join(cmd)}")
print(f" Working dir: {REPO_ROOT}")
print()
result = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env)
if result.returncode != 0:
print("\nBuild FAILED.")
sys.exit(result.returncode)
print("\nBuild complete!")
def sync_venvs():
"""Write .pth files to other venvs so they can import pyngspice."""
if not SYNC_VENVS:
print("No venvs configured for sync. Edit SYNC_VENVS in build_mingw.py.")
return
for venv_path in SYNC_VENVS:
venv = Path(venv_path)
if not venv.exists():
print(f" SKIP (not found): {venv}")
continue
# Find site-packages
if sys.platform == "win32":
site_packages = venv / "Lib" / "site-packages"
else:
py_ver = f"python{sys.version_info.major}.{sys.version_info.minor}"
site_packages = venv / "lib" / py_ver / "site-packages"
if not site_packages.exists():
print(f" SKIP (no site-packages): {venv}")
continue
pth_file = site_packages / "pyngspice.pth"
pth_file.write_text(str(REPO_ROOT) + "\n")
print(f" Synced: {pth_file}")
print("Venv sync complete.")
def verify():
"""Verify the pyngspice installation works."""
print("\nVerifying pyngspice installation...")
# Test basic import
result = subprocess.run(
[sys.executable, "-c", (
"from pyngspice import NgspiceRunner, _cpp_available; "
"print(f' C++ extension: {_cpp_available}'); "
"print(f' NgspiceRunner available: {NgspiceRunner.detect()}'); "
"from pyngspice import __version__; "
"print(f' Version: {__version__}'); "
"print(' OK!')"
)],
cwd=str(REPO_ROOT),
)
if result.returncode != 0:
print("\nVerification FAILED.")
return False
return True
def main():
parser = argparse.ArgumentParser(description="Build pyngspice with MinGW-w64")
parser.add_argument("--clean", action="store_true", help="Clean build artifacts first")
parser.add_argument("--sync", action="store_true", help="Sync .pth files to other venvs")
parser.add_argument("--verify", action="store_true", help="Verify installation only")
parser.add_argument("--debug", action="store_true", help="Build with debug configuration")
parser.add_argument("--no-verify", action="store_true", help="Skip post-build verification")
args = parser.parse_args()
if args.verify:
verify()
return
if args.clean:
clean()
if args.sync:
sync_venvs()
return
build(debug=args.debug)
if not args.no_verify:
verify()
if SYNC_VENVS:
sync_venvs()
if __name__ == "__main__":
main()