Chaste  Release::2024.1
CellMLToSharedLibraryConverter.cpp
1 /*
2 
3 Copyright (c) 2005-2021, University of Oxford.
4 All rights reserved.
5 
6 University of Oxford means the Chancellor, Masters and Scholars of the
7 University of Oxford, having an administrative office at Wellington
8 Square, Oxford OX1 2JD, UK.
9 
10 This file is part of Chaste.
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions are met:
14  * Redistributions of source code must retain the above copyright notice,
15  this list of conditions and the following disclaimer.
16  * Redistributions in binary form must reproduce the above copyright notice,
17  this list of conditions and the following disclaimer in the documentation
18  and/or other materials provided with the distribution.
19  * Neither the name of the University of Oxford nor the names of its
20  contributors may be used to endorse or promote products derived from this
21  software without specific prior written permission.
22 
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 
34 */
35 #include "CellMLToSharedLibraryConverter.hpp"
36 
37 #include <sstream>
38 #include <fstream> // for std::ofstream
39 #include <sys/stat.h> // For mkdir()
40 #include <ctime>
41 #include <cstring> // For strerror()
42 #include <cerrno> // For errno
43 
44 #include <boost/foreach.hpp>
45 
46 #include "ChasteSyscalls.hpp"
47 #include "Exception.hpp"
48 #include "Warnings.hpp"
49 #include "ChasteBuildRoot.hpp"
50 #include "PetscTools.hpp"
51 #include "DynamicModelLoaderRegistry.hpp"
52 #include "GetCurrentWorkingDirectory.hpp"
53 
54 #define IGNORE_EXCEPTIONS(code) \
55  try { \
56  code; \
57  } catch (...) {}
58 
60 #ifdef __APPLE__
61 //Mac OSX
62 const std::string CellMLToSharedLibraryConverter::msSoSuffix = "dylib";
63 #else
64 //Normal behaviour
65 const std::string CellMLToSharedLibraryConverter::msSoSuffix = "so";
66 #endif
67 
69  std::string component)
70  : mPreserveGeneratedSources(preserveGeneratedSources),
71  mComponentName(component)
72 {
73 }
74 
75 DynamicCellModelLoaderPtr CellMLToSharedLibraryConverter::Convert(const FileFinder& rFilePath,
76  bool isCollective)
77 {
78  DynamicCellModelLoaderPtr p_loader;
79  std::string absolute_path = rFilePath.GetAbsolutePath();
80  // Find out whether rFilePath is a .cellml or .so
81  size_t dot_position = absolute_path.find_last_of(".");
82  if (dot_position == std::string::npos || absolute_path[dot_position+1] =='/')
83  {
84  // Either there is no dot in the absolute path or there is one along the path (/home/user/chaste/../temp/stuff)
85  EXCEPTION("File does not have an extension: " + absolute_path);
86  }
87  std::string extension = absolute_path.substr(dot_position+1);
88  // We make a modifiable version of the const FileFinder just incase we feel like
89  // amending the suffix
90  FileFinder file_path_copy(rFilePath);
91 #ifdef __APPLE__
92  if (extension == "so")
93  {
94  WARN_ONCE_ONLY("CellMLToSharedLibraryConverter asked to load a \".so\" file. On this architecture it should be \".dylib\"");
95  extension = "dylib";
96  absolute_path.replace(dot_position+1, 5, extension);
97  file_path_copy.SetPath(absolute_path, RelativeTo::Absolute);
98  }
99 #endif
100  // Check the file exists
101  if (!file_path_copy.Exists())
102  {
103  EXCEPTION("Dynamically loadable cell model '" + absolute_path + "' does not exist.");
104  }
105  if (extension == "cellml")
106  {
107  // Split the path into folder and leaf
108  size_t slash_position = absolute_path.find_last_of("/\\");
109  assert(slash_position != std::string::npos);
110  std::string folder = absolute_path.substr(0, slash_position+1); // Include trailing slash
111  std::string leaf = absolute_path.substr(slash_position+1, dot_position-slash_position); // Include dot
112  std::string so_path = folder + "lib" + leaf + msSoSuffix;
113  // Does the .so file already exist (and was it modified after the .cellml?)
114  FileFinder so_file(so_path, RelativeTo::Absolute);
115  if (!so_file.Exists() || rFilePath.IsNewerThan(so_file))
116  {
117  if (!isCollective)
118  {
119  EXCEPTION("Unable to convert .cellml to .so unless called collectively, due to possible race conditions.");
120  }
121  ConvertCellmlToSo(absolute_path, folder);
122  }
123  // Load the .so
124  p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(so_file);
125  }
126  else if (extension == msSoSuffix)
127  {
128  // Just load the .so
129  // Note that this may have been modified to .dylib
130  p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(file_path_copy);
131  }
132  else
133  {
134  EXCEPTION("Unsupported extension '." + extension + "' of file '" + absolute_path + "'; must be .so, .dylib or .cellml");
135  }
136 
137  return p_loader;
138 }
139 
140 void CellMLToSharedLibraryConverter::ConvertCellmlToSo(const std::string& rCellmlFullPath,
141  const std::string& rCellmlFolder)
142 {
143  FileFinder tmp_folder;
144  FileFinder build_folder;
145 
146  std::string old_cwd = GetCurrentWorkingDirectory();
147  // Check that the Chaste build tree exists
148  FileFinder chaste_root("", RelativeTo::ChasteBuildRoot);
149  FileFinder chaste_source("", RelativeTo::ChasteSourceRoot);
150 
151  if (!chaste_root.IsDir())
152  {
153  EXCEPTION("No Chaste build tree found at '" << chaste_root.GetAbsolutePath()
154  << "' - you need the source to use CellML models directly in Chaste.");
155  }
157  if (!component_dir.IsDir())
158  {
159  EXCEPTION("Unable to convert CellML model: required Chaste component '" << mComponentName
160  << "' does not exist in '" << chaste_root.GetAbsolutePath() << "'.");
161  }
162  // Try the conversion
163  try
164  {
165  // Need to create a .so file from the CellML...
166  if (PetscTools::AmMaster())
167  {
168  // Create a temporary folder within heart/dynamic
169  std::stringstream folder_name;
170  folder_name << "dynamic/tmp_" << getpid() << "_" << time(NULL);
171 
172 #ifdef CHASTE_CMAKE
173  tmp_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
174  build_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
175 #else
176  tmp_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
177  build_folder.SetPath(component_dir.GetAbsolutePath() + "/build/" + ChasteBuildDirName() + "/" + folder_name.str(), RelativeTo::Absolute);
178 #endif
179 
180  int ret = mkdir((tmp_folder.GetAbsolutePath()).c_str(), 0700);
181  if (ret != 0)
182  {
183  EXCEPTION("Failed to create temporary folder '" << tmp_folder.GetAbsolutePath() << "' for CellML conversion: "
184  << strerror(errno));
185  }
186 
187 
188  // Copy the .cellml file (and any relevant others) into the temporary folder
189  FileFinder cellml_file(rCellmlFullPath, RelativeTo::Absolute);
190  FileFinder cellml_folder = cellml_file.GetParent();
191  std::string cellml_leaf_name = cellml_file.GetLeafNameNoExtension();
192  std::vector<FileFinder> cellml_files = cellml_folder.FindMatches(cellml_leaf_name + "*");
193 
194  BOOST_FOREACH(const FileFinder& r_cellml_file, cellml_files)
195  {
196  r_cellml_file.CopyTo(tmp_folder);
197  }
198 
199 #ifdef CHASTE_CMAKE
200  std::string cmake_lists_filename = tmp_folder.GetAbsolutePath() + "/CMakeLists.txt";
201  std::ofstream cmake_lists_filestream(cmake_lists_filename.c_str());
202  cmake_lists_filestream << "cmake_minimum_required(VERSION 2.8.12)\n" <<
203  "add_compile_options(-std=c++14)\n" <<
204  "find_package(PythonInterp 3.5 REQUIRED)\n" <<
205  "set(codegen_python3_venv " + chaste_root.GetAbsolutePath() + "/codegen_python3_venv/bin)\n" <<
206  "find_package(Chaste COMPONENTS " << mComponentName << ")\n" <<
207  "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON " << codegen_args << ")\n" <<
208  "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})\n" <<
209  "include_directories(${Chaste_THIRD_PARTY_INCLUDE_DIRS} ${Chaste_INCLUDE_DIRS})\n" <<
210  "add_library(" << cellml_leaf_name << " SHARED " << "${sources})\n" <<
211  "if (${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n" <<
212  " target_link_libraries(" << cellml_leaf_name << " \"-Wl,-undefined,dynamic_lookup\")\n" <<
213  "endif()\n";
214  cmake_lists_filestream.close();
215  std::string cmake_args = " -DCMAKE_PREFIX_PATH=" + chaste_root.GetAbsolutePath() +
216  " -DCMAKE_BUILD_TYPE=" + ChasteBuildType() +
217  " -DChaste_ENABLE_TESTING=OFF" +
218  " -DBUILD_SHARED_LIBS=ON";
219  EXPECT0(chdir, tmp_folder.GetAbsolutePath());
220  EXPECT0(system, "cmake" + cmake_args + " .");
221  EXPECT0(system, "cmake --build . --config " + ChasteBuildType());
222 #else
223  // Change to Chaste source folder
224  EXPECT0(chdir, chaste_root.GetAbsolutePath());
225 
226  // Run scons to generate C++ code and compile it to a .so
227  EXPECT0(system, "scons --warn=no-all dyn_libs_only=1 chaste_libs=1 --codegen_args=\""+codegen_args+"\" --codegen_base_folder=" + chaste_source.GetAbsolutePath() + " build=" + ChasteBuildType() + " " + tmp_folder.GetAbsolutePath());
229  {
230  // Copy the generated source (.hpp and .cpp) to the same place as the .so file is going.
231  // NB. CMake does this by default
232  FileFinder destination_folder_for_sources(rCellmlFolder, RelativeTo::Absolute);
233  // Copy generated source code as well
234  std::vector<FileFinder> generated_files = build_folder.FindMatches("*.?pp");
235  BOOST_FOREACH(const FileFinder& r_generated_file, generated_files)
236  {
237  r_generated_file.CopyTo(destination_folder_for_sources);
238  }
239  }
240 #endif
241 
242  FileFinder so_file(tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix, RelativeTo::Absolute);
243 std::cout<< "so file: "<< tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix <<std::endl;
244  EXCEPT_IF_NOT(so_file.Exists());
245  // CD back
246  EXPECT0(chdir, old_cwd);
247 
248  // Copy the .so to the same folder as the original .cellml file
249  FileFinder destination_folder(rCellmlFolder, RelativeTo::Absolute);
250  so_file.CopyTo(destination_folder);
251 
252  // Delete the temporary folders
253  build_folder.DangerousRemove();
254  tmp_folder.DangerousRemove();
255  }
256  }
257  catch (Exception& e)
258  {
260  if (tmp_folder.IsPathSet() && tmp_folder.Exists())
261  {
263  {
264  // Copy any temporary files
265  IGNORE_EXCEPTIONS(build_folder.CopyTo(FileFinder(rCellmlFolder + "/build/", RelativeTo::Absolute)));
266  IGNORE_EXCEPTIONS(tmp_folder.CopyTo(FileFinder(rCellmlFolder + "/tmp/", RelativeTo::Absolute)));
267  }
268  // Delete the temporary folders
269  IGNORE_EXCEPTIONS(build_folder.DangerousRemove()); // rm -r under source
270  IGNORE_EXCEPTIONS(tmp_folder.DangerousRemove()); // rm -r under source
271  }
272  IGNORE_RET(chdir, old_cwd);
273  EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
274  }
275  // This also has the effect of a barrier, ensuring all processes wait for the
276  // shared library to be created.
278 }
279 
280 void CellMLToSharedLibraryConverter::SetOptions(const std::vector<std::string>& rArgs)
281 {
282  std::stringstream args_stream;
283  copy(rArgs.begin(), rArgs.end(), std::ostream_iterator<std::string>(args_stream, " "));
284  codegen_args = args_stream.str();
285 }
std::string GetAbsolutePath() const
Definition: FileFinder.cpp:224
static DynamicModelLoaderRegistry * Instance()
std::string ChasteBuildType()
void DangerousRemove() const
Definition: FileFinder.cpp:425
bool Exists() const
Definition: FileFinder.cpp:183
#define EXCEPTION(message)
Definition: Exception.hpp:143
std::string GetLeafNameNoExtension() const
Definition: FileFinder.cpp:245
std::string GetMessage() const
Definition: Exception.cpp:83
static bool AmMaster()
Definition: PetscTools.cpp:120
#define EXPECT0(cmd, arg)
Definition: Exception.hpp:254
std::string ChasteBuildDirName()
bool IsPathSet() const
Definition: FileFinder.cpp:178
DynamicCellModelLoaderPtr Convert(const FileFinder &rFilePath, bool isCollective=true)
std::vector< FileFinder > FindMatches(const std::string &rPattern) const
Definition: FileFinder.cpp:430
void SetOptions(const std::vector< std::string > &rArgs)
bool IsDir() const
Definition: FileFinder.cpp:193
static void ReplicateException(bool flag)
Definition: PetscTools.cpp:202
CellMLToSharedLibraryConverter(bool preserveGeneratedSources=false, std::string component="heart")
DynamicCellModelLoaderPtr GetLoader(const std::string &rPath)
#define EXCEPT_IF_NOT(test)
Definition: Exception.hpp:158
FileFinder CopyTo(const FileFinder &rDest) const
Definition: FileFinder.cpp:308
void ConvertCellmlToSo(const std::string &rCellmlFullPath, const std::string &rCellmlFolder)
FileFinder GetParent() const
Definition: FileFinder.cpp:255
virtual void SetPath(const std::string &rPath, RelativeTo::Value relativeTo)
Definition: FileFinder.cpp:102
#define IGNORE_RET(cmd, arg)
Definition: Exception.hpp:316
bool IsNewerThan(const FileFinder &rOtherEntity) const
Definition: FileFinder.cpp:233