bioforge 2.3.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,231 @@
1
+ """
2
+ engine/build.py — Compila el motor C a DLL/SO (Windows / Linux / macOS).
3
+
4
+ Uso:
5
+ python engine/build.py
6
+
7
+ Detecta el compilador automáticamente (GCC, incluida la ubicación típica de
8
+ MSYS2 en Windows; clang en macOS) e intenta enlazar zlib (.gz) y libdeflate
9
+ (.gz ~2× más rápido). Cada dependencia degrada con gracia si no está.
10
+
11
+ Variables de entorno
12
+ ────────────────────
13
+ BIOFORGE_PORTABLE=1 Compila para CPU genérica (sin `-march=native`).
14
+ OBLIGATORIO al construir wheels distribuidos.
15
+ BIOFORGE_STATIC=1 Enlace AUTOCONTENIDO para wheels: en Linux/macOS enlaza
16
+ OpenMP (libgomp/libomp) de forma estática para que el
17
+ .so no dependa de librerías fuera de la lista blanca de
18
+ manylinux. Así el paso "repair" (auditwheel/delocate)
19
+ sobra y se puede saltar. zlib se deja dinámico (está en
20
+ la lista blanca / es del sistema en macOS).
21
+ """
22
+
23
+ import os
24
+ import shutil
25
+ import subprocess
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ ENGINE_DIR = Path(__file__).parent
30
+ SRC = ENGINE_DIR / "engine.c"
31
+
32
+ # Ubicaciones habituales de GCC en Windows (MSYS2) si no está en el PATH.
33
+ _WIN_GCC_CANDIDATES = [
34
+ r"C:\msys64\mingw64\bin\gcc.exe",
35
+ r"C:\msys64\ucrt64\bin\gcc.exe",
36
+ r"C:\mingw64\bin\gcc.exe",
37
+ ]
38
+
39
+
40
+ def _flag(name: str) -> bool:
41
+ return os.environ.get(name, "") not in ("", "0", "false")
42
+
43
+
44
+ def _find_compiler() -> str | None:
45
+ """GCC en Windows/Linux; clang (o gcc) en macOS."""
46
+ if sys.platform == "darwin":
47
+ return shutil.which("clang") or shutil.which("gcc")
48
+ found = shutil.which("gcc")
49
+ if found:
50
+ return found
51
+ if sys.platform == "win32":
52
+ for cand in _WIN_GCC_CANDIDATES:
53
+ if Path(cand).exists():
54
+ return cand
55
+ return None
56
+
57
+
58
+ def _brew_prefix(pkg: str) -> str | None:
59
+ """Prefijo de instalación de un paquete Homebrew (macOS), o None."""
60
+ try:
61
+ res = subprocess.run(["brew", "--prefix", pkg],
62
+ capture_output=True, text=True)
63
+ if res.returncode == 0:
64
+ p = res.stdout.strip()
65
+ return p if p and Path(p).exists() else None
66
+ except FileNotFoundError:
67
+ pass
68
+ return None
69
+
70
+
71
+ def _run(cmd: list[str]) -> subprocess.CompletedProcess:
72
+ env = os.environ.copy()
73
+ cc = cmd[0]
74
+ if sys.platform == "win32" and cc.lower().endswith("gcc.exe"):
75
+ env["PATH"] = str(Path(cc).parent) + os.pathsep + env.get("PATH", "")
76
+ return subprocess.run(cmd, capture_output=True, text=True, env=env)
77
+
78
+
79
+ def _arch() -> list[str]:
80
+ # Nativa para uso local (máxima velocidad), genérica para wheels.
81
+ return ["-O3"] if _flag("BIOFORGE_PORTABLE") else ["-O3", "-march=native"]
82
+
83
+
84
+ # ──────────────────────────────────────────────────────────────────────────────
85
+ # Ruta AUTOCONTENIDA para wheels (Linux/macOS): compilar + enlazar en dos pasos,
86
+ # con OpenMP estático. Devuelve True si produjo engine.so.
87
+ # ──────────────────────────────────────────────────────────────────────────────
88
+ def _build_static_unix(cc: str) -> bool:
89
+ out = ENGINE_DIR / "engine.so"
90
+ obj = ENGINE_DIR / "engine.o"
91
+ is_mac = sys.platform == "darwin"
92
+ arch = _arch()
93
+
94
+ # ── Paso 1: compilar a objeto (pragmas OpenMP activos) ──────────────────────
95
+ cflags = [cc, "-c", *arch, "-fPIC", "-DBIO_USE_ZLIB"]
96
+ libomp = None
97
+ if is_mac:
98
+ libomp = _brew_prefix("libomp")
99
+ if libomp:
100
+ cflags += ["-Xpreprocessor", "-fopenmp", f"-I{libomp}/include"]
101
+ else:
102
+ print("[aviso] libomp no encontrado; motor sin parseo paralelo.")
103
+ else:
104
+ cflags += ["-fopenmp"]
105
+ cflags += ["-o", str(obj), str(SRC)]
106
+
107
+ print("Compilando engine.c -> engine.o (objeto) ...")
108
+ res = _run(cflags)
109
+ if res.returncode != 0:
110
+ # Reintentar sin zlib (p.ej. faltan cabeceras de zlib).
111
+ cflags = [c for c in cflags if c != "-DBIO_USE_ZLIB"]
112
+ res = _run(cflags)
113
+ if res.returncode != 0:
114
+ print("[ERROR] No se pudo compilar el objeto:")
115
+ print(res.stderr)
116
+ return False
117
+ zlib_on = False
118
+ else:
119
+ zlib_on = True
120
+
121
+ # ── Paso 2: enlazar a .so autocontenido (OpenMP estático) ───────────────────
122
+ link = [cc, "-shared", "-o", str(out), str(obj)]
123
+ if is_mac:
124
+ if libomp:
125
+ link += [f"{libomp}/lib/libomp.a"] # OpenMP estático (full path)
126
+ if zlib_on:
127
+ link += ["-lz"] # zlib del sistema (dinámico)
128
+ else:
129
+ link += ["-static-libgcc"]
130
+ # libgomp ESTÁTICO (no está en la lista blanca de manylinux);
131
+ # zlib y pthread DINÁMICOS (sí están en la lista blanca).
132
+ link += ["-Wl,--whole-archive", "-l:libgomp.a", "-Wl,--no-whole-archive"]
133
+ if zlib_on:
134
+ link += ["-lz"]
135
+ link += ["-lpthread", "-lm", "-ldl"]
136
+
137
+ print("Enlazando engine.o -> engine.so (OpenMP estático) ...")
138
+ res = _run(link)
139
+ if res.returncode != 0 and not is_mac:
140
+ # Fallback: libgomp dinámico (wheel funcionará donde haya libgomp.so.1).
141
+ print("[aviso] Enlace estático de OpenMP falló; probando dinámico.")
142
+ link = [cc, "-shared", "-o", str(out), str(obj), "-fopenmp"]
143
+ if zlib_on:
144
+ link += ["-lz"]
145
+ res = _run(link)
146
+
147
+ try:
148
+ obj.unlink()
149
+ except OSError:
150
+ pass
151
+
152
+ if res.returncode == 0 and out.exists():
153
+ size_kb = out.stat().st_size // 1024
154
+ omp = "con" if (is_mac and libomp) or not is_mac else "sin"
155
+ gz = "con" if zlib_on else "sin"
156
+ print(f"[OK] Motor autocontenido: {out} ({size_kb} KB) "
157
+ f"[{omp} OpenMP · {gz} .gz]")
158
+ return True
159
+
160
+ print("[ERROR] Error de enlace:")
161
+ print(res.stderr)
162
+ return False
163
+
164
+
165
+ def build() -> bool:
166
+ cc = _find_compiler()
167
+ if not cc:
168
+ print("[ERROR] No se encontró compilador. Instala MinGW-w64 (Windows), "
169
+ "gcc (Linux) o Xcode/clang (macOS) y vuelve a intentarlo.")
170
+ return False
171
+
172
+ # Wheels distribuidos en Linux/macOS → ruta autocontenida (OpenMP estático).
173
+ if _flag("BIOFORGE_STATIC") and sys.platform != "win32":
174
+ return _build_static_unix(cc)
175
+
176
+ arch = _arch()
177
+ omp = ["-fopenmp"]
178
+
179
+ if sys.platform == "win32":
180
+ out = ENGINE_DIR / "engine.dll"
181
+ # -static enlaza libgomp, libgcc y winpthread DENTRO del DLL.
182
+ base = [cc, *arch, *omp, "-shared", "-static"]
183
+ zlib_link = ["-l:libz.a"]
184
+ deflate_link = ["-l:libdeflate.a"]
185
+ elif sys.platform == "darwin":
186
+ out = ENGINE_DIR / "engine.so"
187
+ libomp = _brew_prefix("libomp")
188
+ extra_inc: list[str] = []
189
+ extra_lib: list[str] = []
190
+ if libomp:
191
+ omp = ["-Xpreprocessor", "-fopenmp"]
192
+ extra_inc += [f"-I{libomp}/include"]
193
+ extra_lib += [f"-L{libomp}/lib", "-lomp"]
194
+ else:
195
+ omp = []
196
+ base = [cc, *arch, *omp, "-shared", "-fPIC", *extra_inc, *extra_lib]
197
+ zlib_link = ["-lz"]
198
+ deflate_link = ["-ldeflate"]
199
+ else: # Linux (uso local, no-wheel)
200
+ out = ENGINE_DIR / "engine.so"
201
+ base = [cc, *arch, *omp, "-shared", "-fPIC"]
202
+ zlib_link = ["-lz"]
203
+ deflate_link = ["-ldeflate"]
204
+
205
+ common = base + ["-o", str(out), str(SRC)]
206
+ attempts = [
207
+ ("CON .gz + libdeflate (rápido)",
208
+ ["-DBIO_USE_ZLIB", "-DBIO_USE_LIBDEFLATE"] + zlib_link + deflate_link),
209
+ ("CON .gz (zlib)", ["-DBIO_USE_ZLIB"] + zlib_link),
210
+ ("SIN soporte .gz", []),
211
+ ]
212
+
213
+ mode = "portátil/genérica" if _flag("BIOFORGE_PORTABLE") else "nativa (-march=native)"
214
+ print(f"Compilando {SRC.name} -> {out.name} [arquitectura {mode}] ...")
215
+ last = None
216
+ for label, link in attempts:
217
+ res = _run(common + link)
218
+ last = res
219
+ if res.returncode == 0:
220
+ size_kb = out.stat().st_size // 1024
221
+ print(f"[OK] Motor compilado [{label}]: {out} ({size_kb} KB)")
222
+ return True
223
+ print(f"[aviso] No se pudo compilar [{label}]; probando alternativa.")
224
+
225
+ print("[ERROR] Error de compilacion:")
226
+ print(last.stderr if last else "(sin salida)")
227
+ return False
228
+
229
+
230
+ if __name__ == "__main__":
231
+ sys.exit(0 if build() else 1)