pgsi-analyzer 1.0.0__py3-none-any.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.
Files changed (212) hide show
  1. pgsi_analyzer/__init__.py +24 -0
  2. pgsi_analyzer/benchmark/__init__.py +19 -0
  3. pgsi_analyzer/benchmark/builder.py +265 -0
  4. pgsi_analyzer/benchmark/executor.py +505 -0
  5. pgsi_analyzer/benchmark/orchestrator.py +413 -0
  6. pgsi_analyzer/benchmark/provider.py +128 -0
  7. pgsi_analyzer/benchmark/results_collector.py +109 -0
  8. pgsi_analyzer/benchmarks/__init__.py +15 -0
  9. pgsi_analyzer/benchmarks/binary-trees/README.md +85 -0
  10. pgsi_analyzer/benchmarks/binary-trees/cpython/main.py +104 -0
  11. pgsi_analyzer/benchmarks/binary-trees/ctypes/binary_tree.c +37 -0
  12. pgsi_analyzer/benchmarks/binary-trees/ctypes/main.py +73 -0
  13. pgsi_analyzer/benchmarks/binary-trees/cython/main.py +19 -0
  14. pgsi_analyzer/benchmarks/binary-trees/cython/raw.c +8855 -0
  15. pgsi_analyzer/benchmarks/binary-trees/cython/raw.pyx +88 -0
  16. pgsi_analyzer/benchmarks/binary-trees/cython/setup.py +9 -0
  17. pgsi_analyzer/benchmarks/binary-trees/py_compile/__compiler.py +8 -0
  18. pgsi_analyzer/benchmarks/binary-trees/py_compile/main.py +105 -0
  19. pgsi_analyzer/benchmarks/binary-trees/pypy/main.py +105 -0
  20. pgsi_analyzer/benchmarks/discovery.py +123 -0
  21. pgsi_analyzer/benchmarks/fannkuch-redux/README.md +116 -0
  22. pgsi_analyzer/benchmarks/fannkuch-redux/cpython/main.py +96 -0
  23. pgsi_analyzer/benchmarks/fannkuch-redux/ctypes/fannkuch.c +68 -0
  24. pgsi_analyzer/benchmarks/fannkuch-redux/ctypes/main.py +51 -0
  25. pgsi_analyzer/benchmarks/fannkuch-redux/cython/main.py +28 -0
  26. pgsi_analyzer/benchmarks/fannkuch-redux/cython/raw.c +13311 -0
  27. pgsi_analyzer/benchmarks/fannkuch-redux/cython/raw.pyx +58 -0
  28. pgsi_analyzer/benchmarks/fannkuch-redux/cython/setup.py +17 -0
  29. pgsi_analyzer/benchmarks/fannkuch-redux/py_compile/__compiler.py +8 -0
  30. pgsi_analyzer/benchmarks/fannkuch-redux/py_compile/main.py +96 -0
  31. pgsi_analyzer/benchmarks/fannkuch-redux/pypy/main.py +96 -0
  32. pgsi_analyzer/benchmarks/fasta/README.md +61 -0
  33. pgsi_analyzer/benchmarks/fasta/cpython/main.py +171 -0
  34. pgsi_analyzer/benchmarks/fasta/ctypes/fasta.c +99 -0
  35. pgsi_analyzer/benchmarks/fasta/ctypes/main.py +85 -0
  36. pgsi_analyzer/benchmarks/fasta/cython/main.py +48 -0
  37. pgsi_analyzer/benchmarks/fasta/cython/raw.c +14552 -0
  38. pgsi_analyzer/benchmarks/fasta/cython/raw.pyx +103 -0
  39. pgsi_analyzer/benchmarks/fasta/cython/setup.py +17 -0
  40. pgsi_analyzer/benchmarks/fasta/py_compile/__compiler.py +8 -0
  41. pgsi_analyzer/benchmarks/fasta/py_compile/main.py +171 -0
  42. pgsi_analyzer/benchmarks/fasta/pypy/main.py +172 -0
  43. pgsi_analyzer/benchmarks/hanoi/README.md +33 -0
  44. pgsi_analyzer/benchmarks/hanoi/cpython/main.py +91 -0
  45. pgsi_analyzer/benchmarks/hanoi/ctypes/hanoi.c +20 -0
  46. pgsi_analyzer/benchmarks/hanoi/ctypes/main.py +62 -0
  47. pgsi_analyzer/benchmarks/hanoi/cython/main.py +54 -0
  48. pgsi_analyzer/benchmarks/hanoi/cython/raw.c +8536 -0
  49. pgsi_analyzer/benchmarks/hanoi/cython/raw.pyx +22 -0
  50. pgsi_analyzer/benchmarks/hanoi/cython/setup.py +9 -0
  51. pgsi_analyzer/benchmarks/hanoi/py_compile/__compailer.py +8 -0
  52. pgsi_analyzer/benchmarks/hanoi/py_compile/main.py +91 -0
  53. pgsi_analyzer/benchmarks/hanoi/pypy/main.py +91 -0
  54. pgsi_analyzer/benchmarks/k-nucleotide/README.md +93 -0
  55. pgsi_analyzer/benchmarks/k-nucleotide/cpython/main.py +87 -0
  56. pgsi_analyzer/benchmarks/k-nucleotide/ctypes/kmer_counter.c +67 -0
  57. pgsi_analyzer/benchmarks/k-nucleotide/ctypes/main.py +101 -0
  58. pgsi_analyzer/benchmarks/k-nucleotide/cython/main.py +70 -0
  59. pgsi_analyzer/benchmarks/k-nucleotide/cython/raw.c +8560 -0
  60. pgsi_analyzer/benchmarks/k-nucleotide/cython/raw.pyx +35 -0
  61. pgsi_analyzer/benchmarks/k-nucleotide/cython/setup.py +9 -0
  62. pgsi_analyzer/benchmarks/k-nucleotide/dna.txt +8 -0
  63. pgsi_analyzer/benchmarks/k-nucleotide/py_compile/__compiler.py +8 -0
  64. pgsi_analyzer/benchmarks/k-nucleotide/py_compile/main.py +87 -0
  65. pgsi_analyzer/benchmarks/k-nucleotide/pypy/main.py +87 -0
  66. pgsi_analyzer/benchmarks/knn/README.md +47 -0
  67. pgsi_analyzer/benchmarks/knn/cpython/main.py +105 -0
  68. pgsi_analyzer/benchmarks/knn/ctypes/knn.c +43 -0
  69. pgsi_analyzer/benchmarks/knn/ctypes/main.py +76 -0
  70. pgsi_analyzer/benchmarks/knn/cython/main.py +49 -0
  71. pgsi_analyzer/benchmarks/knn/cython/raw.c +30444 -0
  72. pgsi_analyzer/benchmarks/knn/cython/raw.pyx +55 -0
  73. pgsi_analyzer/benchmarks/knn/cython/setup.py +9 -0
  74. pgsi_analyzer/benchmarks/knn/py_compile/__compiler.py +8 -0
  75. pgsi_analyzer/benchmarks/knn/py_compile/main.py +117 -0
  76. pgsi_analyzer/benchmarks/knn/pypy/main.py +117 -0
  77. pgsi_analyzer/benchmarks/mandelbrot/README.md +40 -0
  78. pgsi_analyzer/benchmarks/mandelbrot/cpython/main.py +127 -0
  79. pgsi_analyzer/benchmarks/mandelbrot/ctypes/main.py +70 -0
  80. pgsi_analyzer/benchmarks/mandelbrot/ctypes/mandelbrot.c +33 -0
  81. pgsi_analyzer/benchmarks/mandelbrot/cython/main.py +48 -0
  82. pgsi_analyzer/benchmarks/mandelbrot/cython/raw.c +8122 -0
  83. pgsi_analyzer/benchmarks/mandelbrot/cython/raw.pyx +36 -0
  84. pgsi_analyzer/benchmarks/mandelbrot/cython/setup.py +9 -0
  85. pgsi_analyzer/benchmarks/mandelbrot/py_compile/__compiler.py +8 -0
  86. pgsi_analyzer/benchmarks/mandelbrot/py_compile/main.py +126 -0
  87. pgsi_analyzer/benchmarks/mandelbrot/pypy/main.py +128 -0
  88. pgsi_analyzer/benchmarks/n-body/README.md +57 -0
  89. pgsi_analyzer/benchmarks/n-body/cpython/main.py +181 -0
  90. pgsi_analyzer/benchmarks/n-body/ctypes/main.py +100 -0
  91. pgsi_analyzer/benchmarks/n-body/ctypes/nbody.c +84 -0
  92. pgsi_analyzer/benchmarks/n-body/cython/main.py +76 -0
  93. pgsi_analyzer/benchmarks/n-body/cython/raw.c +30596 -0
  94. pgsi_analyzer/benchmarks/n-body/cython/raw.pyx +90 -0
  95. pgsi_analyzer/benchmarks/n-body/cython/setup.py +9 -0
  96. pgsi_analyzer/benchmarks/n-body/py_compile/__compiler.py +8 -0
  97. pgsi_analyzer/benchmarks/n-body/py_compile/main.py +180 -0
  98. pgsi_analyzer/benchmarks/n-body/pypy/main.py +180 -0
  99. pgsi_analyzer/benchmarks/n-queens/README.md +44 -0
  100. pgsi_analyzer/benchmarks/n-queens/cpython/main.py +131 -0
  101. pgsi_analyzer/benchmarks/n-queens/ctypes/main.py +66 -0
  102. pgsi_analyzer/benchmarks/n-queens/ctypes/n_queens.c +76 -0
  103. pgsi_analyzer/benchmarks/n-queens/cython/main.py +52 -0
  104. pgsi_analyzer/benchmarks/n-queens/cython/raw.c +27786 -0
  105. pgsi_analyzer/benchmarks/n-queens/cython/raw.pyx +64 -0
  106. pgsi_analyzer/benchmarks/n-queens/cython/setup.py +9 -0
  107. pgsi_analyzer/benchmarks/n-queens/py_compile/__compailer.py +8 -0
  108. pgsi_analyzer/benchmarks/n-queens/py_compile/main.py +131 -0
  109. pgsi_analyzer/benchmarks/n-queens/pypy/main.py +131 -0
  110. pgsi_analyzer/benchmarks/pi-digits/README.md +56 -0
  111. pgsi_analyzer/benchmarks/pi-digits/cpython/main.py +59 -0
  112. pgsi_analyzer/benchmarks/pi-digits/ctypes/main.py +52 -0
  113. pgsi_analyzer/benchmarks/pi-digits/ctypes/pi_gauss_legendre.c +19 -0
  114. pgsi_analyzer/benchmarks/pi-digits/cython/main.py +30 -0
  115. pgsi_analyzer/benchmarks/pi-digits/cython/raw.c +8000 -0
  116. pgsi_analyzer/benchmarks/pi-digits/cython/raw.pyx +28 -0
  117. pgsi_analyzer/benchmarks/pi-digits/cython/setup.py +9 -0
  118. pgsi_analyzer/benchmarks/pi-digits/py_compile/__compiler.py +8 -0
  119. pgsi_analyzer/benchmarks/pi-digits/py_compile/main.py +59 -0
  120. pgsi_analyzer/benchmarks/pi-digits/pypy/main.py +59 -0
  121. pgsi_analyzer/benchmarks/regex-redux/README.md +97 -0
  122. pgsi_analyzer/benchmarks/regex-redux/cpython/input_fasta.txt +2 -0
  123. pgsi_analyzer/benchmarks/regex-redux/cpython/main.py +116 -0
  124. pgsi_analyzer/benchmarks/regex-redux/ctypes/input_fasta.txt +2 -0
  125. pgsi_analyzer/benchmarks/regex-redux/ctypes/main.py +71 -0
  126. pgsi_analyzer/benchmarks/regex-redux/ctypes/regex_redux.c +110 -0
  127. pgsi_analyzer/benchmarks/regex-redux/cython/input_fasta.txt +2 -0
  128. pgsi_analyzer/benchmarks/regex-redux/cython/main.py +68 -0
  129. pgsi_analyzer/benchmarks/regex-redux/cython/raw.c +9999 -0
  130. pgsi_analyzer/benchmarks/regex-redux/cython/raw.pyx +84 -0
  131. pgsi_analyzer/benchmarks/regex-redux/cython/setup.py +9 -0
  132. pgsi_analyzer/benchmarks/regex-redux/py_compile/__compiler.py +8 -0
  133. pgsi_analyzer/benchmarks/regex-redux/py_compile/input_fasta.txt +2 -0
  134. pgsi_analyzer/benchmarks/regex-redux/py_compile/main.py +116 -0
  135. pgsi_analyzer/benchmarks/regex-redux/pypy/input_fasta.txt +2 -0
  136. pgsi_analyzer/benchmarks/regex-redux/pypy/main.py +113 -0
  137. pgsi_analyzer/benchmarks/registry.py +210 -0
  138. pgsi_analyzer/benchmarks/reverse-complement/README.md +93 -0
  139. pgsi_analyzer/benchmarks/reverse-complement/cpython/main.py +56 -0
  140. pgsi_analyzer/benchmarks/reverse-complement/ctypes/main.py +47 -0
  141. pgsi_analyzer/benchmarks/reverse-complement/ctypes/reverse_comlement.c +28 -0
  142. pgsi_analyzer/benchmarks/reverse-complement/cython/main.py +26 -0
  143. pgsi_analyzer/benchmarks/reverse-complement/cython/raw.c +8364 -0
  144. pgsi_analyzer/benchmarks/reverse-complement/cython/raw.pyx +39 -0
  145. pgsi_analyzer/benchmarks/reverse-complement/cython/setup.py +9 -0
  146. pgsi_analyzer/benchmarks/reverse-complement/py_compile/__compiler.py +8 -0
  147. pgsi_analyzer/benchmarks/reverse-complement/py_compile/main.py +56 -0
  148. pgsi_analyzer/benchmarks/reverse-complement/pypy/main.py +56 -0
  149. pgsi_analyzer/benchmarks/sieve/README.md +67 -0
  150. pgsi_analyzer/benchmarks/sieve/cpython/main.py +60 -0
  151. pgsi_analyzer/benchmarks/sieve/ctypes/main.py +53 -0
  152. pgsi_analyzer/benchmarks/sieve/ctypes/sieve.c +54 -0
  153. pgsi_analyzer/benchmarks/sieve/cython/main.py +24 -0
  154. pgsi_analyzer/benchmarks/sieve/cython/raw.c +8828 -0
  155. pgsi_analyzer/benchmarks/sieve/cython/raw.pyx +38 -0
  156. pgsi_analyzer/benchmarks/sieve/cython/setup.py +9 -0
  157. pgsi_analyzer/benchmarks/sieve/py_compile/__compiler.py +8 -0
  158. pgsi_analyzer/benchmarks/sieve/py_compile/main.py +60 -0
  159. pgsi_analyzer/benchmarks/sieve/pypy/main.py +60 -0
  160. pgsi_analyzer/benchmarks/spectral-norm/README.md +72 -0
  161. pgsi_analyzer/benchmarks/spectral-norm/cpython/main.py +83 -0
  162. pgsi_analyzer/benchmarks/spectral-norm/ctypes/main.py +49 -0
  163. pgsi_analyzer/benchmarks/spectral-norm/ctypes/spectralnorm.c +71 -0
  164. pgsi_analyzer/benchmarks/spectral-norm/cython/main.py +27 -0
  165. pgsi_analyzer/benchmarks/spectral-norm/cython/raw.c +9385 -0
  166. pgsi_analyzer/benchmarks/spectral-norm/cython/raw.pyx +57 -0
  167. pgsi_analyzer/benchmarks/spectral-norm/cython/setup.py +9 -0
  168. pgsi_analyzer/benchmarks/spectral-norm/py_compile/__compiler.py +8 -0
  169. pgsi_analyzer/benchmarks/spectral-norm/py_compile/main.py +83 -0
  170. pgsi_analyzer/benchmarks/spectral-norm/pypy/main.py +83 -0
  171. pgsi_analyzer/benchmarks/strassen/README.md +113 -0
  172. pgsi_analyzer/benchmarks/strassen/cpython/main.py +101 -0
  173. pgsi_analyzer/benchmarks/strassen/ctypes/main.py +83 -0
  174. pgsi_analyzer/benchmarks/strassen/ctypes/strassen.c +91 -0
  175. pgsi_analyzer/benchmarks/strassen/cython/main.py +53 -0
  176. pgsi_analyzer/benchmarks/strassen/cython/raw.c +10465 -0
  177. pgsi_analyzer/benchmarks/strassen/cython/raw.pyx +71 -0
  178. pgsi_analyzer/benchmarks/strassen/cython/setup.py +9 -0
  179. pgsi_analyzer/benchmarks/strassen/py_compile/__compiler.py +8 -0
  180. pgsi_analyzer/benchmarks/strassen/py_compile/main.py +101 -0
  181. pgsi_analyzer/benchmarks/strassen/pypy/main.py +102 -0
  182. pgsi_analyzer/benchmarks/template.py +349 -0
  183. pgsi_analyzer/cli/__init__.py +10 -0
  184. pgsi_analyzer/cli/main.py +478 -0
  185. pgsi_analyzer/config/cpu_power.csv +4904 -0
  186. pgsi_analyzer/config/cpu_power.source.json +15 -0
  187. pgsi_analyzer/config.py +300 -0
  188. pgsi_analyzer/gui/__init__.py +8 -0
  189. pgsi_analyzer/gui/app.py +1105 -0
  190. pgsi_analyzer/measurement/__init__.py +15 -0
  191. pgsi_analyzer/measurement/cpu_power_resolver.py +215 -0
  192. pgsi_analyzer/measurement/energy.py +296 -0
  193. pgsi_analyzer/measurement/estimators.py +315 -0
  194. pgsi_analyzer/measurement/time.py +138 -0
  195. pgsi_analyzer/models/__init__.py +24 -0
  196. pgsi_analyzer/models/aggregation.py +246 -0
  197. pgsi_analyzer/models/carbon.py +85 -0
  198. pgsi_analyzer/models/combination.py +201 -0
  199. pgsi_analyzer/models/greenscore.py +190 -0
  200. pgsi_analyzer/platform/__init__.py +26 -0
  201. pgsi_analyzer/platform/detection.py +102 -0
  202. pgsi_analyzer/platform/hardware.py +202 -0
  203. pgsi_analyzer/platform/paths.py +94 -0
  204. pgsi_analyzer/utils/__init__.py +37 -0
  205. pgsi_analyzer/utils/errors.py +28 -0
  206. pgsi_analyzer/utils/validation.py +46 -0
  207. pgsi_analyzer-1.0.0.dist-info/METADATA +552 -0
  208. pgsi_analyzer-1.0.0.dist-info/RECORD +212 -0
  209. pgsi_analyzer-1.0.0.dist-info/WHEEL +5 -0
  210. pgsi_analyzer-1.0.0.dist-info/entry_points.txt +3 -0
  211. pgsi_analyzer-1.0.0.dist-info/licenses/LICENSE +21 -0
  212. pgsi_analyzer-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,24 @@
