Chaste  Release::3.4
CellMLToSharedLibraryConverter.cpp
1 /*
2 
3 Copyright (c) 2005-2016, 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 
60 
61 
63 #ifdef __APPLE__
64 //Mac OSX
65 const std::string CellMLToSharedLibraryConverter::msSoSuffix = "dylib";
66 #else
67 //Normal behaviour
68 const std::string CellMLToSharedLibraryConverter::msSoSuffix = "so";
69 #endif
70 
72  std::string component)
73  : mPreserveGeneratedSources(preserveGeneratedSources),
74  mComponentName(component)
75 {
76 }
77 
78 DynamicCellModelLoaderPtr CellMLToSharedLibraryConverter::Convert(const FileFinder& rFilePath,
79  bool isCollective)
80 {
81  DynamicCellModelLoaderPtr p_loader;
82  std::string absolute_path = rFilePath.GetAbsolutePath();
83  // Find out whether rFilePath is a .cellml or .so
84  size_t dot_position = absolute_path.find_last_of(".");
85  if (dot_position == std::string::npos)
86  {
87  EXCEPTION("File does not have an extension: " + absolute_path);
88  }
89  std::string extension = absolute_path.substr(dot_position+1);
90  // We make a modifiable version of the const FileFinder just incase we feel like
91  // amending the suffix
92  FileFinder file_path_copy(rFilePath);
93 #ifdef __APPLE__
94  if (extension == "so")
95  {
96  WARN_ONCE_ONLY("CellMLToSharedLibraryConverter asked to load a \".so\" file. On this architecture it should be \".dylib\"");
97  extension = "dylib";
98  absolute_path.replace(dot_position+1, 5, extension);
99  file_path_copy.SetPath(absolute_path, RelativeTo::Absolute);
100  }
101 #endif
102  // Check the file exists
103  if (!file_path_copy.Exists())
104  {
105  EXCEPTION("Dynamically loadable cell model '" + absolute_path + "' does not exist.");
106  }
107  if (extension == "cellml")
108  {
109  // Split the path into folder and leaf
110  size_t slash_position = absolute_path.find_last_of("/\\");
111  assert(slash_position != std::string::npos);
112  std::string folder = absolute_path.substr(0, slash_position+1); // Include trailing slash
113  std::string leaf = absolute_path.substr(slash_position+1, dot_position-slash_position); // Include dot
114  std::string so_path = folder + "lib" + leaf + msSoSuffix;
115  // Does the .so file already exist (and was it modified after the .cellml?)
116  FileFinder so_file(so_path, RelativeTo::Absolute);
117  if (!so_file.Exists() || rFilePath.IsNewerThan(so_file))
118  {
119  if (!isCollective)
120  {
121  EXCEPTION("Unable to convert .cellml to .so unless called collectively, due to possible race conditions.");
122  }
123  ConvertCellmlToSo(absolute_path, folder);
124  }
125  // Load the .so
126  p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(so_file);
127  }
128  else if (extension == msSoSuffix)
129  {
130  // Just load the .so
131  // Note that this may have been modified to .dylib
132  p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(file_path_copy);
133  }
134  else
135  {
136  EXCEPTION("Unsupported extension '." + extension + "' of file '" + absolute_path + "'; must be .so, .dylib or .cellml");
137  }
138 
139  return p_loader;
140 }
141 
142 void CellMLToSharedLibraryConverter::ConvertCellmlToSo(const std::string& rCellmlFullPath,
143  const std::string& rCellmlFolder)
144 {
145  FileFinder tmp_folder;
146  FileFinder build_folder;
147 
148  std::string old_cwd = GetCurrentWorkingDirectory();
149  // Check that the Chaste build tree exists
150  FileFinder chaste_root("", RelativeTo::ChasteBuildRoot);
151 
152  if (!chaste_root.IsDir())
153  {
154  EXCEPTION("No Chaste build tree found at '" << chaste_root.GetAbsolutePath()
155  << "' - you need the source to use CellML models directly in Chaste.");
156  }
158  if (!component_dir.IsDir())
159  {
160  EXCEPTION("Unable to convert CellML model: required Chaste component '" << mComponentName
161  << "' does not exist in '" << chaste_root.GetAbsolutePath() << "'.");
162  }
163  // Try the conversion
164  try
165  {
166  // Need to create a .so file from the CellML...
167  if (PetscTools::AmMaster())
168  {
169  // Create a temporary folder within heart/dynamic
170  std::stringstream folder_name;
171  folder_name << "dynamic/tmp_" << getpid() << "_" << time(NULL);
172 
173 #ifdef CHASTE_CMAKE
174 #define COVERAGE_IGNORE
175  tmp_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
176  build_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
177 #undef COVERAGE_IGNORE
178 #else
179  tmp_folder.SetPath(component_dir.GetAbsolutePath() + "/" + folder_name.str(), RelativeTo::Absolute);
180  build_folder.SetPath(component_dir.GetAbsolutePath() + "/build/" + ChasteBuildDirName() + "/" + folder_name.str(), RelativeTo::Absolute);
181 #endif
182 
183  int ret = mkdir((tmp_folder.GetAbsolutePath()).c_str(), 0700);
184  if (ret != 0)
185  {
186  EXCEPTION("Failed to create temporary folder '" << tmp_folder.GetAbsolutePath() << "' for CellML conversion: "
187  << strerror(errno));
188  }
189 
190  // Copy the .cellml file (and any relevant others) into the temporary folder
191  FileFinder cellml_file(rCellmlFullPath, RelativeTo::Absolute);
192  FileFinder cellml_folder = cellml_file.GetParent();
193  std::string cellml_leaf_name = cellml_file.GetLeafNameNoExtension();
194  std::vector<FileFinder> cellml_files = cellml_folder.FindMatches(cellml_leaf_name + "*");
195 
196  BOOST_FOREACH(const FileFinder& r_cellml_file, cellml_files)
197  {
198  r_cellml_file.CopyTo(tmp_folder);
199  }
200 
201 #ifdef CHASTE_CMAKE
202 #define COVERAGE_IGNORE
203  std::string cmake_lists_filename = tmp_folder.GetAbsolutePath() + "/CMakeLists.txt";
204  std::ofstream cmake_lists_filestream(cmake_lists_filename.c_str());
205  cmake_lists_filestream << "cmake_minimum_required(VERSION 2.8.10)\n" <<
206  "find_package(Chaste COMPONENTS " << mComponentName << ")\n" <<
207  "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON)\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  //"target_link_libraries(" << cellml_leaf_name << " ${Chaste_LIBRARIES})\n"
215  ;
216  cmake_lists_filestream.close();
217  std::string cmake_args = " -DCMAKE_PREFIX_PATH=" + chaste_root.GetAbsolutePath() +
218  " -DCMAKE_BUILD_TYPE=" + ChasteBuildType() +
219  " -DChaste_ENABLE_TESTING=OFF" +
220  " -DBUILD_SHARED_LIBS=ON";
221  EXPECT0(chdir, tmp_folder.GetAbsolutePath());
222  EXPECT0(system, "cmake" + cmake_args + " .");
223  EXPECT0(system, "cmake --build . --config " + ChasteBuildType());
224 #undef COVERAGE_IGNORE
225 #else
226  // Change to Chaste source folder
227  EXPECT0(chdir, chaste_root.GetAbsolutePath());
228  // Run scons to generate C++ code and compile it to a .so
229  EXPECT0(system, "scons --warn=no-all dyn_libs_only=1 build=" + ChasteBuildType() + " " + tmp_folder.GetAbsolutePath());
230 #endif
231 
232  FileFinder so_file(tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix, RelativeTo::Absolute);
233  EXCEPT_IF_NOT(so_file.Exists());
234  // CD back
235  EXPECT0(chdir, old_cwd);
236 
237  // Copy the .so to the same folder as the original .cellml file
238  FileFinder destination_folder(rCellmlFolder, RelativeTo::Absolute);
239  so_file.CopyTo(destination_folder);
240 
242  {
243  // Copy generated source code as well
244  std::vector<FileFinder> generated_files = build_folder.FindMatches("*.?pp");
245  BOOST_FOREACH(const FileFinder& r_generated_file, generated_files)
246  {
247  r_generated_file.CopyTo(destination_folder);
248  }
249  }
250  // Delete the temporary folders
251  build_folder.DangerousRemove();
252  tmp_folder.DangerousRemove();
253  }
254  }
255  catch (Exception& e)
256  {
258  if (tmp_folder.IsPathSet() && tmp_folder.Exists())
259  {
261  {
262  // Copy any temporary files
263  IGNORE_EXCEPTIONS(build_folder.CopyTo(FileFinder(rCellmlFolder + "/build/", RelativeTo::Absolute)));
264  IGNORE_EXCEPTIONS(tmp_folder.CopyTo(FileFinder(rCellmlFolder + "/tmp/", RelativeTo::Absolute)));
265  }
266  // Delete the temporary folders
267  IGNORE_EXCEPTIONS(build_folder.DangerousRemove()); // rm -r under source
268  IGNORE_EXCEPTIONS(tmp_folder.DangerousRemove()); // rm -r under source
269  }
270  IGNORE_RET(chdir, old_cwd);
271  EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
272  }
273  // This also has the effect of a barrier, ensuring all processes wait for the
274  // shared library to be created.
276 }
277 
279  const std::string& rModelName,
280  const std::vector<std::string>& rArgs,
281  const std::string& rExtraXml)
282 {
283  if (PetscTools::AmMaster())
284  {
285  out_stream p_optfile = rHandler.OpenOutputFile(rModelName + "-conf.xml");
286  (*p_optfile) << "<?xml version='1.0'?>" << std::endl
287  << "<pycml_config>" << std::endl;
288  if (!rArgs.empty())
289  {
290  (*p_optfile) << "<command_line_args>" << std::endl;
291  for (unsigned i=0; i<rArgs.size(); i++)
292  {
293  (*p_optfile) << "<arg>" << rArgs[i] << "</arg>" << std::endl;
294  }
295  (*p_optfile) << "</command_line_args>" << std::endl;
296  }
297  (*p_optfile) << rExtraXml << "</pycml_config>" << std::endl;
298  p_optfile->close();
299  }
300  PetscTools::Barrier("CellMLToSharedLibraryConverter::CreateOptionsFile");
301 }
void DangerousRemove() const
Definition: FileFinder.cpp:428
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:87
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:434
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