Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 185 additions & 6 deletions messenger/make.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,136 @@
#!/usr/bin/python

"""
Make : building messenger mex file.

Some functions have been taken from the pexpect module (https://pexpect.readthedocs.org/en/latest/)

The license for pexpect is below:

This license is approved by the OSI and FSF as GPL-compatible.
http://opensource.org/licenses/isc-license.txt

Copyright (c) 2012, Noah Spurrier <noah@noah.org>
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""

from __future__ import print_function
import os
import platform
import sys
import shlex
import shutil
import subprocess
import stat

try:
import pty
except ImportError:
pty = None

def is_executable_file(path):
"""Checks that path is an executable regular file (or a symlink to a file).

This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but
on some platforms :func:`os.access` gives us the wrong answer, so this
checks permission bits directly.

Note
----
This function is taken from the pexpect module, see module doc-string for
license.
"""
# follow symlinks,
fpath = os.path.realpath(path)

# return False for non-files (directories, fifo, etc.)
if not os.path.isfile(fpath):
return False

# On Solaris, etc., "If the process has appropriate privileges, an
# implementation may indicate success for X_OK even if none of the
# execute file permission bits are set."
#
# For this reason, it is necessary to explicitly check st_mode

# get file mode using os.stat, and check if `other',
# that is anybody, may read and execute.
mode = os.stat(fpath).st_mode
if mode & stat.S_IROTH and mode & stat.S_IXOTH:
return True

# get current user's group ids, and check if `group',
# when matching ours, may read and execute.
user_gids = os.getgroups() + [os.getgid()]
if (os.stat(fpath).st_gid in user_gids and
mode & stat.S_IRGRP and mode & stat.S_IXGRP):
return True

# finally, if file owner matches our effective userid,
# check if `user', may read and execute.
user_gids = os.getgroups() + [os.getgid()]
if (os.stat(fpath).st_uid == os.geteuid() and
mode & stat.S_IRUSR and mode & stat.S_IXUSR):
return True

return False


def which(filename):
'''This takes a given filename; tries to find it in the environment path;
then checks if it is executable. This returns the full path to the filename
if found and executable. Otherwise this returns None.

Note
----
This function is taken from the pexpect module, see module doc-string for
license.
'''

# Special case where filename contains an explicit path.
if os.path.dirname(filename) != '' and is_executable_file(filename):
return filename
if 'PATH' not in os.environ or os.environ['PATH'] == '':
p = os.defpath
else:
p = os.environ['PATH']
pathlist = p.split(os.pathsep)
for path in pathlist:
ff = os.path.join(path, filename)
if pty:
if is_executable_file(ff):
return ff
else:
pathext = os.environ.get('Pathext', '.exe;.com;.bat;.cmd')
pathext = pathext.split(os.pathsep) + ['']
for ext in pathext:
if os.access(ff + ext, os.X_OK):
return ff + ext
return None


use_shell = True if sys.platform.startswith("win32") else False


def make_str(byte_or_str):
return byte_or_str if isinstance(byte_or_str, str) \
else str(byte_or_str.decode("UTF-8"))


def esc(path):
if ' ' in path:
return '"' + path + '"'
else:
return path


def get_messenger_dir():
Expand Down Expand Up @@ -50,7 +175,7 @@ def do_build(make_cmd, messenger_exe):
print('Building %s...' % messenger_exe)
print(make_cmd)
messenger_dir = get_messenger_dir()
subprocess.check_output(shlex.split(make_cmd), shell=True)
subprocess.check_output(shlex.split(make_cmd), shell=use_shell)

messenger_loc = os.path.join(messenger_dir, messenger_exe)

Expand All @@ -67,9 +192,56 @@ def build_octave():
do_build(make_cmd, 'messenger.mex')


def which_matlab():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will all be replaced by the pexpect stuff that @blink1073 pointed out. Does that work well on Windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does pexpext work if it's not in the path?

In my which function I look in the default installation location for MATLAB
even if it's not in the path. Annoying because MATLAB uses a different
structure on every platform. It would be great if pexpect could replace
this.
On Apr 10, 2015 4:09 PM, "Ariel Rokem" notifications@github.com wrote:

In messenger/make.py
#178 (comment)
:

@@ -67,9 +76,55 @@ def build_octave():
do_build(make_cmd, 'messenger.mex')

+def which_matlab():

This will all be replaced by the pexpect stuff that @blink1073
https://github.com/blink1073. Does that work well on Windows?


Reply to this email directly or view it on GitHub
https://github.com/arokem/python-matlab-bridge/pull/178/files#r28187251.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does pexpext work if it's not in the path?

pexept assumes the binaries are in the $PATH since it replicates BSD utils which.

However, get_matlab_bin() should return absolute path of the matlab binary regardless of if it is in the path or not.

Then running get_matlab_env(matlab='matlab') with the path to the binary will also return a dictionary containing that particular configuration environment variables.

The only known instance of where this breaks is if a user has two different installations of matlab installed. If that is the cas, you can override the discovery machinery by specifying the MATLAB_BIN variable in the config.ini file in the root directory to specify the copy of matlab you'd like to target.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.. the function which_matlab which I wrote doesn't reimplement which
at all. It calls "which matlab." However it usually isn't in the path, so
when that fails it looks in the default location on all 3 platforms.

I was actually inspired by your function to make it work on all 3
platforms. However, your code assumes the same structure of MATLAB on all 3
platforms, which for some reason isn't the case (why MATHWORKS?), which is
why I had to use if statements and handle each OS separately.

On Fri, Apr 10, 2015 at 4:41 PM, Sang Han notifications@github.com wrote:

In messenger/make.py
#178 (comment)
:

@@ -67,9 +76,55 @@ def build_octave():
do_build(make_cmd, 'messenger.mex')

+def which_matlab():

pexept assumes the binaries are in the path since it replicates BSD utils
which.

However, get_matlab_bin should return absolute path of the matlab binary
regardless of if it is in the path or not.

https://github.com/jjangsangy/python-matlab-bridge/blob/master/messenger/make.py#L206

Then running get_matlab_env with the path to the binary will also return
a dictionary containing that particular configuration environment variables.

https://github.com/jjangsangy/python-matlab-bridge/blob/master/messenger/settings.py#L15


Reply to this email directly or view it on GitHub
https://github.com/arokem/python-matlab-bridge/pull/178/files#r28188377.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the structure can be different.

I know that sometimes there is a R2014_* directory that is present in windows but not on OSX or Linux. I took care of that in this section.

        for p in os.listdir(os.path.join(programs[host], *matlab)):
            # Checks for this folder in the matlab directory
            if p.startswith('R20'):
                matlab.append(p)

        matlab.append('bin')

Sorry, maybe I should have written more comments. Honestly didn't even think anyone would even read this :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, it was easier for me to rewrite this than try to debug it when it
didn't work on Windows when I tried to merge your PR.

Is it possible to just apply the simplifications you need (like 1 config
file, integration with setup.py) using the functions in my PR?

On Fri, Apr 10, 2015 at 5:00 PM, Sang Han notifications@github.com wrote:

In messenger/make.py
#178 (comment)
:

@@ -67,9 +76,55 @@ def build_octave():
do_build(make_cmd, 'messenger.mex')

+def which_matlab():

So the structure can be different.

I know that sometimes there is a R2014_* directory that is present in
windows but not on OSX or Linux. I took care of that with this code.

Sorry, maybe I should have written more comments. Honestly didn't even
think anyone would even read this :)

    for p in os.listdir(os.path.join(programs[host], *matlab)):
        if p.startswith('R20'):
            matlab.append(p)

    matlab.append('bin')


Reply to this email directly or view it on GitHub
https://github.com/arokem/python-matlab-bridge/pull/178/files#r28189008.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also dude, it wasn't bad to read; it's just a bit different than my coding
style so.

On Fri, Apr 10, 2015 at 5:05 PM, Ali Ebrahim aebrahim@ucsd.edu wrote:

Honestly, it was easier for me to rewrite this than try to debug it when
it didn't work on Windows when I tried to merge your PR.

Is it possible to just apply the simplifications you need (like 1 config
file, integration with setup.py) using the functions in my PR?

On Fri, Apr 10, 2015 at 5:00 PM, Sang Han notifications@github.com
wrote:

In messenger/make.py
#178 (comment)
:

@@ -67,9 +76,55 @@ def build_octave():
do_build(make_cmd, 'messenger.mex')

+def which_matlab():

So the structure can be different.

I know that sometimes there is a R2014_* directory that is present in
windows but not on OSX or Linux. I took care of that with this code.

Sorry, maybe I should have written more comments. Honestly didn't even
think anyone would even read this :)

    for p in os.listdir(os.path.join(programs[host], *matlab)):
        if p.startswith('R20'):
            matlab.append(p)

    matlab.append('bin')


Reply to this email directly or view it on GitHub
https://github.com/arokem/python-matlab-bridge/pull/178/files#r28189008
.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest that we finish this PR, and then refine the config system on
another PR.

On Fri, Apr 10, 2015 at 5:06 PM, Ali Ebrahim notifications@github.com
wrote:

In messenger/make.py
#178 (comment)
:

@@ -67,9 +76,55 @@ def build_octave():
do_build(make_cmd, 'messenger.mex')

+def which_matlab():

Honestly, it was easier for me to rewrite this than try to debug it when
it didn't work on Windows when I tried to merge your PR. Is it possible to
just apply the simplifications you need (like 1 config file, integration
with setup.py) using the functions in my PR?
… <#14ca5cc41acde2db_>
On Fri, Apr 10, 2015 at 5:00 PM, Sang Han notifications@github.com
wrote: In messenger/make.py <
https://github.com/arokem/python-matlab-bridge/pull/178#discussion_r28189008>
: > @@ -67,9 +76,55 @@ def build_octave(): > do_build(make_cmd,
'messenger.mex') > > > +def which_matlab(): So the structure can be
different. I know that sometimes there is a R2014_* directory that is
present in windows but not on OSX or Linux. I took care of that with this
code. Sorry, maybe I should have written more comments. Honestly didn't
even think anyone would even read this :) for p in
os.listdir(os.path.join(programs[host], *matlab)): if p.startswith('R20'):
matlab.append(p) matlab.append('bin') — Reply to this email directly or
view it on GitHub <
https://github.com/arokem/python-matlab-bridge/pull/178/files#r28189008>.


Reply to this email directly or view it on GitHub
https://github.com/arokem/python-matlab-bridge/pull/178/files#r28189149.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you embed the call to the pexpect which in a function that will gracefully handle failure modes (e.g. matlab not on the path)?

try:
matlab_path = which('matlab').strip()
matlab_path = make_str(matlab_path)
return os.path.dirname(os.path.realpath(matlab_path))
except (OSError, subprocess.CalledProcessError):
def ensure_path(path, extension=''):
return os.path.isdir(path) and \
os.path.isfile(os.path.join(path, "matlab" + extension))

# need to guess the location of MATLAB
if sys.platform.startswith("darwin"):
MATLABs = [os.path.join("/Applications", i, "bin")
for i in os.listdir("/Applications")
if i.startswith("MATLAB_R")]
# only want ones with MATLAB executables
# sort so we can get the latest
MATLABs = list(sorted(filter(ensure_path, MATLABs)))
return MATLABs[-1] if len(MATLABs) > 0 else None
elif sys.platform.startswith("win32"):
MATLAB_loc = "C:\\Program Files\\MATLAB"
print(MATLAB_loc)
if not os.path.isdir(MATLAB_loc):
return None
MATLABs = [os.path.join(MATLAB_loc, i, "bin")
for i in os.listdir(MATLAB_loc)]
print(MATLABs)
print(i)
# only want ones with MATLAB executables
# sort so we can get the latest
MATLABs = list(sorted(filter(lambda x: ensure_path(x, ".exe"),
MATLABs)))
print(MATLABs)
return MATLABs[-1] if len(MATLABs) > 0 else None
elif sys.platform.startswith("linux"):
MATLAB_loc = "/usr/local/MATLAB/"
if not os.path.isdir(MATLAB_loc):
return None
MATLABs = [os.path.join(MATLAB_loc, i, "bin")
for i in os.listdir(MATLAB_loc)
if i.startswith("R")]
# only want ones with MATLAB executables
# sort so we can get the latest
MATLABs = list(sorted(filter(ensure_path, MATLABs)))
return MATLABs[-1] if len(MATLABs) > 0 else None


def build_matlab(static=False):
"""build the messenger mex for MATLAB

