diff --git a/messenger/make.py b/messenger/make.py index 7048aab..3419422 100755 --- a/messenger/make.py +++ b/messenger/make.py @@ -1,4 +1,28 @@ #!/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 + 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 @@ -6,6 +30,107 @@ 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(): @@ -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) @@ -67,9 +192,56 @@ def build_octave(): do_build(make_cmd, 'messenger.mex') +def which_matlab(): + 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 @@ -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")) + 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")) paths = "-L%(zmq_lib)s -I%(zmq_inc)s" % cfg make_cmd = '%s -O %s -lzmq ./src/messenger.c' % (mex, paths) if static: @@ -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() diff --git a/messenger/mexa64/local.cfg b/messenger/mexa64/local.cfg index 54ff2f8..a9a742c 100644 --- a/messenger/mexa64/local.cfg +++ b/messenger/mexa64/local.cfg @@ -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= diff --git a/messenger/mexa64/messenger.mexa64 b/messenger/mexa64/messenger.mexa64 index 9ff2538..f585eab 100755 Binary files a/messenger/mexa64/messenger.mexa64 and b/messenger/mexa64/messenger.mexa64 differ diff --git a/messenger/mexa64/messenger.mexa64.zmq4 b/messenger/mexa64/messenger.mexa64.zmq4 deleted file mode 100755 index a01118c..0000000 Binary files a/messenger/mexa64/messenger.mexa64.zmq4 and /dev/null differ diff --git a/messenger/mexmaci64/local.cfg b/messenger/mexmaci64/local.cfg index 1de2d8b..138795a 100644 --- a/messenger/mexmaci64/local.cfg +++ b/messenger/mexmaci64/local.cfg @@ -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= diff --git a/messenger/mexmaci64/messenger.mexmaci64 b/messenger/mexmaci64/messenger.mexmaci64 index 3cc7b8d..a48edc0 100755 Binary files a/messenger/mexmaci64/messenger.mexmaci64 and b/messenger/mexmaci64/messenger.mexmaci64 differ diff --git a/messenger/mexw32/local.cfg b/messenger/mexw32/local.cfg deleted file mode 100644 index d8d9ff6..0000000 --- a/messenger/mexw32/local.cfg +++ /dev/null @@ -1,3 +0,0 @@ -MATLAB_BIN=/Applications/MATLAB_R2012a.app/bin -HEADER_PATH=/usr/include -LIB_PATH=/usr/local/lib diff --git a/messenger/mexw64/local.cfg b/messenger/mexw64/local.cfg index daf8ab6..ab61682 100644 --- a/messenger/mexw64/local.cfg +++ b/messenger/mexw64/local.cfg @@ -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" diff --git a/messenger/mexw64/messenger.mexw64 b/messenger/mexw64/messenger.mexw64 index f1ec6fa..3f0abd5 100644 Binary files a/messenger/mexw64/messenger.mexw64 and b/messenger/mexw64/messenger.mexw64 differ diff --git a/messenger/src/messenger.c b/messenger/src/messenger.c index 4be1b71..102999e 100644 --- a/messenger/src/messenger.c +++ b/messenger/src/messenger.c @@ -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; @@ -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 */ @@ -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)) { @@ -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;