Skip to content

Commit 4b1e73b

Browse files
authored
Refactor de_export.py, extract model compilation (#2315)
Move model compilation to a free function in a separate module. Easier to test and more reusable for recompilation after initial import. Related to #2306
1 parent 780d767 commit 4b1e73b

File tree

2 files changed

+87
-66
lines changed

2 files changed

+87
-66
lines changed

python/sdist/amici/compile.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Functionality for building the C++ extensions of an amici-created model
3+
package.
4+
"""
5+
import subprocess
6+
import sys
7+
from typing import Optional, Union
8+
from pathlib import Path
9+
import os
10+
11+
12+
def build_model_extension(
13+
package_dir: Union[str, Path],
14+
verbose: Optional[Union[bool, int]] = False,
15+
compiler: Optional[str] = None,
16+
extra_msg: Optional[str] = None,
17+
) -> None:
18+
"""
19+
Compile the model extension of an amici-created model package.
20+
21+
:param package_dir:
22+
Directory of the model package to be compiled. I.e., the directory
23+
containing the `setup.py` file.
24+
25+
:param verbose:
26+
Make model compilation verbose.
27+
28+
:param compiler:
29+
Absolute path to the compiler executable to be used to build the Python
30+
extension, e.g. ``/usr/bin/clang``.
31+
32+
:param extra_msg:
33+
Additional message to be printed in case of a failed build.
34+
"""
35+
# setup.py assumes it is run from within the model directory
36+
package_dir = Path(package_dir)
37+
script_args = [sys.executable, package_dir / "setup.py"]
38+
39+
if verbose:
40+
script_args.append("--verbose")
41+
else:
42+
script_args.append("--quiet")
43+
44+
script_args.extend(
45+
[
46+
"build_ext",
47+
f"--build-lib={package_dir}",
48+
# This is generally not required, but helps to reduce the path
49+
# length of intermediate build files, that may easily become
50+
# problematic on Windows, due to its ridiculous 255-character path
51+
# length limit.
52+
f'--build-temp={package_dir / "build"}',
53+
]
54+
)
55+
56+
env = os.environ.copy()
57+
if compiler is not None:
58+
# CMake will use the compiler specified in the CXX environment variable
59+
env["CXX"] = compiler
60+
61+
# distutils.core.run_setup looks nicer, but does not let us check the
62+
# result easily
63+
try:
64+
result = subprocess.run(
65+
script_args,
66+
cwd=str(package_dir),
67+
stdout=subprocess.PIPE,
68+
stderr=subprocess.STDOUT,
69+
check=True,
70+
env=env,
71+
)
72+
except subprocess.CalledProcessError as e:
73+
print(e.output.decode("utf-8"))
74+
print("Failed building the model extension.")
75+
if extra_msg:
76+
print(f"Note: {extra_msg}")
77+
raise
78+
79+
if verbose:
80+
print(result.stdout.decode("utf-8"))

python/sdist/amici/de_export.py

Lines changed: 7 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
import os
1616
import re
1717
import shutil
18-
import subprocess
19-
import sys
2018
from dataclasses import dataclass
2119
from itertools import chain
2220
from pathlib import Path
@@ -61,6 +59,7 @@
6159
_default_simplify,
6260
)
6361
from .logging import get_logger, log_execution_time, set_log_level
62+
from .compile import build_model_extension
6463
from .sympy_utils import (
6564
_custom_pow_eval_derivative,
6665
_monkeypatched,
@@ -2893,7 +2892,12 @@ def compile_model(self) -> None:
28932892
"""
28942893
Compiles the generated code it into a simulatable module
28952894
"""
2896-
self._compile_c_code(compiler=self.compiler, verbose=self.verbose)
2895+
build_model_extension(
2896+
package_dir=self.model_path,
2897+
compiler=self.compiler,
2898+
verbose=self.verbose,
2899+
extra_msg="\n".join(self._build_hints),
2900+
)
28972901

28982902
def _prepare_model_folder(self) -> None:
28992903
"""
@@ -2950,69 +2954,6 @@ def _generate_c_code(self) -> None:
29502954
CXX_MAIN_TEMPLATE_FILE, os.path.join(self.model_path, "main.cpp")
29512955
)
29522956

2953-
def _compile_c_code(
2954-
self,
2955-
verbose: Optional[Union[bool, int]] = False,
2956-
compiler: Optional[str] = None,
2957-
) -> None:
2958-
"""
2959-
Compile the generated model code
2960-
2961-
:param verbose:
2962-
Make model compilation verbose
2963-
2964-
:param compiler:
2965-
Absolute path to the compiler executable to be used to build the Python
2966-
extension, e.g. ``/usr/bin/clang``.
2967-
"""
2968-
# setup.py assumes it is run from within the model directory
2969-
module_dir = self.model_path
2970-
script_args = [sys.executable, os.path.join(module_dir, "setup.py")]
2971-
2972-
if verbose:
2973-
script_args.append("--verbose")
2974-
else:
2975-
script_args.append("--quiet")
2976-
2977-
script_args.extend(
2978-
[
2979-
"build_ext",
2980-
f"--build-lib={module_dir}",
2981-
# This is generally not required, but helps to reduce the path
2982-
# length of intermediate build files, that may easily become
2983-
# problematic on Windows, due to its ridiculous 255-character path
2984-
# length limit.
2985-
f'--build-temp={Path(module_dir, "build")}',
2986-
]
2987-
)
2988-
2989-
env = os.environ.copy()
2990-
if compiler is not None:
2991-
# CMake will use the compiler specified in the CXX environment variable
2992-
env["CXX"] = compiler
2993-
2994-
# distutils.core.run_setup looks nicer, but does not let us check the
2995-
# result easily
2996-
try:
2997-
result = subprocess.run(
2998-
script_args,
2999-
cwd=module_dir,
3000-
stdout=subprocess.PIPE,
3001-
stderr=subprocess.STDOUT,
3002-
check=True,
3003-
env=env,
3004-
)
3005-
except subprocess.CalledProcessError as e:
3006-
print(e.output.decode("utf-8"))
3007-
print("Failed building the model extension.")
3008-
if self._build_hints:
3009-
print("Note:")
3010-
print("\n".join(self._build_hints))
3011-
raise
3012-
3013-
if verbose:
3014-
print(result.stdout.decode("utf-8"))
3015-
30162957
def _generate_m_code(self) -> None:
30172958
"""
30182959
Create a Matlab script for compiling code files to a mex file

0 commit comments

Comments
 (0)