static : bool
Determines if the zmq library has been statically linked.
If so, it will append the command line option -DZMQ_STATIC
Expand All @@ -78,14 +250,19 @@ def build_matlab(static=False):
cfg = get_config()
# To deal with spaces, remove quotes now, and add
# to the full commands themselves.
matlab_bin = cfg['matlab_bin'].strip('"')
if 'matlab_bin' in cfg and cfg['matlab_bin'] != '.':
matlab_bin = cfg['matlab_bin'].strip('"')
else: # attempt to autodetect MATLAB filepath
matlab_bin = which_matlab()
if matlab_bin is None:
raise ValueError("specify 'matlab_bin' in cfg file")
# Get the extension
extcmd = '"' + os.path.join(matlab_bin, "mexext") + '"'
extension = subprocess.check_output(extcmd, shell=True)
extcmd = esc(os.path.join(matlab_bin, "mexext"))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work on python 3, because matlab_bin is bytes and "mexext" is a string.

I propose:
extcmd = esc(os.path.join(matlab_bin.decode('utf-8'), "mexext"))

extension = subprocess.check_output(extcmd, shell=use_shell)
extension = extension.decode('utf-8').rstrip('\r\n')

# Build the mex file
mex = '"' + os.path.join(matlab_bin, "mex") + '"'
mex = esc(os.path.join(matlab_bin, "mex"))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

