/
__init__.py
195 lines (151 loc) · 6.9 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
from __future__ import with_statement # with statement in Python 2.5
# mex_builder.py: Matlab extension builder
#
# by Marc Joliet, marcec@gmx.de, 2010.04.22
# (loosely based on mex.py by Joe VanAndel, vanandel@ucar.edu, 2010/1/15)
#
# This is a little SCons builder tool that finds Matlabs installation directory
# and sets various variables of the build environment. To do that, it starts a
# Matlab subprocess that issues a few commands that print to a file, and parses
# the file. Furthermore, it adds a pseudo-builder method to the environment
# that wraps the SharedLibrary builder.
import os
import subprocess as subp
import sys
import pickle
# Windows only: catches Matlabs output, since you cannot print to stdout
_matlab_log_file = '.matlab_output'
# caches the Matlab variables
_vars_file = '.matlab_vars_cache'
def _load_matlab_vars(env):
"""Load various Matlab specific variables from a cache file via the pickle
module.
"""
with open( _vars_file, 'r' ) as f:
p = pickle.Unpickler(f)
env['MATLAB'] = p.load()
def _cache_matlab_vars(matlab_vars):
"""Store various Matlab specific variables in a cache file via the pickle
module.
"""
# store the objects in a file
with open(_vars_file, 'w') as f:
p = pickle.Pickler(f)
p.dump(matlab_vars)
def _gen_matlab_env(env, **kwargs):
"""Obtain various Matlab specific variables and put them in env['MATLAB'].
"""
if os.path.isfile(_vars_file):
if not env.GetOption('silent'):
print "Loading Matlab vars from cache..."
_load_matlab_vars(env)
return
# invoke matlab and print required information
matlab_cmd = r"fprintf(1, '>>%s\n%s\n%s\n%s\n', mexext, matlabroot, computer('arch'), version); quit;"
cmd_line = ['matlab', '-nodesktop', '-nosplash']
if os.name == "nt":
cmd_line += ['-r', '"' + matlab_cmd + '"']
# stop Matlab from forking and output to a log file
cmd_line += ['-wait', '-logfile', _matlab_log_file]
try:
# open a Matlab subprocess that communicates over pipes
matlab_proc = subp.Popen(cmd_line, stdin=subp.PIPE, stdout=subp.PIPE)
# capture Matlabs stdout
mlab_out = matlab_proc.communicate(matlab_cmd)[0]
except BaseException, e:
# PEP 352 can't go ahead quickly enough, stupid args tuple. I want the
# message attribute back!
print >> sys.stderr, "Error:", ', '.join([repr(i) for i in e.args])
exit("Error calling Matlab, exiting.")
if os.name == 'nt':
with open(_matlab_log_file) as mlab_out:
mlab_out = mlab_out.readlines()[-4::]
else:
# everything before the first input line can be ignored
# NOTE: I'm not sure, but I think you can't change the '>>' string, so this
# should be reliable
mlab_out = mlab_out.split('>>')[-1].split('\n')
# save non-empty lines from stdout and strip surrounding whitespace
lines = [l.strip() for l in mlab_out if len(l)>0]
matlab_root = lines[1]
matlab_arch = lines[2].lower()
matlab_ver, matlab_release = lines[3].split()
env['MATLAB'] = {
"MEX_EXT": "." + lines[0],
"ROOT": matlab_root,
"ARCH": matlab_arch,
"VERSION": matlab_ver,
"RELEASE": matlab_release.strip('()'),
"SRC": os.sep.join([matlab_root, 'extern', 'src']),
"INCLUDE": os.sep.join([matlab_root, 'extern', 'include']),
"LIB_DIR": [os.sep.join([matlab_root, 'bin', matlab_arch])]
}
if matlab_arch == 'win32':
env['MATLAB']['LIB_DIR'] += \
[os.sep.join([matlab_root, 'extern', 'lib', 'win32', 'microsoft'])]
print "Caching Matlab vars..."
_cache_matlab_vars(env['MATLAB'])
def _mex_builder(env, target, source, gen_def=False, **kwargs):
"""A Mex pseudo-builder for SCons that wraps the SharedLibrary builder.
This pseudo-builder merely inserts some library dependencies, source file
dependencies and the compiler expected by Matlab. We don't return the
targets and sources, since they only change on Unix, where we don't do
anything but build anyway.
"""
source = list(source)
platform = env['PLATFORM']
# define operating system independent options and dependencies
if platform == "win32":
# Matlab doesn't follow the Windows standard and adds a 'lib' prefix anyway
env.AppendUnique(LIBS = ["libmex", "libmx"])
else:
env.AppendUnique(LIBS = ["mex", "mx"])
# OS dependent stuff, we assume GCC on Unix like platforms
if platform in ("posix", "darwin"):
# add "exceptions" option, without which any mex function that raises an
# exception (e.g., mexErrMsgTxt()) causes Matlab to crash
env.AppendUnique(CCFLAGS=["-fexceptions", "-pthread"])
elif platform == "win32":
if gen_def:
env.Replace(WINDOWS_INSERT_DEF = True)
# add the Textfile builder to the build environment
env.Tool('textfile')
# generate a .def file
env.Textfile(target,
source=["LIBRARY " + [s for s in source if target in s][0],
"EXPORTS mexFunction"],
TEXTFILESUFFIX='.def')
else:
exit("Oops, not a supported platform.")
# the need for mexversion.c was removed in Matlab version 7.9
if env['MATLAB']['RELEASE'] < "R2009a":
# give each Mex file its own mexversion object (prevents warnings
# from SCons and makes sure the same compiler options are used)
mexversion = env.Clone()
if os.name == 'nt':
mexversion_obj = mexversion.RES("mexversion_" + target,
os.sep.join([env['MATLAB']['INCLUDE'],
"mexversion.rc"]))
else:
mexversion_obj = mexversion.SharedObject("mexversion_" + target,
os.sep.join([env["MATLAB"]["SRC"],
"mexversion.c"]))
source.append(mexversion_obj)
env.AppendUnique(CPPDEFINES = ["MATLAB_MEX_FILE"],
CPPPATH = [env['MATLAB']['INCLUDE']],
LIBPATH = [env['MATLAB']['LIB_DIR']])
# add compile target: return the node object
return env.SharedLibrary(target, source,
SHLIBPREFIX="",
SHLIBSUFFIX=env['MATLAB']['MEX_EXT'],
**kwargs)
def generate(env, **kwargs):
_gen_matlab_env(env, **kwargs)
# add the Mex pseudo-builder to the environment
# NOTE: adding the function to $BUILDERS wraps it in a BuilderMethod object,
# which gives it the calling conventions of regular builders.
env['BUILDERS']['Mex'] = _mex_builder
def exists(env):
if not env.WhereIs("matlab"):
return False
return True