Chaste Commit::a9c8bf7350f67d7cf086e6fe3cf5461521554546
CellMLToSharedLibraryConverter.cpp
1/*
2
3Copyright (c) 2005-2026, 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
45#include "ChasteSyscalls.hpp"
46#include "Exception.hpp"
47#include "Warnings.hpp"
48#include "ChasteBuildRoot.hpp"
49#include "PetscTools.hpp"
50#include "DynamicModelLoaderRegistry.hpp"
51#include "GetCurrentWorkingDirectory.hpp"
52
53#define IGNORE_EXCEPTIONS(code) \
54 try { \
55 code; \
56 } catch (...) {}
57
59#ifdef __APPLE__
60//Mac OSX
61const std::string CellMLToSharedLibraryConverter::msSoSuffix = "dylib";
62#else
63//Normal behaviour
64const std::string CellMLToSharedLibraryConverter::msSoSuffix = "so";
65#endif
66
68 std::string component)
69 : mPreserveGeneratedSources(preserveGeneratedSources),
70 mComponentName(component)
71{
72}
73
74DynamicCellModelLoaderPtr CellMLToSharedLibraryConverter::Convert(const FileFinder& rFilePath,
75 bool isCollective)
76{
77 DynamicCellModelLoaderPtr p_loader;
78 std::string absolute_path = rFilePath.GetAbsolutePath();
79 // Find out whether rFilePath is a .cellml or .so
80 size_t dot_position = absolute_path.find_last_of(".");
81 if (dot_position == std::string::npos || absolute_path[dot_position+1] =='/')
82 {
83 // Either there is no dot in the absolute path or there is one along the path (/home/user/chaste/../temp/stuff)
84 EXCEPTION("File does not have an extension: " + absolute_path);
85 }
86 std::string extension = absolute_path.substr(dot_position+1);
87 // We make a modifiable version of the const FileFinder just incase we feel like
88 // amending the suffix
89 FileFinder file_path_copy(rFilePath);
90#ifdef __APPLE__
91 if (extension == "so")
92 {
93 WARN_ONCE_ONLY("CellMLToSharedLibraryConverter asked to load a \".so\" file. On this architecture it should be \".dylib\"");
94 extension = "dylib";
95 absolute_path.replace(dot_position+1, 5, extension);
96 file_path_copy.SetPath(absolute_path, RelativeTo::Absolute);
97 }
98#endif
99 // Check the file exists
100 if (!file_path_copy.Exists())
101 {
102 EXCEPTION("Dynamically loadable cell model '" + absolute_path + "' does not exist.");
103 }
104 if (extension == "cellml")
105 {
106 // Split the path into folder and leaf
107 size_t slash_position = absolute_path.find_last_of("/\\");
108 assert(slash_position != std::string::npos);
109 std::string folder = absolute_path.substr(0, slash_position+1); // Include trailing slash
110 std::string leaf = absolute_path.substr(slash_position+1, dot_position-slash_position); // Include dot
111 std::string so_path = folder + "lib" + leaf + msSoSuffix;
112 // Does the .so file already exist (and was it modified after the .cellml?)
113 FileFinder so_file(so_path, RelativeTo::Absolute);
114 if (!so_file.Exists() || rFilePath.IsNewerThan(so_file))
115 {
116 if (!isCollective)
117 {
118 EXCEPTION("Unable to convert .cellml to .so unless called collectively, due to possible race conditions.");
119 }
120 ConvertCellmlToSo(absolute_path, folder);
121 }
122 // Load the .so
123 p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(so_file);
124 }
125 else if (extension == msSoSuffix)
126 {
127 // Just load the .so
128 // Note that this may have been modified to .dylib
129 p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(file_path_copy);
130 }
131 else
132 {
133 EXCEPTION("Unsupported extension '." + extension + "' of file '" + absolute_path + "'; must be .so, .dylib or .cellml");
134 }
135
136 return p_loader;
137}
138
139void CellMLToSharedLibraryConverter::ConvertCellmlToSo(const std::string& rCellmlFullPath,
140 const std::string& rCellmlFolder)
141{
142 FileFinder tmp_folder;
143 FileFinder build_folder;
144
145 std::string old_cwd = GetCurrentWorkingDirectory();
146 // Check that the Chaste build tree exists
148 FileFinder chaste_source("", RelativeTo::ChasteSourceRoot);
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...
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
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 for (const FileFinder& r_cellml_file : cellml_files)
194 {
195 r_cellml_file.CopyTo(tmp_folder);
196 }
197
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 3.16.3)\n" <<
201 "set(CMAKE_CXX_STANDARD 17)\n" <<
202 "set(CMAKE_CXX_STANDARD_REQUIRED ON)\n" <<
203 "set(CMAKE_CXX_EXTENSIONS OFF)\n" <<
204 "project (ChasteCellMLToSharedLibraryConverter)\n" <<
205 "find_package(Python3 3.5 REQUIRED)\n" <<
206 "set(chaste_python3_venv " + chaste_root.GetAbsolutePath() + "/chaste_python3_venv/bin)\n" <<
207 "find_package(Chaste COMPONENTS " << mComponentName << ")\n" <<
208 "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON " << codegen_args << ")\n" <<
209 "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})\n" <<
210 "include_directories(${Chaste_THIRD_PARTY_INCLUDE_DIRS} ${Chaste_INCLUDE_DIRS})\n" <<
211 "add_library(" << cellml_leaf_name << " SHARED " << "${sources})\n" <<
212 "if (${CMAKE_SYSTEM_NAME} MATCHES \"Darwin\")\n" <<
213 " target_link_libraries(" << cellml_leaf_name << " \"-Wl,-undefined,dynamic_lookup\")\n" <<
214 "endif()\n" <<
215 "if (${CMAKE_CXX_COMPILER_ID} STREQUAL \"IntelLLVM\")\n" <<
216 " set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fp-model precise\")\n" <<
217 "endif()\n";
218 cmake_lists_filestream.close();
219 std::string cmake_args = " -DCMAKE_PREFIX_PATH=" + chaste_root.GetAbsolutePath() +
220 " -DCMAKE_BUILD_TYPE=" + ChasteBuildType() +
221 " -DChaste_ENABLE_TESTING=OFF" +
222 " -DBUILD_SHARED_LIBS=ON";
223 EXPECT0(chdir, tmp_folder.GetAbsolutePath());
224 EXPECT0(system, "cmake" + cmake_args + " .");
225 EXPECT0(system, "cmake --build . --config " + ChasteBuildType());
226
227 FileFinder so_file(tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix, RelativeTo::Absolute);
228std::cout<< "so file: "<< tmp_folder.GetAbsolutePath() + "/lib" + cellml_leaf_name + "." + msSoSuffix <<std::endl;
229 EXCEPT_IF_NOT(so_file.Exists());
230 // CD back
231 EXPECT0(chdir, old_cwd);
232
233 // Copy the .so to the same folder as the original .cellml file
234 FileFinder destination_folder(rCellmlFolder, RelativeTo::Absolute);
235 so_file.CopyTo(destination_folder);
236
237 // Delete the temporary folders
238 build_folder.DangerousRemove();
239 tmp_folder.DangerousRemove();
240 }
241 }
242 catch (Exception& e)
243 {
245 if (tmp_folder.IsPathSet() && tmp_folder.Exists())
246 {
248 {
249 // Copy any temporary files
250 IGNORE_EXCEPTIONS(build_folder.CopyTo(FileFinder(rCellmlFolder + "/build/", RelativeTo::Absolute)));
251 IGNORE_EXCEPTIONS(tmp_folder.CopyTo(FileFinder(rCellmlFolder + "/tmp/", RelativeTo::Absolute)));
252 }
253 // Delete the temporary folders
254 IGNORE_EXCEPTIONS(build_folder.DangerousRemove()); // rm -r under source
255 IGNORE_EXCEPTIONS(tmp_folder.DangerousRemove()); // rm -r under source
256 }
257 IGNORE_RET(chdir, old_cwd);
258 EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
259 }
260 // This also has the effect of a barrier, ensuring all processes wait for the
261 // shared library to be created.
263}
264
265void CellMLToSharedLibraryConverter::SetOptions(const std::vector<std::string>& rArgs)
266{
267 std::stringstream args_stream;
268 copy(rArgs.begin(), rArgs.end(), std::ostream_iterator<std::string>(args_stream, " "));
269 codegen_args = args_stream.str();
270}
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)