Chaste Commit::f2ff7ee04e70ac9d06c57344df8d017dbb12b97b
CellMLToSharedLibraryConverter.cpp
1/*
2
3Copyright (c) 2005-2024, University of Oxford.
4All rights reserved.
5
6University of Oxford means the Chancellor, Masters and Scholars of the
7University of Oxford, having an administrative office at Wellington
8Square, Oxford OX1 2JD, UK.
9
10This file is part of Chaste.
11
12Redistribution and use in source and binary forms, with or without
13modification, 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
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32OF 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
62const std::string CellMLToSharedLibraryConverter::msSoSuffix = "dylib";
63#else
64//Normal behaviour
65const std::string CellMLToSharedLibraryConverter::msSoSuffix = "so";
66#endif
67
69 std::string component)
70 : mPreserveGeneratedSources(preserveGeneratedSources),
71 mComponentName(component)
72{
73}
74
75DynamicCellModelLoaderPtr 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
140void 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
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...
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 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 3.16.3)\n" <<
202 "set(CMAKE_CXX_STANDARD 17)\n" <<
203 "set(CMAKE_CXX_STANDARD_REQUIRED ON)\n" <<
204 "set(CMAKE_CXX_EXTENSIONS OFF)\n" <<
205 "project (ChasteCellMLToSharedLibraryConverter)\n" <<
206 "find_package(PythonInterp 3.5 REQUIRED)\n" <<
207 "set(codegen_python3_venv " + chaste_root.GetAbsolutePath() + "/codegen_python3_venv/bin)\n" <<
208 "find_package(Chaste COMPONENTS " << mComponentName << ")\n" <<
209 "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON " << codegen_args << ")\n" <<
210 "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})\n" <<
211 "include_directories(${Chaste_THIRD_PARTY_INCLUDE_DIRS} ${Chaste_INCLUDE_DIRS})\n" <<
212 "add_library(" << cellml_leaf_name << " SHARED " << "${sources})\n" <<
213 "if (${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n" <<
214 " target_link_libraries(" << cellml_leaf_name << " \"-Wl,-undefined,dynamic_lookup\")\n" <<
215 "endif()\n" <<
216 "if (${CMAKE_CXX_COMPILER_ID} STREQUAL \"IntelLLVM\")\n" <<
217 " set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fp-model precise\")\n" <<
218 "endif()\n";
219 cmake_lists_filestream.close();
220 std::string cmake_args = " -DCMAKE_PREFIX_PATH=" + chaste_root.GetAbsolutePath() +
221 " -DCMAKE_BUILD_TYPE=" + ChasteBuildType() +
222 " -DChaste_ENABLE_TESTING=OFF" +
223 " -DBUILD_SHARED_LIBS=ON";
224 EXPECT0(chdir, tmp_folder.GetAbsolutePath());
225 EXPECT0(system, "cmake" + cmake_args + " .");
226 EXPECT0(system, "cmake --build . --config " + ChasteBuildType());
227
228 FileFinder so_file(tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix, RelativeTo::Absolute);
229std::cout<< "so file: "<< tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix <<std::endl;
230 EXCEPT_IF_NOT(so_file.Exists());
231 // CD back
232 EXPECT0(chdir, old_cwd);
233
234 // Copy the .so to the same folder as the original .cellml file
235 FileFinder destination_folder(rCellmlFolder, RelativeTo::Absolute);
236 so_file.CopyTo(destination_folder);
237
238 // Delete the temporary folders
239 build_folder.DangerousRemove();
240 tmp_folder.DangerousRemove();
241 }
242 }
243 catch (Exception& e)
244 {
246 if (tmp_folder.IsPathSet() && tmp_folder.Exists())
247 {
249 {
250 // Copy any temporary files
251 IGNORE_EXCEPTIONS(build_folder.CopyTo(FileFinder(rCellmlFolder + "/build/", RelativeTo::Absolute)));
252 IGNORE_EXCEPTIONS(tmp_folder.CopyTo(FileFinder(rCellmlFolder + "/tmp/", RelativeTo::Absolute)));
253 }
254 // Delete the temporary folders
255 IGNORE_EXCEPTIONS(build_folder.DangerousRemove()); // rm -r under source
256 IGNORE_EXCEPTIONS(tmp_folder.DangerousRemove()); // rm -r under source
257 }
258 IGNORE_RET(chdir, old_cwd);
259 EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
260 }
261 // This also has the effect of a barrier, ensuring all processes wait for the
262 // shared library to be created.
264}
265
266void CellMLToSharedLibraryConverter::SetOptions(const std::vector<std::string>& rArgs)
267{
268 std::stringstream args_stream;
269 copy(rArgs.begin(), rArgs.end(), std::ostream_iterator<std::string>(args_stream, " "));
270 codegen_args = args_stream.str();
271}
std::string ChasteBuildType()
std::string ChasteBuildDirName()
#define EXPECT0(cmd, arg)
#define IGNORE_RET(cmd, arg)
#define EXCEPTION(message)
#define EXCEPT_IF_NOT(test)
DynamicCellModelLoaderPtr Convert(const FileFinder &rFilePath, bool isCollective=true)
CellMLToSharedLibraryConverter(bool preserveGeneratedSources=false, std::string component="heart")
void SetOptions(const std::vector< std::string > &rArgs)
void ConvertCellmlToSo(const std::string &rCellmlFullPath, const std::string &rCellmlFolder)
DynamicCellModelLoaderPtr GetLoader(const std::string &rPath)
static DynamicModelLoaderRegistry * Instance()
std::string GetLeafNameNoExtension() const
bool IsNewerThan(const FileFinder &rOtherEntity) const
std::string GetAbsolutePath() const
void DangerousRemove() const
std::vector< FileFinder > FindMatches(const std::string &rPattern) const
FileFinder GetParent() const
bool IsDir() const
virtual void SetPath(const std::string &rPath, RelativeTo::Value relativeTo)
bool Exists() const
FileFinder CopyTo(const FileFinder &rDest) const
bool IsPathSet() const
static bool AmMaster()
static void ReplicateException(bool flag)