Source code for gillespy2.solvers.cpp.build.make

# GillesPy2 is a modeling toolkit for biochemical simulation.
# Copyright (C) 2019-2023 GillesPy2 developers.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import subprocess

from importlib.util import find_spec
from pathlib import Path
import shutil
import sys

from gillespy2.core import log
from gillespy2.core import gillespyError
from gillespy2.core import BuildError

[docs]class Make(): def __init__(self, makefile: str, output_dir: str, obj_dir: str = None, template_dir: str = None): self.makefile = Path(makefile).resolve() self.self_dir = Path(__file__).parent self.cbase_dir = self.self_dir.joinpath("../c_base").resolve() self.output_dir = Path(output_dir).resolve() # obj_dir is, presumably, only supplied if caching is enabled. # If not supplied, it should be assumed ethereal and cleaned up # with the rest of the tmp directory. if obj_dir is None: self.obj_dir = self.output_dir.joinpath("gillespy2_obj").resolve() else: self.obj_dir = Path(obj_dir).resolve() if template_dir is None: self.template_dir = self.output_dir.joinpath("gillespy2_template").resolve() else: self.template_dir = Path(template_dir).resolve() for path in [self.output_dir, self.obj_dir]: if path.is_file(): raise gillespyError.DirectoryError( f"Error while attempting to open directory:\n" f"- {path} is actually a file." ) if not path.is_dir(): path.mkdir() self.output_file = "GillesPy2_Simulation.exe" self.output_file = Path(self.output_dir, self.output_file) # SCons can either be an executable or a Python package. scons_exe = shutil.which("scons") if scons_exe is None: self.scons_cmd = [str(Path(sys.executable).resolve()), "-m", "SCons"] elif find_spec("SCons") is not None: self.scons_cmd = [str(Path(scons_exe).resolve())] else: raise BuildError("SCons must be installed in order to compile solver with C++")
[docs] def prebuild(self): self.__execute("build")
[docs] def build_simulation(self, simulation_name: str, **kwargs): self.__execute(simulation_name, **kwargs)
def __execute(self, target: str, **kwargs): # Default make arguments. args_dict = { "cbase_dir": str(self.cbase_dir.resolve()), "obj_dir": str(self.obj_dir.resolve()), "template_dir": str(self.template_dir.resolve()), "solver": str(target), "output_file": str(self.output_file) } # Overwrite keys supplied in **kwargs. for key, value in kwargs.items(): args_dict[key] = value # Create the emake arguments. Note, arguments are UPPERCASE. make_args = [(f"{key.upper()}={value}") for key, value in args_dict.items()] # Create the make command. make_cmd = [*self.scons_cmd, f"-C{str(self.output_dir.resolve())}", f"-f{str(self.makefile)}"] + make_args try: result = subprocess.run(make_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except KeyboardInterrupt: log.warning( f"Makefile was interrupted during execution of target: '{target}', unexpected behavior may occur.") if result.returncode == 0 and os.path.exists(self.output_file): return raise gillespyError.BuildError(f"Error encountered during execution of Makefile target: '{target}'.\n" f"Return code: {result.returncode}" f"- stdout: {result.stdout.decode('utf-8', errors='ignore')}\n" f"- stderr: {result.stderr.decode('utf-8', errors='ignore')}\n" f"- make_cmd: {' '.join(make_cmd)}\n" f"- os.listdir({os.path.join(self.cbase_dir,'template')}): {os.listdir(os.path.join(self.cbase_dir,'template'))}\n" f"- os.path.exists({self.output_file}): {os.path.exists(self.output_file)}\n")