paths = "-L%(zmq_lib)s -I%(zmq_inc)s" % cfg
make_cmd = '%s -O %s -lzmq ./src/messenger.c' % (mex, paths)
if static:
Expand All @@ -107,6 +284,8 @@ def build_matlab(static=False):
if args.target == "matlab":
build_matlab(static=args.static)
elif args.target == "octave":
if args.static:
raise ValueError("static building not yet supported for octave")
build_octave()
else:
raise ValueError()
2 changes: 1 addition & 1 deletion messenger/mexa64/local.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MATLAB_BIN=/home/silvester/matlab2014b/bin/
MATLAB_BIN=
OCTAVE_INC=/usr/include
OCTAVE_LIB=/usr/lib/x86_64-linux-gnu/
ZMQ_INC=
Expand Down
Binary file modified messenger/mexa64/messenger.mexa64
Binary file not shown.
Binary file removed messenger/mexa64/messenger.mexa64.zmq4
Binary file not shown.
6 changes: 3 additions & 3 deletions messenger/mexmaci64/local.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MATLAB_BIN=/Applications/MATLAB_R2012b.app/bin
ZMQ_INC=/usr/include
ZMQ_LIB=/usr/lib/x86_64-linux-gnu/
MATLAB_BIN=
ZMQ_INC=
ZMQ_LIB=
OCTAVE_INC=
OCTAVE_LIB=
Binary file modified messenger/mexmaci64/messenger.mexmaci64
Binary file not shown.
3 changes: 0 additions & 3 deletions messenger/mexw32/local.cfg

