Cantera installer

Cantera is a toolkit for problems involving chemical kinetics and thermodynamics. We use it within a sub-project of the DFG Collaborative Research Center 1029: TurbIn.

To simplify the installation of Cantera in Linux environments, a installer script has been written and is maintained on this homepage. It takes care of everything from installing (uncommon) dependencies, setting up a python virtualenv for local, prefixed installation, downloading and compiling Cantera.

The script

Download: cantera_build.py (Latest update: 31.10.2012)

  1 #!/usr/bin/env python
  2 # encoding: utf-8
  3 #
  4 # A script to build a Cantera installation on *ix systems
  5 # Cantera-2.x version
  6 #
  7 # Copyright (c) 2012, Phillip Berndt
  8 # All rights reserved.
  9 # Redistribution in source and binary forms permitted under the terms of the
 10 # FreeBSD licence.
 11 #
 12 import tempfile
 13 import re
 14 import urllib
 15 import os
 16 import termios
 17 import sys
 18 import tty
 19
 20 try:
 21     import readline
 22 except:
 23     try:
 24         import libedit
 25     except:
 26         pass
 27
 28 welcome_message = """\033[4;1mCANTERA INSTALL SCRIPT\033[0m
 29
 30 Welcome to the cantera installation script. This script will
 31 download the latest Cantera 2.x from the google code homepage at
 32     <http://code.google.com/p/cantera/>
 33 or from the subversion repository and build it for your system,
 34 along with all common prerequisites.
 35
 36 You should in advance install all the prerequisites from
 37     <http://cantera.github.com/docs/sphinx/html/compiling.html>
 38 except for sundials, which we will download and install for you.
 39
 40 """
 41
 42 script_template = """
 43 export PS1="[CANTERA] $PS1"
 44 VIRTUAL_ENV="{dest}"
 45 export VIRTUAL_ENV
 46 PATH="{dest}/bin:$PATH"
 47 export PATH
 48 unset PYTHONHOME
 49 LD_LIBRARY_PATH={dest}/lib:$LD_LIBRARY_PATH
 50 export LD_LIBRARY_PATH
 51 PYTHON_CMD={dest}/bin/python
 52 export PYTHON_CMD
 53 MATLABPATH=$MATLABPATH:{dest}/lib/cantera/matlab/toolbox:{dest}/lib/cantera/matlab/toolbox/1D:{dest}/share/matlab/toolbox
 54 export MATLABPATH
 55 PYTHONPATH=`echo {dest}/lib/python2.?/site-packages`:$PYTHONPATH
 56 export PYTHONPATH
 57 export MATLAB_MEM_MGR=compact
 58 export LD_PRELOAD={stdcpplib}
 59 export CANTERA_XML_DIR={dest}/share/cantera/xml
 60 export PKG_CONFIG_PATH={dest}/lib/pkgconfig
 61 """
 62
 63 def ld_search(library):
 64     if os.environ["LD_LIBRARY_PATH"]:
 65         for path in os.environ["LD_LIBRARY_PATH"].split(":"):
 66             if os.path.isdir(path):
 67                 joinedPath = os.path.join(path, library)
 68                 if(os.access(joinedPath, os.F_OK)):
 69                     return joinedPath
 70     if os.access("/etc/ld.so.cache", os.R_OK):
 71         libs = open("/etc/ld.so.cache").read().split("\0")
 72         try:
 73             return libs[libs.index(library) + 1]
 74         except:
 75             pass
 76     return ""
 77
 78 def compile_test(code, flags=None):
 79     tempFile = tempfile.mktemp(".c")
 80     executable = tempfile.mktemp(".exe")
 81     tempFileObject = open(tempFile, "w")
 82     tempFileObject.write(code)
 83     tempFileObject.close()
 84     retval = os.system("cc -o %s %s %s >/dev/null 2>&1" % (executable, flags or "", tempFile)) != 0 or os.system(executable + " >/dev/null 2>&1") != 0
 85     try:
 86         os.unlink(tempFile)
 87         os.unlink(executable)
 88     except:
 89         pass
 90     return not retval
 91
 92 def urlretrieve_callback(chunks, chunk_size, total_size):
 93     downloaded = chunks * chunk_size
 94     per20 = downloaded * 20 / total_size
 95     progress = "\033[2K[" + ("-" * per20) + (" " * (20 - per20)) + ("] %02d%% (%02.2f of %02.2f Mb)" % (per20 * 5, downloaded / 1024**2, total_size / 1024**2)) + "\r"
 96     sys.stdout.write(progress)
 97     sys.stdout.flush()
 98
 99 def read_yes_no():
