Chaste  Release::2017.1
CellMLToSharedLibraryConverter.cpp
1 /*
2 
3 Copyright (c) 2005-2017, 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)
84  {
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 
150  if (!chaste_root.IsDir())
151  {
152  EXCEPTION("No Chaste build tree found at '" << chaste_root.GetAbsolutePath()
153  << "' - you need the source to use CellML models directly in Chaste.");
154  }
156  if (!component_dir.IsDir())
157  {
158  EXCEPTION("Unable to convert CellML model: required Chaste component '" << mComponentName
159  << "' does not exist in '" << chaste_root.GetAbsolutePath() << "'.");
160  }
161  // Try the conversion
162  try
163  {
164  // Need to create a .so file from the CellML...
165  if (PetscTools::AmMaster())
166  {
167  // Create a temporary folder within heart/dynamic
168  std::stringstream folder_name;
169  folder_name << "dynamic/tmp_" << getpid() << "_" << time(NULL);
170 
171 #ifdef CHASTE_CMAKE
172  tmp_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
173  build_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
174 #else
175  tmp_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
176  build_folder.SetPath(component_dir.GetAbsolutePath() + "/build/" + ChasteBuildDirName() + "/" + folder_name.str(), RelativeTo::Absolute);
177 #endif
178 
179  int ret = mkdir((tmp_folder.GetAbsolutePath()).c_str(), 0700);
180  if (ret != 0)
181  {
182  EXCEPTION("Failed to create temporary folder '" << tmp_folder.GetAbsolutePath() << "' for CellML conversion: "
183  << strerror(errno));
184  }
185 
186  // Copy the .cellml file (and any relevant others) into the temporary folder
187  FileFinder cellml_file(rCellmlFullPath, RelativeTo::Absolute);
188  FileFinder cellml_folder = cellml_file.GetParent();
189  std::string cellml_leaf_name = cellml_file.GetLeafNameNoExtension();
190  std::vector<FileFinder> cellml_files = cellml_folder.FindMatches(cellml_leaf_name + "*");
191 
192  BOOST_FOREACH(const FileFinder& r_cellml_file, cellml_files)
193  {
194  r_cellml_file.CopyTo(tmp_folder);
195  }
196 
197 #ifdef CHASTE_CMAKE
198  std::string cmake_lists_filename = tmp_folder.GetAbsolutePath() + "/CMakeLists.txt";
199  std::ofstream cmake_lists_filestream(cmake_lists_filename.c_str());
200  cmake_lists_filestream << "cmake_minimum_required(VERSION 2.8.12)\n" <<
201  "add_compile_options(-std=c++11)\n" <<
202  "find_package(Chaste COMPONENTS " << mComponentName << ")\n" <<
203  "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON)\n" <<
204  "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})\n" <<
205  "include_directories(${Chaste_THIRD_PARTY_INCLUDE_DIRS} ${Chaste_INCLUDE_DIRS})\n" <<
206  "add_library(" << cellml_leaf_name << " SHARED " << "${sources})\n" <<
207  "if (${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n" <<
208  " target_link_libraries(" << cellml_leaf_name << " \"-Wl,-undefined,dynamic_lookup\")\n" <<
209  "endif()\n"
210  //"target_link_libraries(" << cellml_leaf_name << " ${Chaste_LIBRARIES})\n"
211  ;
212  cmake_lists_filestream.close();
213  std::string cmake_args = " -DCMAKE_PREFIX_PATH=" + chaste_root.GetAbsolutePath() +
214  " -DCMAKE_BUILD_TYPE=" + ChasteBuildType() +
215  " -DChaste_ENABLE_TESTING=OFF" +
216  " -DBUILD_SHARED_LIBS=ON";
217  EXPECT0(chdir, tmp_folder.GetAbsolutePath());
218  EXPECT0(system, "cmake" + cmake_args + " .");
219  EXPECT0(system, "cmake --build . --config " + ChasteBuildType());
220 #else
221  // Change to Chaste source folder
222  EXPECT0(chdir, chaste_root.GetAbsolutePath());
223  // Run scons to generate C++ code and compile it to a .so
224  EXPECT0(system, "scons --warn=no-all dyn_libs_only=1 build=" + ChasteBuildType() + " " + tmp_folder.GetAbsolutePath());
226  {
227  // Copy the generated source (.hpp and .cpp) to the same place as the .so file is going.
228  // NB. CMake does this by default
229  FileFinder destination_folder_for_sources(rCellmlFolder, RelativeTo::Absolute);
230  // Copy generated source code as well
231  std::vector<FileFinder> generated_files = build_folder.FindMatches("*.?pp");
232  BOOST_FOREACH(const FileFinder& r_generated_file, generated_files)
233  {
234  r_generated_file.CopyTo(destination_folder_for_sources);
235  }
236  }
237 #endif
238 
239  FileFinder so_file(tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix, RelativeTo::Absolute);
240  EXCEPT_IF_NOT(so_file.Exists());
241  // CD back
242  EXPECT0(chdir, old_cwd);
243 
244  // Copy the .so to the same folder as the original .cellml file
245  FileFinder destination_folder(rCellmlFolder, RelativeTo::Absolute);
246  so_file.CopyTo(destination_folder);
247 
248  // Delete the temporary folders
249  build_folder.DangerousRemove();
250  tmp_folder.DangerousRemove();
251  }
252  }
253  catch (Exception& e)
254  {
256  if (tmp_folder.IsPathSet() && tmp_folder.Exists())
257  {
259  {
260  // Copy any temporary files
261  IGNORE_EXCEPTIONS(build_folder.CopyTo(FileFinder(rCellmlFolder + "/build/", RelativeTo::Absolute)));
262  IGNORE_EXCEPTIONS(tmp_folder.CopyTo(FileFinder(rCellmlFolder + "/tmp/", RelativeTo::Absolute)));
263  }
264  // Delete the temporary folders
265  IGNORE_EXCEPTIONS(build_folder.DangerousRemove()); // rm -r under source
266  IGNORE_EXCEPTIONS(tmp_folder.DangerousRemove()); // rm -r under source
267  }
268  IGNORE_RET(chdir, old_cwd);
269  EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
270  }
271  // This also has the effect of a barrier, ensuring all processes wait for the
272  // shared library to be created.
274 }
275 
277  const std::string& rModelName,
278  const std::vector<std::string>& rArgs,
279  const std::string& rExtraXml)
280 {
281  if (PetscTools::AmMaster())
282  {
283  out_stream p_optfile = rHandler.OpenOutputFile(rModelName + "-conf.xml");
284  (*p_optfile) << "<?xml version='1.0'?>" << std::endl
285  << "<pycml_config>" << std::endl;
286  if (!rArgs.empty())
287  {
288  (*p_optfile) << "<command_line_args>" << std::endl;
289  for (unsigned i=0; i<rArgs.size(); i++)
290  {
291  (*p_optfile) << "<arg>" << rArgs[i] << "</arg>" << std::endl;
292  }
293  (*p_optfile) << "</command_line_args>" << std::endl;
294  }
295  (*p_optfile) << rExtraXml << "</pycml_config>" << std::endl;
296  p_optfile->close();
297  }
298  PetscTools::Barrier("CellMLToSharedLibraryConverter::CreateOptionsFile");
299 }
void DangerousRemove() const
Definition: FileFinder.cpp:426
static DynamicModelLoaderRegistry * Instance()
std::string ChasteBuildType()
static void Barrier(const std::string callerId="")
Definition: PetscTools.cpp:134
std::string GetAbsolutePath() const
Definition: FileFinder.cpp:221
bool IsPathSet() const
Definition: FileFinder.cpp:175
#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:190
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:432
bool Exists() const
Definition: FileFinder.cpp:180
FileFinder GetParent() const
Definition: FileFinder.cpp:252
std::string GetLeafNameNoExtension() const
Definition: FileFinder.cpp:242
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:99
#define IGNORE_RET(cmd, arg)
Definition: Exception.hpp:316
bool IsNewerThan(const FileFinder &rOtherEntity) const
Definition: FileFinder.cpp:230