This file was deleted.

2 changes: 1 addition & 1 deletion messenger/mexw64/local.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MATLAB_BIN="C:\Program Files\MATLAB\2014b\bin"
MATLAB_BIN="C:\Program Files\MATLAB\2013a\bin"
OCTAVE_INC="C:\Octave\Octave-3.8.2\include\octave-3.8.2\octave"
OCTAVE_LIB="C:\Octave\Octave-3.8.2\lib\octave\3.8.2"
ZMQ_INC="C:\zeromq-4.0.5\include"
Expand Down
Binary file modified messenger/mexw64/messenger.mexw64
Binary file not shown.
43 changes: 20 additions & 23 deletions messenger/src/messenger.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/* Set a 200MB receiver buffer size */
#define BUFLEN 200000000

/* The variable cannot be named socket on windows */
void *ctx, *socket_ptr;
static int initialized = 0;

Expand All @@ -26,28 +27,18 @@ int initialize(char *socket_addr) {
}
}

/* Listen over an existing socket
* Now the receiver buffer is pre-allocated
* In the future we can possibly use multi-part messaging
*/
int listen_zmq(char *buffer, int buflen) {
/* Check if the ZMQ server is intialized and print an error if not */
int checkInitialized(void) {
if (!initialized) {
mexErrMsgTxt("Error: ZMQ session not initialized");
return 0;
}

return zmq_recv(socket_ptr, buffer, buflen, ZMQ_NOBLOCK);
}

/* Sending out a message */
int respond(char *msg_out, int len) {
if (!initialized) {
mexErrMsgTxt("Error: ZMQ session not initialized");
else {
return 1;
}

return zmq_send(socket_ptr, msg_out, len, 0);

}


/* Cleaning up after session finished */
void cleanup (void) {
/* Send a confirmation message to the client */
Expand Down Expand Up @@ -106,14 +97,18 @@ void mexFunction(int nlhs, mxArray *plhs[],

/* Listen over an existing socket */
} else if (strcmp(cmd, "listen") == 0) {
int byte_recvd;
char *recv_buffer = mxCalloc(BUFLEN, sizeof(char));
zmq_pollitem_t polls[] = {{socket_ptr, 0, ZMQ_POLLIN, 0}};

if (!checkInitialized()) return;

/* allow MATLAB to draw its graphics every 20ms */
while (zmq_poll(polls, 1, 20000) == 0) {
mexEvalString("drawnow");
}

int byte_recvd = listen_zmq(recv_buffer, BUFLEN);

while (byte_recvd == -1 && errno == EAGAIN) {
mexCallMATLAB(0, NULL, 0, NULL, "drawnow");
byte_recvd = listen_zmq(recv_buffer, BUFLEN);
}
byte_recvd = zmq_recv(socket_ptr, recv_buffer, BUFLEN, 0);

/* Check if the received data is complete and correct */
if ((byte_recvd > -1) && (byte_recvd <= BUFLEN)) {
Expand All @@ -138,13 +133,15 @@ void mexFunction(int nlhs, mxArray *plhs[],
mexErrMsgTxt("Please provide the message to send");
}

if (!checkInitialized()) return;

msglen = mxGetNumberOfElements(prhs[1]);
msg_out = mxArrayToString(prhs[1]);

plhs[0] = mxCreateLogicalMatrix(1, 1);
p = mxGetLogicals(plhs[0]);

if (msglen == respond(msg_out, msglen)) {
if (msglen == zmq_send(socket_ptr, msg_out, msglen, 0)) {
p[0] = 1;
} else {
p[0] = 0;
Expand Down