100     fileno = sys.stdin.fileno()
101     old = termios.tcgetattr(fileno)
102     yes_no = ""
103     try:
104         tty.setraw(fileno)
105         while yes_no not in ("y", "n"):
106             yes_no = sys.stdin.read(1).lower()
107     finally:
108         termios.tcsetattr(fileno, termios.TCSADRAIN, old)
109     return yes_no == "y"
110
111 def update_config(config, setting, value):
112     for line in range(len(config)):
113         if config[line][:len(setting)] == setting:
114             config[line] = setting + "='" + value + "'\n"
115     return config
116
117 def main():
118     print welcome_message
119
120     # Query installation target
121     if len(sys.argv) > 1:
122         dest = sys.argv[1].strip()
123         print "\033[32mAssuming\033[0m %s \033[32mis the installation directory\033[0m" % dest
124         if not os.path.isdir(dest):
125             try:
126                 os.mkdir(dest)
127             except:
128                 print "\033[31mFailed to create the directory\033[0m", dest
129     else:
130         default = os.popen("pkg-config --variable=prefix cantera 2>/dev/null").read().strip()
131         if not default:
132             default = "~/Cantera"
133         while True:
134             print "\033[33mWhere do you want to install Cantera? [default: %s]\033[0m" % default
135             dest = raw_input().strip()
136             if dest == "":
137                 dest = default
138             dest = os.path.abspath(os.path.expanduser(dest))
139             if not os.path.isdir(dest):
140                 try:
141                     os.mkdir(dest)
142                     break
143                 except:
144                     print "\033[31mFailed to create the directory\033[0m", dest
145             else:
146                 break
147     os.chdir(dest)
148     if not os.path.isdir("build"):
149         os.mkdir("build")
150     os.chdir("build")
151
152     # Check other build requisites
153     print "\033[32mChecking for required programs..\033[0m"
154     apps = ("gcc", "g++", )
155     for app in apps:
156         if os.system("which %s > /dev/null 2>&1" % app) != 0:
157             print "\033[31mCould not find `%s' executable. Please install it using your package manager.\033[0m"
158             sys.exit(0)
159     
160     # Check mex
161     print "\033[32mSearching for a matlab installation..\033[0m"
162     matlab_root = os.popen('echo "exit" | matlab -nojvm -nodesktop -r matlabroot | grep -E "^/" | tail -n1').read().strip()
163     if os.path.isdir(matlab_root):
164         print "\033[32mAutodetermining if mex is properly set up\033[0m"
165         mex_installed = os.access(os.path.join(matlab_root, "bin/gccopts.sh"), os.R_OK) or os.access(os.path.join(matlab_root, "bin/mexopts.sh"), os.R_OK)
166         if not mex_installed:
167             print
168             print "\033[33mMex is NOT properly set up. User interaction required.\033[0m"
169     else:
170         print "\033[33mMatlab not found.\nDo you have Matlab and did you already execute `mex -setup' once? [yn]\033[0m"
171         mex_installed = read_yes_no()
172     no_matlab = False
173     if not mex_installed:
174         print "\033[32mExecuting `mex -setup'\033[0m"
175         if not os.system(os.path.join(matlab_root, "bin/mex") + " -setup"):
176             print "Command failed! Do you have Matlab installed? NOT BUILDING MATLAB INTERFACE!"
177             no_matlab = True
178
179     # Setup virtualenv
180     if os.access(os.path.join(dest, "bin/python"), os.F_OK):
181         print "\033[32mFound local Python installation in destination. No virtualenv required.\033[0m"
182     else:
183         print "\033[32mDownloading a copy of virtualenv.. \033[0m"
184         urllib.urlretrieve("https://raw.github.com/pypa/virtualenv/master/virtualenv.py", "virtualenv.py", urlretrieve_callback)
185         print "\nok"
186         print "\033[32mSetting up virtualenv for", dest, ".. \033[0m"
187         os.system("virtualenv ../")
188     os.environ["VIRTUAL_ENV"] = dest
189     os.environ["PYTHON_CMD"] = os.path.join(dest, "bin/python")
190     os.environ["PATH"] = os.path.join(dest, "bin/") + ":" +  os.environ["PATH"]
191     if "PYTHONHOME" in os.environ:
192         del os.environ["PYTHONHOME"]
193
194     # Check for numpy
195     has_numpy = os.system(repr(os.environ["PYTHON_CMD"]) + " -c 'import numpy' 2>/dev/null") == 0
196     if has_numpy:
197         print "\033[32mnumpy is already installed, skipping installation\033[0m"
198     else:
199         print "\033[32mInstalling numpy.. \033[0m"
200         if os.system("pip install numpy") != 0:
201             print "\033[31mError\033[0m"
202             sys.exit(1)
203     
204     # Check for scons WITHIN THE PREFIX
205     if os.system("which scons >/dev/null") == 0:
206         print "\033[32mscons is already installed, skipping installation\033[0m"
207     else:
208         print "\033[32mInstalling scons.. \033[0m"
209         if os.system("pip install scons") != 0:
210             # pip can not install scons because of a silly --single-version-externally-managed error
211             print "\033[32mError while installing scons. THIS IS PROBABLY NORMAL and a bug in pip/scons. Trying to fix this manually..\033[0m"
212             os.chdir("scons")
213             if os.system("python setup.py install") != 0:
214                 print "\033[31mError\033[0m. Nope. This is a real error."
215                 sys.exit(1)
216             os.chdir("..")
217
218     # Download & build Sundials
219     # This can be problematic on 64-bit systems with the Python interface
220     sundials_version = "2.5.0"
221     has_cvode = compile_test("""
222         #include <cvode/cvode.h>
223         #include <ida/ida.h>
224
225         int main(int argc, char *argv[]) {
226             return 0;
227         }
228     """)
229     if has_cvode:
230         print "\033[32mSystemwide Sundials already found. Skipping build..\033[0m"
231     elif os.access(os.path.join(dest, "lib/libsundials_cvode.a"), os.F_OK):
232         print "\033[32mSundials already found in destination. Skipping build..\033[0m"
233     else:
234         print "\033[32mDownloading and installing Sundials.. \033[0m"
235         urllib.urlretrieve("https://computation.llnl.gov/casc/sundials/download/code/sundials-" + sundials_version + ".tar.gz", "sundials.tar.gz", urlretrieve_callback)
236         print "\n"
237         os.system("tar xzf sundials.tar.gz")
238         os.chdir("sundials-" + sundials_version)
239         old_cflags = "" if "CFLAGS" not in os.environ else os.environ["CFLAGS"]
240         # -fPIC is required for 64bit systems
241         os.environ["CFLAGS"] = "-O3 -fPIC -Wall " + old_cflags
242         if os.system("./configure --prefix=\"%s\" --disable-idas --disable-kinsol --disable-cpodes && make && make install" % dest) != 0:
243             print "\033[31mError\033[0m"
244             sys.exit(1)
245         if old_cflags:
246             os.environ["CFLAGS"] = old_cflags
247         else:
248             del os.environ["CFLAGS"]
249         os.chdir("..")
250         
251     # Download Cantera
252     present = [ x for x in os.listdir(".") if "cantera-2." in x and os.path.isdir(x) ]
253     if present:
254         print "\033[32mCantera " + present[0] + " already present. Using that version..\033[0m"
255         os.chdir(present[0])
256         if os.path.isdir(".svn") and "-trunk" in present[0]:
257             print "\033[32mUpdating to latest trunk..\033[0m"
258             if os.system("svn up") != 0:
259                 print "\033[31mError\033[0m: Failed to update Cantera\033[0m"
260                 sys.exit(1)
261     else:
262         print "\033[33mDo you want the latest stable (y) or trunk (n) version?\033[0m"
263         if read_yes_no():
264             print "\033[32mDownloading Cantera from the homepage.. \033[0m"
265             page = urllib.urlopen("http://code.google.com/p/cantera/downloads/list").read()
266             version = re.search(r"(cantera-2\.[0-9.]+)\.tar\.gz", page)
267             if not version:
268                 print "\033[31mError[\0330m: Failed to find Cantera download on homepage"
269                 sys.exit(1)
270             print " Version: " + version.group(1)
271             urllib.urlretrieve("http://cantera.googlecode.com/files/" + version.group(0), "cantera.tar.gz", urlretrieve_callback)
272             print "\n"
273             if os.system("tar xzf cantera.tar.gz") != 0:
274                 print "\033[31mError\033[0m: Failed to unpack Cantera"
275                 sys.exit(1)
276             os.chdir(version.group(1))
277         else:
278             print "\033[32mDownloading Cantera from the subversion repository.. \033[0m"
279             if os.system("svn checkout http://cantera.googlecode.com/svn/cantera/trunk/ cantera-2.x-trunk") != 0:
280                 print "\033[31mError\033[0m: Failed to download Cantera"
281                 sys.exit(1)
282             os.chdir("cantera-2.x-trunk")
283
284             
285
286     # Check for doxygen
287     if os.system("which doxygen >/dev/null 2>&1") != 0:
288         print "\033[32mDoxygen not found. NOT building documentation.\033[0m"
289         doxygen = "no"
290     else:
291         print "\033[32mDoxygen found. Building documentation\033[0m"
292         doxygen = "yes"
293         # We need to do some adjustments.. at least Debian's latest doxygen version has
294         # problems with the configuration
295         doxyfile = open("doc/doxygen/Doxyfile").read()
296         doxyfile = doxyfile.replace("EXCLUDE_PATTERNS  ", "#EXCLUDE_PATTERNS")
297         doxyfile = doxyfile.replace("LATEX_BATCHMODE  ", "LATEX_BATCHMODE = YES\n# LATEX_BATCHMODE  ")
298         open("doc/doxygen/Doxyfile", "w").write(doxyfile)
299
300     # The configuration file cantera.conf does not work properly when invoked
301     # from a script, so we will put the configuration onto the command line
302     config = " ".join(( a + "='" + b.replace("'", r"\'") + "'" for (a, b) in {
303         "prefix": dest,
304         "python_package": "full",
305         "python_cmd": os.path.join(dest, "bin/python"),
306         "python_array": "numpy",
307         "matlab_toolbox": "y" if not no_matlab else "n",
308         "matlab_path": "" if no_matlab else matlab_root,
309         "doxygen_docs": doxygen,
310         "use_sundials": "y",
311         "python3_package": "n",
312         "sundials_include": os.path.join(dest, "include"),
313         "sundials_libdir": os.path.join(dest, "lib"),
314         "build_thread_safe": "y",
315     }.items()))
316
317     # Configure & build Cantera
318     print "\033[32mBuilding Cantera.. \033[0m"
319     if os.system("scons build " + config) != 0:
320         print "\033[31mError\033[0m"
321         sys.exit(1)
322     print "\033[32mInstalling Cantera.. \033[0m"
323     if os.system("scons install " + config) != 0:
324         print "\033[31mError\033[0m"
325         sys.exit(1)
326
327     os.chdir("../../")
328
329     # Install sd-toolbox
330     python_dirs = [ x for x in os.listdir("lib") if os.path.isdir("lib/" + x) and "python" in x ]
331     python_dir = python_dirs[0]
332     if os.path.isdir("lib/{0}/site-packages/SDToolbox".format(python_dir)):
333         print "\033[32mSD_Toolbox already installed. Skipping..\033[0m"
334     else:
335         print "\033[32mInstalling SD_Toolbox.. \033[0m"
336         os.system("""
337             mkdir -p lib/{0}/site-packages/SDToolbox;
338             cd lib/{0}/site-packages/SDToolbox;
339             wget http://www2.galcit.caltech.edu/EDL/public/cantera/1.7/python/lin/sdt/SDToolbox.tar &&
340             tar xf SDToolbox.tar &&
341             rm -f SDToolbox.tar &&
342             echo "from numpy import *\nfrom numpy.numarray import *" | tee numarray.py Numeric.py
343         """.format(python_dir))
344         os.system("""
345             mkdir -p share/doc/Cantera_SD_Toolbox/demos;
346             cd share/doc/Cantera_SD_Toolbox/demos;
347             wget http://www2.galcit.caltech.edu/EDL/public/cantera/1.7/python/lin/sdt/demos.tar &&
348             tar xf demos.tar &&
349             mv demos/* . && rmdir demos &&
350             rm -f demos.tar &&
351             wget http://www2.galcit.caltech.edu/EDL/public/cantera/mechs/cti/web/h2o2_highT.cti &&
352             wget http://www2.galcit.caltech.edu/EDL/public/cantera/mechs/cti/web/h2air_highT.cti &&
353             wget http://www2.galcit.caltech.edu/EDL/public/cantera/1.7/matlab/win/SDToolbox/demos.zip &&
354             unzip demos.zip &&
355             rm -f demos.zip &&
356             wget http://www2.galcit.caltech.edu/EDL/public/cantera/1.7/matlab/win/SDToolbox/demos_advanced.zip &&
357             unzip demos_advanced.zip &&
358             rm -f demos_advanced.zip
359         """)
360         os.system("""
361             mkdir -p share/matlab/toolbox/ &&
362             cd share/matlab/toolbox &&
363             wget http://www2.galcit.caltech.edu/EDL/public/cantera/1.7/matlab/win/SDToolbox/SDToolbox.zip &&
364             unzip SDToolbox.zip &&
365             rm -f SDToolbox.zip
366         """)
367
368     # There might be other interesting toolboxes to include here!? I don't know of any (yet).
369
370     # Clean up & create run script
371     os.system("""
372             mkdir -p share/doc/Cantera share/cantera/xml; mv share/cantera/doc share/cantera/samples share/doc/Cantera/;
373             rm -f bin/activate bin/activate.csh bin/activate.fish bin/activate_this.py;
374     """)
375     print "\033[33mRemove the temporary files now?\033[0m"
376     if read_yes_no():
377         print "\033[32mCleaning up.. \033[0m"
378         os.system("""
379             rm -rf build
380         """)
381
382     print "\033[32mCreating run script.. \033[0m"
383     setup_env = open("setupenv", "w")
384     print >> setup_env, script_template.format(dest=dest, stdcpplib=ld_search("libstdc++.so.6"))
385     setup_env.close()
386
387     print
388     print "\033[32;1mDone\033[0m"
389     print
390
391     print "Source the script `setupenv' from the installation root to"
392     print "setup your environment for Cantera!"
393     print
394
395 if __name__ == '__main__':
396     try:
397         main()
398     except KeyboardInterrupt:
399         print