Chaste Release::3.1
CellMLToSharedLibraryConverter.cpp
00001 /*
00002 
00003 Copyright (c) 2005-2012, University of Oxford.
00004 All rights reserved.
00005 
00006 University of Oxford means the Chancellor, Masters and Scholars of the
00007 University of Oxford, having an administrative office at Wellington
00008 Square, Oxford OX1 2JD, UK.
00009 
00010 This file is part of Chaste.
00011 
00012 Redistribution and use in source and binary forms, with or without
00013 modification, are permitted provided that the following conditions are met:
00014  * Redistributions of source code must retain the above copyright notice,
00015    this list of conditions and the following disclaimer.
00016  * Redistributions in binary form must reproduce the above copyright notice,
00017    this list of conditions and the following disclaimer in the documentation
00018    and/or other materials provided with the distribution.
00019  * Neither the name of the University of Oxford nor the names of its
00020    contributors may be used to endorse or promote products derived from this
00021    software without specific prior written permission.
00022 
00023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00024 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00025 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00026 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
00027 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00028 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
00029 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00030 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
00032 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00033 
00034 */
00035 
00036 #include "CellMLToSharedLibraryConverter.hpp"
00037 
00038 #include <sstream>
00039 #include <unistd.h> // For getpid()
00040 #include <sys/stat.h> // For mkdir()
00041 #include <ctime>
00042 #include <cstring> // For strerror()
00043 #include <cerrno> // For errno
00044 
00045 #include "Exception.hpp"
00046 #include "ChasteBuildRoot.hpp"
00047 #include "PetscTools.hpp"
00048 #include "DynamicModelLoaderRegistry.hpp"
00049 #include "GetCurrentWorkingDirectory.hpp"
00050 
00051 CellMLToSharedLibraryConverter::CellMLToSharedLibraryConverter(bool preserveGeneratedSources,
00052                                                                std::string component)
00053     : mPreserveGeneratedSources(preserveGeneratedSources),
00054       mComponentName(component)
00055 {
00056 }
00057 
00058 DynamicCellModelLoaderPtr CellMLToSharedLibraryConverter::Convert(const FileFinder& rFilePath,
00059                                                                   bool isCollective)
00060 {
00061     DynamicCellModelLoaderPtr p_loader;
00062     std::string absolute_path = rFilePath.GetAbsolutePath();
00063     // Check the file exists
00064     if (!rFilePath.Exists())
00065     {
00066         EXCEPTION("Dynamically loadable cell model '" + absolute_path + "' does not exist.");
00067     }
00068     // Find out whether rFilePath is a .cellml or .so
00069     size_t dot_position = absolute_path.find_last_of(".");
00070     if (dot_position == std::string::npos)
00071     {
00072         EXCEPTION("File does not have an extension: " + absolute_path);
00073     }
00074     std::string extension = absolute_path.substr(dot_position+1);
00075     if (extension == "cellml")
00076     {
00077         // Split the path into folder and leaf
00078         size_t slash_position = absolute_path.find_last_of("/\\");
00079         assert(slash_position != std::string::npos);
00080         std::string folder = absolute_path.substr(0, slash_position+1); // Include trailing slash
00081         std::string leaf = absolute_path.substr(slash_position+1, dot_position-slash_position); // Include dot
00082         std::string so_path = folder + "lib" + leaf + "so";
00083         // Does the .so file already exist (and was it modified after the .cellml?)
00084         FileFinder so_file(so_path, RelativeTo::Absolute);
00085         if (!so_file.Exists() || rFilePath.IsNewerThan(so_file))
00086         {
00087             if (!isCollective)
00088             {
00089                 EXCEPTION("Unable to convert .cellml to .so unless called collectively, due to possible race conditions.");
00090             }
00091             ConvertCellmlToSo(absolute_path, folder, leaf);
00092         }
00093         // Load the .so
00094         p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(so_file);
00095     }
00096     else if (extension == "so")
00097     {
00098         // Just load the .so
00099         p_loader = DynamicModelLoaderRegistry::Instance()->GetLoader(rFilePath);
00100     }
00101     else
00102     {
00103         EXCEPTION("Unsupported extension '." + extension + "' of file '" + absolute_path + "'; must be .so or .cellml");
00104     }
00105 
00106     return p_loader;
00107 }
00108 
00109 void CellMLToSharedLibraryConverter::ConvertCellmlToSo(const std::string& rCellmlFullPath,
00110                                                        const std::string& rCellmlFolder,
00111                                                        const std::string& rModelLeafName)
00112 {
00113     std::string tmp_folder, build_folder;
00114     std::string old_cwd = GetCurrentWorkingDirectory();
00115     // Check that the Chaste source tree exists
00116     FileFinder chaste_root("", RelativeTo::ChasteSourceRoot);
00117     if (!chaste_root.IsDir())
00118     {
00119         EXCEPTION("No Chaste source tree found at '" << chaste_root.GetAbsolutePath()
00120                   << "' - you need the source to use CellML models directly in Chaste.");
00121     }
00122     FileFinder component_dir(mComponentName, RelativeTo::ChasteSourceRoot);
00123     if (!component_dir.IsDir())
00124     {
00125         EXCEPTION("Unable to convert CellML model: required Chaste component '" << mComponentName
00126                   << "' does not exist in '" << ChasteBuildRootDir() << "'.");
00127     }
00128     // Try the conversion
00129     try
00130     {
00131         // Need to create a .so file from the CellML...
00132         if (PetscTools::AmMaster())
00133         {
00134             // Create a temporary folder within heart/dynamic
00135             std::stringstream folder_name;
00136             folder_name << "dynamic/tmp_" << getpid() << "_" << time(NULL);
00137             tmp_folder = component_dir.GetAbsolutePath() + "/" + folder_name.str();
00138             build_folder = component_dir.GetAbsolutePath() + "/build/" + ChasteBuildDirName() + "/" + folder_name.str();
00139             int ret = mkdir(tmp_folder.c_str(), 0700);
00140             if (ret != 0)
00141             {
00142                 EXCEPTION("Failed to create temporary folder '" << tmp_folder << "' for CellML conversion: "
00143                           << strerror(errno));
00144             }
00145             // Copy the .cellml file (and any relevant others) into the temporary folder
00146             size_t dot_pos = rCellmlFullPath.rfind('.');
00147             std::string cellml_base = rCellmlFullPath.substr(0, dot_pos);
00148             EXPECT0(system, "cp " + cellml_base + "* " + tmp_folder);
00149             // If there's a config file, copy that too
00150             std::string config_path = rCellmlFullPath.substr(0, rCellmlFullPath.length() - 7) + "-conf.xml";
00151             if (FileFinder(config_path, RelativeTo::Absolute).Exists())
00152             {
00153                 EXPECT0(system, "cp " + config_path + " " + tmp_folder);
00154             }
00155             // Change to Chaste source folder
00156             EXPECT0(chdir, ChasteBuildRootDir());
00157             // Run scons to generate C++ code and compile it to a .so
00158             EXPECT0(system, "scons --warn=no-all dyn_libs_only=1 build=" + ChasteBuildType() + " " + tmp_folder);
00159             EXCEPT_IF_NOT(FileFinder(tmp_folder + "/lib" + rModelLeafName + "so", RelativeTo::Absolute).Exists());
00160             // CD back
00161             EXPECT0(chdir, old_cwd);
00162             // Copy the .so to the same folder as the original .cellml file
00163             EXPECT0(system, "cp " + tmp_folder + "/lib" + rModelLeafName + "so " + rCellmlFolder);
00164             if (mPreserveGeneratedSources)
00165             {
00166                 // Copy generated source code as well
00167                 EXPECT0(system, "cp " + build_folder + "/*.?pp " + rCellmlFolder);
00168             }
00169             // Delete the temporary folders
00170             EXPECT0(system, "rm -r " + build_folder);
00171             EXPECT0(system, "rm -r " + tmp_folder);
00172         }
00173     }
00174     catch (Exception& e)
00175     {
00176         PetscTools::ReplicateException(true);
00177         if (FileFinder(tmp_folder, RelativeTo::Absolute).Exists())
00178         {
00179             if (mPreserveGeneratedSources)
00180             {
00181                 // Copy any temporary files
00182                 IGNORE_RET(system, "cp -r " + build_folder + " " + rCellmlFolder + "/build/");
00183                 IGNORE_RET(system, "cp -r " + tmp_folder + " " + rCellmlFolder + "/tmp/");
00184             }
00185             // Delete the temporary folders
00186             IGNORE_RET(system, "rm -rf " + build_folder); // -f because folder might not exist
00187             IGNORE_RET(system, "rm -r " + tmp_folder);
00188         }
00189         IGNORE_RET(chdir, old_cwd);
00190         EXCEPTION("Conversion of CellML to Chaste shared object failed. Error was: " + e.GetMessage());
00191     }
00192     // This also has the effect of a barrier, ensuring all processes wait for the
00193     // shared library to be created.
00194     PetscTools::ReplicateException(false);
00195 }
00196 
00197 void CellMLToSharedLibraryConverter::CreateOptionsFile(const OutputFileHandler& rHandler,
00198                                                        const std::string& rModelName,
00199                                                        const std::vector<std::string>& rArgs,
00200                                                        const std::string& rExtraXml)
00201 {
00202     if (PetscTools::AmMaster())
00203     {
00204         out_stream p_optfile = rHandler.OpenOutputFile(rModelName + "-conf.xml");
00205         (*p_optfile) << "<?xml version='1.0'?>" << std::endl
00206                      << "<pycml_config>" << std::endl;
00207         if (!rArgs.empty())
00208         {
00209             (*p_optfile) << "<command_line_args>" << std::endl;
00210             for (unsigned i=0; i<rArgs.size(); i++)
00211             {
00212                 (*p_optfile) << "<arg>" << rArgs[i] << "</arg>" << std::endl;
00213             }
00214             (*p_optfile) << "</command_line_args>" << std::endl;
00215         }
00216         (*p_optfile) << rExtraXml << "</pycml_config>" << std::endl;
00217         p_optfile->close();
00218     }
00219     PetscTools::Barrier("CellMLToSharedLibraryConverter::CreateOptionsFile");
00220 }