File: hatch_build.py

package info (click to toggle)
packmol 1%3A21.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,504 kB
  • sloc: tcl: 7,504; f90: 5,290; fortran: 1,879; sh: 299; makefile: 167; python: 121; lisp: 101
file content (108 lines) | stat: -rw-r--r-- 3,400 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Custom Hatchling build hook to compile packmol binary."""

import os
import shutil
import subprocess
from pathlib import Path
from typing import Any

from hatchling.builders.hooks.plugin.interface import BuildHookInterface


class CustomBuildHook(BuildHookInterface):
    """Build hook to compile packmol from Fortran source."""

    def initialize(self, version: str, build_data: dict[str, Any]) -> None:
        """Compile packmol and copy binary to package.

        Args:
            version: The version of the package being built.
            build_data: Dictionary of build configuration data.
        """
        # Find gfortran compiler
        gfortran = self._find_gfortran()

        # Verify source files exist
        src_dir = Path("src")
        if not src_dir.exists():
            raise RuntimeError(
                f"Source directory not found: {src_dir}. "
                "Ensure packmol Fortran source files are in the src/ directory."
            )

        # Create binaries output directory
        binaries_dir = Path("python/packmol/binaries")
        binaries_dir.mkdir(parents=True, exist_ok=True)

        # Clean previous builds
        print("Cleaning previous build artifacts...")
        subprocess.run(["make", "clean"], check=False, cwd=".")

        # Compile packmol
        print(f"Compiling packmol with {gfortran}...")
        result = subprocess.run(
            ["make", f"FORTRAN={gfortran}"],
            check=False,
            capture_output=True,
            text=True,
        )

        if result.returncode != 0:
            raise RuntimeError(
                f"Failed to compile packmol:\n{result.stderr}\n{result.stdout}"
            )

        # Find compiled binary
        binary_name = "packmol.exe" if os.name == "nt" else "packmol"
        binary_path = Path(binary_name)

        if not binary_path.exists():
            raise RuntimeError(
                f"Compiled binary not found: {binary_path}. "
                "Compilation may have failed."
            )

        # Copy binary to package
        target_path = binaries_dir / binary_name
        print(f"Copying {binary_path} to {target_path}...")
        shutil.copy2(binary_path, target_path)

        # Ensure binary is executable
        target_path.chmod(0o755)

        print(f"Successfully built packmol binary: {target_path}")

        # Mark wheel as platform-specific
        build_data["pure_python"] = False
        build_data["infer_tag"] = True

    def _find_gfortran(self) -> str:
        """Find gfortran compiler.

        Returns:
            Path to gfortran executable.

        Raises:
            RuntimeError: If gfortran is not found.
        """
        # Try plain gfortran first
        if shutil.which("gfortran"):
            return "gfortran"

        # Try versioned gfortran (e.g., gfortran-13, gfortran-14)
        for version in range(20, 10, -1):
            gfortran_versioned = f"gfortran-{version}"
            if shutil.which(gfortran_versioned):
                return gfortran_versioned

        # Check common Homebrew paths on macOS
        homebrew_paths = [
            "/opt/homebrew/bin/gfortran",
            "/usr/local/bin/gfortran",
        ]
        for path in homebrew_paths:
            if Path(path).exists():
                return path

        # Not found
        raise RuntimeError("gfortran compiler not found.")