Files

177 lines
6.3 KiB
Python
Raw Permalink Normal View History

2026-09-13 19:53:09 +01:00
"""
install_dependencies.py
========================
Installs all Python dependencies for the PST Email Extractor and
verifies that pypff (libpff) is importable.
Run once before first use:
python install_dependencies.py
Platform notes
--------------
* Linux / macOS pypff is usually available via pip (libpff-python) or a
system package manager.
* Windows pypff typically requires a pre-built wheel or compiling
libpff from source. See the README for details.
"""
import os
import sys
import subprocess
import platform
from importlib.util import find_spec
# ── package lists ────────────────────────────────────────────────
# Packages that install cleanly via pip on all platforms
PIP_PACKAGES: list[tuple[str, str]] = [
("tqdm", "tqdm>=4.66"), # Progress bars
]
# pypff can be installed with 'libpff-python' on Linux/macOS
PYPFF_PIP_NAME = "libpff-python"
# ── helpers ──────────────────────────────────────────────────────
def run(cmd: list[str]) -> subprocess.CompletedProcess:
"""Print and run a command, returning the result without raising."""
print(f"\n>> {' '.join(cmd)}")
return subprocess.run(cmd, check=False)
def is_importable(module: str) -> bool:
"""Return True if *module* can be found in the current environment."""
return find_spec(module) is not None
# ── steps ────────────────────────────────────────────────────────
def ensure_pip() -> None:
"""Bootstrap / upgrade pip, setuptools, and wheel."""
print("\n[1/3] Ensuring pip is up-to-date …")
run([sys.executable, "-m", "ensurepip", "--upgrade"])
run([sys.executable, "-m", "pip", "install", "--upgrade",
"pip", "setuptools", "wheel"])
def install_pip_packages() -> None:
"""Install the pip-only dependencies."""
print("\n[2/3] Installing pip packages …")
specs = [spec for _, spec in PIP_PACKAGES]
result = run([sys.executable, "-m", "pip", "install", "--upgrade"] + specs)
if result.returncode != 0:
print("\n❌ ERROR: Failed to install pip packages.")
sys.exit(result.returncode)
# Verify each import
for module, spec in PIP_PACKAGES:
ok = is_importable(module)
status = "✓" if ok else "✗"
print(f" {status} {spec} ({'ok' if ok else 'MISSING'})")
def install_pypff() -> bool:
"""
Try to install pypff via pip (works on Linux/macOS).
On Windows this usually fails; we give targeted guidance instead.
"""
print("\n[3/3] Installing pypff (libpff-python) …")
if is_importable("pypff"):
print(" ✓ pypff is already importable skipping install.")
return True
result = run([sys.executable, "-m", "pip", "install", "--upgrade", PYPFF_PIP_NAME])
if result.returncode == 0 and is_importable("pypff"):
print(" ✓ pypff installed and importable.")
return True
# ── second attempt: some distros ship it as 'pypff' directly ──
result2 = run([sys.executable, "-m", "pip", "install", "--upgrade", "pypff"])
if result2.returncode == 0 and is_importable("pypff"):
print(" ✓ pypff installed via 'pypff' package name.")
return True
print(" ✗ Could not install pypff automatically.")
return False
# ── platform guidance ─────────────────────────────────────────────
def print_pypff_guidance() -> None:
system = platform.system()
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
print("\n" + "=" * 72)
print("MANUAL INSTALLATION REQUIRED: pypff / libpff")
print("=" * 72)
if system == "Linux":
print(
"On Debian / Ubuntu:\n"
" sudo apt-get install python3-libpff\n"
" # or\n"
" pip install libpff-python\n\n"
"On Fedora / RHEL:\n"
" sudo dnf install libpff-devel python3-libpff\n"
)
elif system == "Darwin":
print(
"On macOS (Homebrew):\n"
" brew install libpff\n"
" pip install libpff-python\n"
)
elif system == "Windows":
print(
f"On Windows (Python {py_ver}):\n"
" Option 1 Pre-built wheel (if available for your Python version):\n"
" pip install libpff-python\n\n"
" Option 2 Build from source:\n"
" https://github.com/libyal/libpff/blob/main/documentation/Building.md\n\n"
" Option 3 Use WSL (Windows Subsystem for Linux) and follow the Linux steps.\n"
)
else:
print(
" Please visit https://github.com/libyal/libpff for build instructions\n"
f" appropriate for your platform ({system}).\n"
)
print("=" * 72)
# ── main ──────────────────────────────────────────────────────────
def main() -> None:
print("PST Email Extractor Dependency Installer")
print("=" * 72)
print(f" Python: {sys.version.replace(chr(10), ' ')}")
print(f" Executable: {sys.executable}")
print(f" Platform: {platform.platform()}")
print(f" CWD: {os.getcwd()}")
ensure_pip()
install_pip_packages()
has_pypff = install_pypff()
# ── summary ──────────────────────────────────────────────────
print("\n" + "=" * 72)
print("Installation summary")
print("=" * 72)
for module, spec in PIP_PACKAGES:
ok = is_importable(module)
print(f" {'✓' if ok else '✗'} {spec}")
print(f" {'✓' if has_pypff else '✗'} pypff (libpff)")
print("=" * 72)
if not has_pypff:
print_pypff_guidance()
sys.exit(2)
print("\n✅ All dependencies are installed.")
print(" You can now run: python Extract.py [directory]")
if __name__ == "__main__":
main()