1
+ """
2
+ pgsi-analyzer: A comprehensive benchmarking and analysis tool for comparing
3
+ energy consumption, execution time, and carbon footprint of Python execution methods.
4
+
5
+ This package provides tools for measuring and analyzing the sustainability
6
+ of different Python execution methods using the GreenScore metric.
7
+ """
8
+
9
+ __version__ = "1.0.0"
10
+ __author__ = "Md Fatin Shadab Turja"
11
+ __license__ = "MIT"
12
+
13
+ # Package metadata
14
+ __all__ = [
15
+ "__version__",
16
+ "__author__",
17
+ "__license__",
18
+ ]
19
+
20
+ # Top-level imports will be added as modules are implemented
21
+ # from .measurement import measure_energy_to_csv, measure_time_to_csv
22
+ # from .models import calculate_greenscore, calculate_carbon_footprint
23
+ # from .platform import detect_platform, get_system_info
24
+
@@ -0,0 +1,19 @@
1
+ """
2
+ Benchmark execution framework for pgsi-analyzer.
3
+
4
+ This module provides tools for building, executing, and orchestrating
5
+ benchmark runs across multiple Python execution methods.
6
+ """
7
+
8
+ from .builder import build_benchmark, requires_build
9
+ from .executor import execute_benchmark
10
+ from .orchestrator import run_benchmark_suite
11
+ from .provider import FileSystemProvider
12
+
13
+ __all__ = [
14
+ "build_benchmark",
15
+ "requires_build",
16
+ "execute_benchmark",
17
+ "run_benchmark_suite",
18
+ "FileSystemProvider",
19
+ ]
@@ -0,0 +1,265 @@
1
+ """
2
+ Benchmark build system for Cython and ctypes benchmarks.
3
+
4
+ Handles compilation of Cython extensions and C shared libraries
5
+ before benchmark execution. Compilation is done separately from
6
+ measurement to ensure only execution time/energy is measured.
7
+ """
8
+
9
+ import subprocess
10
+ import sys
11
+ import platform
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ from ..utils import ConfigurationError, PlatformError
16
+ from ..config import ToolPaths
17
+
18
+
19
+ def requires_build(method: str) -> bool:
20
+ """
21
+ Check if a method requires compilation before execution.
22
+
23
+ Args:
24
+ method: Execution method name
25
+
26
+ Returns:
27
+ True if method requires build step
28
+ """
29
+ return method in ("cython", "ctypes")
30
+
31
+
32
+ def build_cython(
33
+ benchmark_path: Path,
34
+ build_dir: Optional[Path] = None,
35
+ tool_paths: Optional[ToolPaths] = None,
36
+ ) -> Path:
37
+ """
38
+ Build Cython extension module.
39
+
40
+ Runs: python setup.py build_ext --inplace
41
+
42
+ Args:
43
+ benchmark_path: Path to Cython benchmark directory (contains setup.py)
44
+ build_dir: Optional directory for build artifacts (defaults to benchmark_path)
45
+ tool_paths: Optional ToolPaths configuration for Python executable
46
+
47
+ Returns:
48
+ Path to benchmark directory (build artifacts are in-place)
49
+
50
+ Raises:
51
+ ConfigurationError: If setup.py not found or build fails
52
+ """
53
+ if build_dir is None:
54
+ build_dir = benchmark_path
55
+
56
+ setup_py = benchmark_path / "setup.py"
57
+ if not setup_py.exists():
58
+ raise ConfigurationError(f"setup.py not found in {benchmark_path}")
59
+
60
+ # Use configured Python or default
61
+ python_exe = str(tool_paths.python) if tool_paths else sys.executable
62
+
63
+ # Change to benchmark directory for build
64
+ original_cwd = Path.cwd()
65
+
66
+ try:
67
+ # Build extension in-place
68
+ result = subprocess.run(
69
+ [python_exe, "setup.py", "build_ext", "--inplace"],
70
+ cwd=str(benchmark_path),
71
+ capture_output=True,
72
+ text=True,
73
+ timeout=300, # 5 minute timeout for compilation
74
+ )
75
+
76
+ if result.returncode != 0:
77
+ raise ConfigurationError(
78
+ f"Cython build failed for {benchmark_path}:\n"
79
+ f"stdout: {result.stdout}\n"
80
+ f"stderr: {result.stderr}"
81
+ )
82
+
83
+ return benchmark_path
84
+
85
+ except subprocess.TimeoutExpired:
86
+ raise ConfigurationError(f"Cython build timed out for {benchmark_path}")
87
+ except Exception as e:
88
+ raise ConfigurationError(f"Cython build error for {benchmark_path}: {e}")
89
+ finally:
90
+ # Restore original working directory
91
+ import os
92
+ os.chdir(str(original_cwd))
93
+
94
+
95
+ def build_ctypes(
96
+ benchmark_path: Path,
97
+ build_dir: Optional[Path] = None,
98
+ tool_paths: Optional[ToolPaths] = None,
99
+ ) -> Path:
100
+ """
101
+ Build C shared library for ctypes benchmark.
102
+
103
+ Compiles .c files to .so (Linux) or .dll (Windows).
104
+
105
+ Args:
106
+ benchmark_path: Path to ctypes benchmark directory (contains .c files)
107
+ build_dir: Optional directory for build artifacts (defaults to benchmark_path)
108
+ tool_paths: Optional ToolPaths configuration for C compiler
109
+
110
+ Returns:
111
+ Path to benchmark directory (shared library is in-place)
112
+
113
+ Raises:
114
+ ConfigurationError: If compilation fails
115
+ PlatformError: If C compiler not available
116
+ """
117
+ if build_dir is None:
118
+ build_dir = benchmark_path
119
+
120
+ # Find .c files (sorted for deterministic library name)
121
+ c_files = sorted(benchmark_path.glob("*.c"))
122
+ if not c_files:
123
+ raise ConfigurationError(f"No .c files found in {benchmark_path}")
124
+
125
+ # Determine output library name and extension
126
+ if platform.system() == "Windows":
127
+ lib_ext = ".dll"
128
+ # Use first .c file name as base
129
+ lib_name = c_files[0].stem
130
+ else:
131
+ lib_ext = ".so"
132
+ lib_name = f"lib{c_files[0].stem}"
133
+
134
+ lib_path = benchmark_path / f"{lib_name}{lib_ext}"
135
+
136
+ # Check if already compiled and up-to-date
137
+ if lib_path.exists():
138
+ # Check if .c files are newer than library
139
+ c_newer = any(c.stat().st_mtime > lib_path.stat().st_mtime for c in c_files)
140
+ if not c_newer:
141
+ return benchmark_path # Already built and up-to-date
142
+
143
+ # Determine compiler
144
+ if tool_paths and tool_paths.c_compiler:
145
+ compiler_exe = str(tool_paths.c_compiler)
146
+ else:
147
+ # Fallback to auto-detection
148
+ if platform.system() == "Windows":
149
+ compiler_exe = "gcc" # Will try cl.exe if gcc fails
150
+ else:
151
+ compiler_exe = "gcc"
152
+
153
+ # Compile command
154
+ if platform.system() == "Windows":
155
+ # Windows: use configured compiler or try gcc first, then cl.exe
156
+ compile_cmd = [
157
+ compiler_exe,
158
+ "-shared",
159
+ "-o", str(lib_path),
160
+ *[str(c) for c in c_files],
161
+ "-fPIC",
162
+ ]
163
+ else:
164
+ # Linux/macOS: use gcc
165
+ compile_cmd = [
166
+ compiler_exe,
167
+ "-shared",
168
+ "-fPIC",
169
+ "-o", str(lib_path),
170
+ *[str(c) for c in c_files],
171
+ ]
172
+
173
+ try:
174
+ result = subprocess.run(
175
+ compile_cmd,
176
+ cwd=str(benchmark_path),
177
+ capture_output=True,
178
+ text=True,
179
+ timeout=300, # 5 minute timeout
180
+ )
181
+
182
+ if result.returncode != 0:
183
+ # If configured compiler failed, try fallback detection
184
+ if tool_paths and tool_paths.c_compiler:
185
+ # User configured compiler failed
186
+ raise ConfigurationError(
187
+ f"ctypes compilation failed with configured compiler '{compiler_exe}':\n"
188
+ f"Command: {' '.join(compile_cmd)}\n"
189
+ f"stdout: {result.stdout}\n"
190
+ f"stderr: {result.stderr}"
191
+ )
192
+
193
+ # Try alternative: check if gcc is available
194
+ gcc_check = subprocess.run(
195
+ ["gcc", "--version"],
196
+ capture_output=True,
197
+ text=True,
198
+ )
199
+ if gcc_check.returncode != 0:
200
+ raise PlatformError(
201
+ "C compiler not found. Configure PGSI_CC_PATH, use --cc-path, "
202
+ "or install gcc/cl.exe to build ctypes benchmarks."
203
+ )
204
+
205
+ raise ConfigurationError(
206
+ f"ctypes compilation failed for {benchmark_path}:\n"
207
+ f"Command: {' '.join(compile_cmd)}\n"
208
+ f"stdout: {result.stdout}\n"
209
+ f"stderr: {result.stderr}"
210
+ )
211
+
212
+ if not lib_path.exists():
213
+ raise ConfigurationError(
214
+ f"Compilation succeeded but library not found: {lib_path}"
215
+ )
216
+
217
+ return benchmark_path
218
+
219
+ except subprocess.TimeoutExpired:
220
+ raise ConfigurationError(f"ctypes compilation timed out for {benchmark_path}")
221
+ except FileNotFoundError:
222
+ raise PlatformError(
223
+ "C compiler not found. Configure PGSI_CC_PATH, use --cc-path, "
224
+ "or install gcc/cl.exe to build ctypes benchmarks."
225
+ )
226
+ except Exception as e:
227
+ raise ConfigurationError(f"ctypes compilation error for {benchmark_path}: {e}")
228
+
229
+
230
+ def build_benchmark(
231
+ algorithm: str,
232
+ method: str,
233
+ benchmark_path: Path,
234
+ tool_paths: Optional[ToolPaths] = None,
235
+ ) -> Path:
236
+ """
237
+ Build a benchmark if it requires compilation.
238
+
239
+ Args:
240
+ algorithm: Algorithm name (for error messages)
241
+ method: Execution method
242
+ benchmark_path: Path to benchmark directory
243
+ tool_paths: Optional ToolPaths configuration
244
+
245
+ Returns:
246
+ Path to benchmark directory (ready for execution)
247
+
248
+ Raises:
249
+ ConfigurationError: If build fails
250
+ PlatformError: If build tools not available
251
+ """
252
+ if not requires_build(method):
253
+ return benchmark_path # No build needed
254
+
255
+ # Compatibility guard: older registries/discovery may resolve build-based
256
+ # methods to .../main.py. Build routines require the method directory.
257
+ if benchmark_path.is_file() and benchmark_path.name == "main.py":
258
+ benchmark_path = benchmark_path.parent
259
+
260
+ if method == "cython":
261
+ return build_cython(benchmark_path, tool_paths=tool_paths)
262
+ elif method == "ctypes":
263
+ return build_ctypes(benchmark_path, tool_paths=tool_paths)
264
+ else:
265
+ raise ValueError(f"Unknown build method: {method}")