Chaste  Release::2018.1
CellMLToSharedLibraryConverter.cpp
1 /*
2 
3 Copyright (c) 2005-2018, 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 
36 #include "CellMLToSharedLibraryConverter.hpp"
37 
38 #include <sstream>
39 #include <fstream> // for std::ofstream
40 #include <sys/stat.h> // For mkdir()
41 #include <ctime>
42 #include <cstring> // For strerror()
43 #include <cerrno> // For errno
44 
45 #include <boost/foreach.hpp>
46 
47 #include "ChasteSyscalls.hpp"
48 #include "Exception.hpp"
49 #include "Warnings.hpp"
50 #include "ChasteBuildRoot.hpp"
51 #include "PetscTools.hpp"
52 #include "DynamicModelLoaderRegistry.hpp"
53 #include "GetCurrentWorkingDirectory.hpp"
54 
55 #define IGNORE_EXCEPTIONS(code) \
56  try { \
57  code; \
58  } catch (...) {}
59 
61 #ifdef __APPLE__
62 //Mac OSX
63 const std::string CellMLToSharedLibraryConverter::msSoSuffix = "dylib";
64 #else
65 //Normal behaviour
66 const std::string CellMLToSharedLibraryConverter::msSoSuffix = "so";
67 #endif
68 
70  std::string component)
71  : mPreserveGeneratedSources(preserveGeneratedSources),
72  mComponentName(component)
73 {
74 }
75 
76 DynamicCellModelLoaderPtr CellMLToSharedLibraryConverter::Convert(const FileFinder& rFilePath,
77  bool isCollective)
78 {
79  DynamicCellModelLoaderPtr p_loader;
80  std::string absolute_path = rFilePath.GetAbsolutePath();
81  // Find out whether rFilePath is a .cellml or .so
82  size_t dot_position = absolute_path.find_last_of(".");
83  if (dot_position == std::string::npos || absolute_path[dot_position+1] =='/')
84  {
85  // Either there is no dot in the absolute path or there is one along the path (/home/user/chaste/../temp/stuff)
86  EXCEPTION("File does not have an extension: " + absolute_path);
87  }
88  std::string extension = absolute_path.substr(dot_position+1);
89  // We make a modifiable version of the const FileFinder just incase we feel like
90  // amending the suffix
91  FileFinder file_path_copy(rFilePath);
92 #ifdef __APPLE__
93  if (extension == "so")
94  {
95  WARN_ONCE_ONLY("CellMLToSharedLibraryConverter asked to load a \".so\" file. On this architecture it should be \".dylib\"");
96  extension = "dylib";
97  absolute_path.replace(dot_position+1, 5, extension);
98  file_path_copy.SetPath(absolute_path, RelativeTo::Absolute);
99  }
100 #endif
101  // Check the file exists
102  if (!file_path_copy.Exists())
103  {
104  EXCEPTION("Dynamically loadable cell model '" + absolute_path + "' does not exist.");
105  }
106  if (extension == "cellml")
107  {
108  // Split the path into folder and leaf
109  size_t slash_position = absolute_path.find_last_of("/\\");
110  assert(slash_position != std::string::npos);
111  std::string folder = absolute_path.substr(0, slash_position+1); // Include trailing slash
112  std::string leaf = absolute_path.substr(slash_position+1, dot_position-slash_position); // Include dot
113  std::string so_path = folder + "lib" + leaf + msSoSuffix;
114  // Does the .so file already exist (and was it modified after the .cellml?)
115  FileFinder so_file(so_path, RelativeTo::Absolute);
116  if (!so_file.Exists() || rFilePath.IsNewerThan(so_file))
117  {
118  if (!isCollective)
119  {
120  EXCEPTION("Unable to convert .cellml to .so unless called collectively, due to possible race conditions.");
121  }
122  ConvertCellmlToSo(absolute_path, folder);
123  }
124  // Load the .so
125  p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(so_file);
126  }
127  else if (extension == msSoSuffix)
128  {
129  // Just load the .so
130  // Note that this may have been modified to .dylib
131  p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(file_path_copy);
132  }
133  else
134  {
135  EXCEPTION("Unsupported extension '." + extension + "' of file '" + absolute_path + "'; must be .so, .dylib or .cellml");
136  }
137 
138  return p_loader;
139 }
140 
141 void CellMLToSharedLibraryConverter::ConvertCellmlToSo(const std::string& rCellmlFullPath,
142  const std::string& rCellmlFolder)
143 {
144  FileFinder tmp_folder;
145  FileFinder build_folder;
146 
147  std::string old_cwd = GetCurrentWorkingDirectory();
148  // Check that the Chaste build tree exists
149  FileFinder chaste_root("", RelativeTo::ChasteBuildRoot);
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  // Copy the .cellml file (and any relevant others) into the temporary folder
188  FileFinder cellml_file(rCellmlFullPath, RelativeTo::Absolute);
189  FileFinder cellml_folder = cellml_file.GetParent();
190  std::string cellml_leaf_name = cellml_file.GetLeafNameNoExtension();
191  std::vector<FileFinder> cellml_files = cellml_folder.FindMatches(cellml_leaf_name + "*");
192 
193  BOOST_FOREACH(const FileFinder& r_cellml_file, cellml_files)
194  {
195  r_cellml_file.CopyTo(tmp_folder);
196  }
197 
198 #ifdef CHASTE_CMAKE
199  std::string cmake_lists_filename = tmp_folder.GetAbsolutePath() + "/CMakeLists.txt";
200  std::ofstream cmake_lists_filestream(cmake_lists_filename.c_str());
201  cmake_lists_filestream << "cmake_minimum_required(VERSION 2.8.12)\n" <<
202  "add_compile_options(-std=c++11)\n" <<
203  "find_package(Chaste COMPONENTS " << mComponentName << ")\n" <<
204  "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON)\n" <<
205  "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})\n" <<
206  "include_directories(${Chaste_THIRD_PARTY_INCLUDE_DIRS} ${Chaste_INCLUDE_DIRS})\n" <<
207  "add_library(" << cellml_leaf_name << " SHARED " << "${sources})\n" <<
208  "if (${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n" <<
209  " target_link_libraries(" << cellml_leaf_name << " \"-Wl,-undefined,dynamic_lookup\")\n" <<
210  "endif()\n"
211  //"target_link_libraries(" << cellml_leaf_name << " ${Chaste_LIBRARIES})\n"
212  ;
213  cmake_lists_filestream.close();
214  std::string cmake_args = " -DCMAKE_PREFIX_PATH=" + chaste_root.GetAbsolutePath() +
215  " -DCMAKE_BUILD_TYPE=" + ChasteBuildType() +
216  " -DChaste_ENABLE_TESTING=OFF" +
217  " -DBUILD_SHARED_LIBS=ON";
218  EXPECT0(chdir, tmp_folder.GetAbsolutePath());
219  EXPECT0(system, "cmake" + cmake_args + " .");
220  EXPECT0(system, "cmake --build . --config " + ChasteBuildType());
221 #else
222  // Change to Chaste source folder
223  EXPECT0(chdir, chaste_root.GetAbsolutePath());
224  // Run scons to generate C++ code and compile it to a .so
225  EXPECT0(system, "scons --warn=no-all dyn_libs_only=1 build=" + ChasteBuildType() + " " + tmp_folder.GetAbsolutePath());
227  {
228  // Copy the generated source (.hpp and .cpp) to the same place as the .so file is going.
229  // NB. CMake does this by default
230  FileFinder destination_folder_for_sources(rCellmlFolder, RelativeTo::Absolute);
231  // Copy generated source code as well
232  std::vector<FileFinder> generated_files = build_folder.FindMatches("*.?pp");
233  BOOST_FOREACH(const FileFinder& r_generated_file, generated_files)
234  {
235  r_generated_file.CopyTo(destination_folder_for_sources);
236  }
237  }
238 #endif
239 
240  FileFinder so_file(tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix, RelativeTo::Absolute);
241  EXCEPT_IF_NOT(so_file.Exists());
242  // CD back
243  EXPECT0(chdir, old_cwd);
244 
245  // Copy the .so to the same folder as the original .cellml file
246  FileFinder destination_folder(rCellmlFolder, RelativeTo::Absolute);
247  so_file.CopyTo(destination_folder);
248 
249  // Delete the temporary folders
250  build_folder.DangerousRemove();
251  tmp_folder.DangerousRemove();
252  }
253  }
254  catch (Exception& e)
255  {
257  if (tmp_folder.IsPathSet() && tmp_folder.Exists())
258  {
260  {
261  // Copy any temporary files
262  IGNORE_EXCEPTIONS(build_folder.CopyTo(FileFinder(rCellmlFolder + "/build/", RelativeTo::Absolute)));
263  IGNORE_EXCEPTIONS(tmp_folder.CopyTo(FileFinder(rCellmlFolder + "/tmp/", RelativeTo::Absolute)));
264  }
265  // Delete the temporary folders
266  IGNORE_EXCEPTIONS(build_folder.DangerousRemove()); // rm -r under source
267  IGNORE_EXCEPTIONS(tmp_folder.DangerousRemove()); // rm -r under source
268  }
269  IGNORE_RET(chdir, old_cwd);
270  EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
271  }
272  // This also has the effect of a barrier, ensuring all processes wait for the
273  // shared library to be created.
275 }
276 
278  const std::string& rModelName,
279  const std::vector<std::string>& rArgs,
280  const std::string& rExtraXml)
281 {
282  if (PetscTools::AmMaster())
283  {
284  out_stream p_optfile = rHandler.OpenOutputFile(rModelName + "-conf.xml");
285  (*p_optfile) << "<?xml version='1.0'?>" << std::endl
286  << "<pycml_config>" << std::endl;
287  if (!rArgs.empty())
288  {
289  (*p_optfile) << "<command_line_args>" << std::endl;
290  for (unsigned i=0; i<rArgs.size(); i++)
291  {
292  (*p_optfile) << "<arg>" << rArgs[i] << "</arg>" << std::endl;
293  }
294  (*p_optfile) << "</command_line_args>" << std::endl;
295  }
296  (*p_optfile) << rExtraXml << "</pycml_config>" << std::endl;
297  p_optfile->close();
298  }
299  PetscTools::Barrier("CellMLToSharedLibraryConverter::CreateOptionsFile");
300 }
void DangerousRemove() const
Definition: FileFinder.cpp:425
static DynamicModelLoaderRegistry * Instance()
std::string ChasteBuildType()
static void Barrier(const std::string callerId="")
Definition: PetscTools.cpp:134
std::string GetAbsolutePath() const
Definition: FileFinder.cpp:224
bool IsPathSet() const
Definition: FileFinder.cpp:178
#define EXCEPTION(message)
Definition: Exception.hpp:143
static bool AmMaster()
Definition: PetscTools.cpp:120
FileFinder CopyTo(const FileFinder &rDest) const
Definition: FileFinder.cpp:308
#define EXPECT0(cmd, arg)
Definition: Exception.hpp:254
std::string ChasteBuildDirName()
DynamicCellModelLoaderPtr Convert(const FileFinder &rFilePath, bool isCollective=true)
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
bool IsDir() const
Definition: FileFinder.cpp:193
std::string GetMessage() const
Definition: Exception.cpp:83
static void ReplicateException(bool flag)
Definition: PetscTools.cpp:198
CellMLToSharedLibraryConverter(bool preserveGeneratedSources=false, std::string component="heart")
DynamicCellModelLoaderPtr GetLoader(const std::string &rPath)
#define EXCEPT_IF_NOT(test)
Definition: Exception.hpp:158
std::vector< FileFinder > FindMatches(const std::string &rPattern) const
Definition: FileFinder.cpp:430
bool Exists() const
Definition: FileFinder.cpp:183
FileFinder GetParent() const
Definition: FileFinder.cpp:255
std::string GetLeafNameNoExtension() const
Definition: FileFinder.cpp:245
static void CreateOptionsFile(const OutputFileHandler &rHandler, const std::string &rModelName, const std::vector< std::string > &rArgs, const std::string &rExtraXml="")
void ConvertCellmlToSo(const std::string &rCellmlFullPath, const std::string &rCellmlFolder)
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