diff --git a/.gitignore b/.gitignore index 5bd58c1..4c79558 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ rebuildTags.sh # caused py setup.py develop pymatbridge.egg-info + +.idea/scopes/scope_settings.xml \ No newline at end of file diff --git a/LICENSE b/LICENSE index d3632fc..2759572 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,33 @@ +Pymatbridge +=========== + +A python interface to call out to Matlab(R). + + +License information +=================== + +Copyright (c) 2012 -- , Max Jaderberg, Ariel Rokem, Haoxing Zhao +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the Oxford University, Stanford University nor the names of +its contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Copyright (c) 2013. See "Contributors". MATLAB (R) is copyright of the Mathworks. All rights reserved. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..86ea71e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,24 @@ +include LICENSE +include config.ini +include README.md +include requirements.txt +include setup.cfg +include scripts/publish-notebook + +include pymatbridge/messenger/src/messenger.c + +include pymatbridge/messenger/mexa64/* +include pymatbridge/messenger/mexmaci64/* +include pymatbridge/messenger/mexw64/* +include pymatbridge/messenger/mexa64/* + +include pymatbridge/matlab/messenger.mex +include pymatbridge/matlab/messenger.mex.zmq3 +include pymatbridge/matlab/messenger.mexmaci64 +include pymatbridge/matlab/messenger.mexmaci64.zmq3 +include pymatbridge/matlab/util/json_v0.2.2/json/java/json.jar +include pymatbridge/matlab/util/json_v0.2.2/LICENSE + +recursive-include pymatbridge *.py *.ipynb *.m +recursive-include scripts * +recursive-include tools * \ No newline at end of file diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..26cb7fa --- /dev/null +++ b/config.ini @@ -0,0 +1,20 @@ +[Darwin] +; MATLAB_BIN=Applications/MATLAB_R2014a.app/bin +OCTAVE_INC=/usr/local/Cellar/octave/3.8.2/include/octave-3.8.2/octave +OCTAVE_LIB=/usr/local/Cellar/octave/3.8.2/lib/octave/3.8.2 +ZMQ_INC=/usr/local/include +ZMQ_LIB=/usr/local/lib + +[Windows] +; MATLAB_BIN=C:\Program Files\MATLAB\R2014b\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:/Program Files/ZeroMQ 4.0.4/include +ZMQ_LIB=C:/Program Files/ZeroMQ 4.0.4/lib + +[Linux] +; MATLAB_BIN=/usr/local/bin +; OCTAVE_INC=/usr/include +; OCTAVE_LIB=/usr/lib/x86_64-linux-gnu/ +ZMQ_INC= +ZMQ_LIB= diff --git a/messenger/__init__.py b/messenger/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/messenger/make.py b/messenger/make.py deleted file mode 100755 index 3419422..0000000 --- a/messenger/make.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/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 -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(): - # Check the system platform first - splatform = sys.platform - - if splatform.startswith('linux'): - messenger_dir = 'mexa64' - elif splatform.startswith('darwin'): - messenger_dir = 'mexmaci64' - elif splatform.startswith('win32'): - if splatform == "win32": - # We have a win64 messenger, so we need to figure out if this is 32 - # or 64 bit Windows: - if not platform.machine().endswith('64'): - raise ValueError("pymatbridge does not work on win32") - - # We further need to differniate 32 from 64 bit: - maxint = sys.maxsize - if maxint == 9223372036854775807: - messenger_dir = 'mexw64' - elif maxint == 2147483647: - messenger_dir = 'mexw32' - return messenger_dir - - -def get_config(): - messenger_dir = get_messenger_dir() - with open(os.path.join(messenger_dir, 'local.cfg')) as fid: - lines = fid.readlines() - - cfg = {} - for line in lines: - if '=' not in line: - continue - name, path = line.split('=') - cfg[name.lower()] = path.strip() or '.' - return cfg - - -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=use_shell) - - messenger_loc = os.path.join(messenger_dir, messenger_exe) - - shutil.move(messenger_exe, messenger_loc) - - if os.path.exists('messenger.o'): - os.remove('messenger.o') - - -def build_octave(): - paths = "-L%(octave_lib)s -I%(octave_inc)s -L%(zmq_lib)s -I%(zmq_inc)s" - paths = paths % get_config() - make_cmd = "mkoctfile --mex %s -lzmq ./src/messenger.c" % paths - 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 - when compiling the mex so it matches libzmq. - """ - cfg = get_config() - # To deal with spaces, remove quotes now, and add - # to the full commands themselves. - 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 = 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 = 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: - make_cmd += ' -DZMQ_STATIC' - do_build(make_cmd, 'messenger.%s' % extension) - - -if __name__ == '__main__': - import argparse - parser = argparse.ArgumentParser() - parser.add_argument( - "target", - choices=["matlab", "octave"], - type=str.lower, - help="target to be built") - parser.add_argument("--static", action="store_true", - help="staticly link libzmq") - args = parser.parse_args() - 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 deleted file mode 100644 index a9a742c..0000000 --- a/messenger/mexa64/local.cfg +++ /dev/null @@ -1,6 +0,0 @@ -MATLAB_BIN= -OCTAVE_INC=/usr/include -OCTAVE_LIB=/usr/lib/x86_64-linux-gnu/ -ZMQ_INC= -ZMQ_LIB= - diff --git a/messenger/mexmaci64/local.cfg b/messenger/mexmaci64/local.cfg deleted file mode 100644 index 138795a..0000000 --- a/messenger/mexmaci64/local.cfg +++ /dev/null @@ -1,5 +0,0 @@ -MATLAB_BIN= -ZMQ_INC= -ZMQ_LIB= -OCTAVE_INC= -OCTAVE_LIB= diff --git a/messenger/mexw64/README.md b/messenger/mexw64/README.md deleted file mode 100644 index 47100e8..0000000 --- a/messenger/mexw64/README.md +++ /dev/null @@ -1,41 +0,0 @@ -Dynamically linked building instructions ----------------------------------------- - -1) Install zeromq from the website: http://zeromq.org/distro:microsoft-windows - -2) Rename one of the lib/libzmq-*.lib files to libzmq.lib in the ZeroMQ - installation directory - -3) Add the ZeroMQ bin directory to your path. - -4) Edit the messenger/mexw64/local.cfg file in messenger to point to the - zeromq install directory (you will need to update both ZMQ_INC and ZMQ_LIB). - Also ensure the MATLAB directory is correct. - -5) Run ```make.py matlab``` in the messenger directory. This should build - messenger.mexw64 - -Statically linked building instructions ---------------------------------------- - -A statically linked library has the advantage of not requiring libzmq.dll to -be found in the path. For this reason, including it in the installer results -in a simpler and more robust installation process. While building a statically -linked mex is simple in practice, but because zeromq (as of 3/10/15) does not -provide a .lib for static linking with the windows installer, you will need to -compile this yourself. These directions are from zeromq 4.0.5. - -1) Download and unzip the zeromq zip file (listed as Windows sources) from - http://zeromq.org/intro:get-the-software - -2) In the builds/msvc directory open the msvc.sln file in Visual Studio. - -3) Create a new Platform for x64. In the Librarian section of properties, set - the target section to /Machine:X64 - -4) Build libzmq with the "StaticRelease" for x64. - -5) Edit the messenger/mexw64/local.cfg file to point to where you built ZeroMQ - and your MATLAB bin directory. - -6) Build messenger.mexw64 with ```make matlab --static``` diff --git a/messenger/mexw64/local.cfg b/messenger/mexw64/local.cfg deleted file mode 100644 index ab61682..0000000 --- a/messenger/mexw64/local.cfg +++ /dev/null @@ -1,5 +0,0 @@ -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" -ZMQ_LIB="C:\zeromq-4.0.5\lib" diff --git a/pymatbridge/__init__.py b/pymatbridge/__init__.py index 45d2145..bc6b244 100644 --- a/pymatbridge/__init__.py +++ b/pymatbridge/__init__.py @@ -1,5 +1,6 @@ from .pymatbridge import * -from .version import __version__ +from .__version__ import __version__ +from .__version__ import __build__ try: from .publish import * @@ -10,3 +11,4 @@ from .matlab_magic import * except ImportError: pass + diff --git a/pymatbridge/__version__.py b/pymatbridge/__version__.py new file mode 100644 index 0000000..8f0f77e --- /dev/null +++ b/pymatbridge/__version__.py @@ -0,0 +1,3 @@ +__version__ = '0.5.0' +__build__ = 'dev1' +__release__ = '.'.join([__version__, __build__]) diff --git a/pymatbridge/messenger/__init__.py b/pymatbridge/messenger/__init__.py new file mode 100644 index 0000000..8a14d96 --- /dev/null +++ b/pymatbridge/messenger/__init__.py @@ -0,0 +1,2 @@ +from .settings import * +from .make import * diff --git a/pymatbridge/messenger/__main__.py b/pymatbridge/messenger/__main__.py new file mode 100644 index 0000000..5820228 --- /dev/null +++ b/pymatbridge/messenger/__main__.py @@ -0,0 +1,41 @@ +import sys + +from argparse import ArgumentParser + +from .make import * + +def command_line(): + """ + Manages command line arguments. + + Returns + ------- + Namespace containing parsed arguments + """ + parser = ArgumentParser(prog='messenger') + + parser.add_argument( + "target", + choices=["matlab", "octave"], + type=str.lower, + help="target to be built" + ) + parser.add_argument( + "--static", + action="store_true", + help="statically link libzmq" + ) + return parser.parse_args() + +def main(): + args = command_line() + build = { + 'matlab': build_matlab, + 'octave': build_octave, + } + target = build[args.target] + + return target(static=args.static) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pymatbridge/messenger/make.py b/pymatbridge/messenger/make.py new file mode 100644 index 0000000..a4f8cde --- /dev/null +++ b/pymatbridge/messenger/make.py @@ -0,0 +1,207 @@ +from __future__ import print_function + +import os +import sys +import shlex +import shutil +import subprocess +import platform +import glob +import operator + +try: + from ConfigParser import ConfigParser +except ImportError: + from configparser import ConfigParser + +from . import settings + +__all__ = [ + 'get_messenger_dir', + 'get_matlab_bin', + 'get_config', + 'do_build', + 'build_octave', + 'build_matlab', + 'split_command_line', +] + +def split_command_line(command_line): + """ + This splits a command line into a list of arguments. It splits arguments + on spaces, but handles embedded quotes, double quotes, and escaped + characters. It's impossible to do this with a regular expression, so I + wrote a little state machine to parse the command line. + """ + arg_list = [] + arg = '' + + # Constants to name the states we can be in. + state_basic = 0 + state_esc = 1 + state_singlequote = 2 + state_doublequote = 3 + # The state when consuming whitespace between commands. + state_whitespace = 4 + state = state_basic + + for c in command_line: + if state == state_basic or state == state_whitespace: + if c == '\\': + # Escape the next character + state = state_esc + elif c == r"'": + # Handle single quote + state = state_singlequote + elif c == r'"': + # Handle double quote + state = state_doublequote + elif c.isspace(): + # Add arg to arg_list if we aren't in the middle of whitespace. + if state == state_whitespace: + # Do nothing. + None + else: + arg_list.append(arg) + arg = '' + state = state_whitespace + else: + arg = arg + c + state = state_basic + elif state == state_esc: + arg = arg + c + state = state_basic + elif state == state_singlequote: + if c == r"'": + state = state_basic + else: + arg = arg + c + elif state == state_doublequote: + if c == r'"': + state = state_basic + else: + arg = arg + c + + if arg != '': + arg_list.append(arg) + return arg_list + +def get_messenger_dir(): + host, is_64bit = platform.system(), platform.machine().endswith('64') + ostype = { + 'Darwin': 'mexmaci64', + 'Linux': 'mexa64', + 'Windows': 'mexw64', + } + if not is_64bit and host == 'Windows': + raise ValueError("pymatbridge does not support 32-bit Windows") + + return ostype[host] if is_64bit else 'mexw32' + + +def get_config(host, config='config.ini'): + + cfg = ConfigParser() + cfg.read(config) + + return dict(cfg.items(host)) + + +def do_build(make_cmd, messenger_exe): + + messenger_dir = get_messenger_dir() + use_shell = sys.platform.startswith('win32') + + print('Building %s...' % messenger_exe) + print(make_cmd) + + subprocess.check_output(make_cmd, shell=use_shell) + + messenger_loc = os.path.join(messenger_dir, messenger_exe) + + shutil.move(messenger_exe, os.path.join('messenger', messenger_loc)) + + if os.path.isfile('messenger.o'): + os.remove('messenger.o') + + +def build_octave(static=False): + + paths = "-L%(octave_lib)s -I%(octave_inc)s -L%(zmq_lib)s -I%(zmq_inc)s" + paths = paths % get_config(platform.system()) + make_cmd = "mkoctfile --mex %s -lzmq messenger/src/messenger.c" % paths + + do_build(split_command_line(make_cmd), 'messenger.mex') + + +def build_matlab(static=False, messenger='messenger.c'): + """ + Build the messenger mex for MATLAB + + Parameters + ---------- + static : bool + Determines if the zmq library has been statically linked. + If so, it will append the command line option -DZMQ_STATIC + when compiling the mex so it matches libzmq. + """ + matlab_bin, cfg = get_matlab_bin(), get_config(platform.system()) + + use_shell = sys.platform.startswith('win32') + extcmd = split_command_line(os.path.join(matlab_bin, "mexext")) + extension = subprocess.check_output(extcmd, shell=use_shell) + extension = extension.decode('utf-8').rstrip() + + # Build the mex file + mex = os.path.join(matlab_bin, "mex") + paths = "-L'%(zmq_lib)s' -I'%(zmq_inc)s'" % cfg + make_cmd = '%s -O %s -lzmq %s' % (mex, paths, messenger) + + if static: + make_cmd += ' -DZMQ_STATIC' + + print(make_cmd) + subprocess.check_output(split_command_line(make_cmd), shell=use_shell) + + +def get_matlab_bin(config='config.ini'): + """ + Tries to find the MATLAB bin directory independent on host platform. + The operation of this function can be overridden by setting the MATLAB_BIN + variable within the configuration file specified. + + Parameters + ---------- + config: str + Relative path to configuration file + + Returns + ------- + matlab: str or None + Absolute path to matlab bin directory. + Returns None if Matlab path could not be determined + """ + host = platform.system() + cfg = get_config(host, config=config) + programs = { + 'Darwin' : r'/Applications', + 'Windows': r'C:/Program Files', + 'Linux' : r'/usr/local', + } + + if cfg.get('MATLAB_BIN', None): + matlab = cfg['MATLAB_BIN'] + else: + # Searches for Matlab bin if it's not set + matlab = [p for p in os.listdir(programs[host]) if p.startswith('MATLAB')] + + if not matlab: + return None + + for p in os.listdir(os.path.join(programs[host], *matlab)): + if p.startswith('R20'): + matlab.append(p) + + matlab.append('bin') + + return os.path.normpath(os.path.join(programs[host], *matlab)) diff --git a/messenger/mexa64/messenger.mex b/pymatbridge/messenger/mexa64/messenger.mex similarity index 100% rename from messenger/mexa64/messenger.mex rename to pymatbridge/messenger/mexa64/messenger.mex diff --git a/messenger/mexa64/messenger.mex.zmq4 b/pymatbridge/messenger/mexa64/messenger.mex.zmq4 similarity index 100% rename from messenger/mexa64/messenger.mex.zmq4 rename to pymatbridge/messenger/mexa64/messenger.mex.zmq4 diff --git a/messenger/mexa64/messenger.mexa64 b/pymatbridge/messenger/mexa64/messenger.mexa64 similarity index 100% rename from messenger/mexa64/messenger.mexa64 rename to pymatbridge/messenger/mexa64/messenger.mexa64 diff --git a/pymatbridge/messenger/mexa64/messenger.mexa64.zmq4 b/pymatbridge/messenger/mexa64/messenger.mexa64.zmq4 new file mode 100755 index 0000000..a01118c Binary files /dev/null and b/pymatbridge/messenger/mexa64/messenger.mexa64.zmq4 differ diff --git a/messenger/mexmaci64/messenger.mex b/pymatbridge/messenger/mexmaci64/messenger.mex similarity index 100% rename from messenger/mexmaci64/messenger.mex rename to pymatbridge/messenger/mexmaci64/messenger.mex diff --git a/messenger/mexmaci64/messenger.mex.zmq3 b/pymatbridge/messenger/mexmaci64/messenger.mex.zmq3 similarity index 100% rename from messenger/mexmaci64/messenger.mex.zmq3 rename to pymatbridge/messenger/mexmaci64/messenger.mex.zmq3 diff --git a/messenger/mexmaci64/messenger.mexmaci64 b/pymatbridge/messenger/mexmaci64/messenger.mexmaci64 similarity index 100% rename from messenger/mexmaci64/messenger.mexmaci64 rename to pymatbridge/messenger/mexmaci64/messenger.mexmaci64 diff --git a/messenger/mexmaci64/messenger.mexmaci64.zmq3 b/pymatbridge/messenger/mexmaci64/messenger.mexmaci64.zmq3 similarity index 100% rename from messenger/mexmaci64/messenger.mexmaci64.zmq3 rename to pymatbridge/messenger/mexmaci64/messenger.mexmaci64.zmq3 diff --git a/messenger/mexw64/messenger.mex b/pymatbridge/messenger/mexw64/messenger.mex similarity index 100% rename from messenger/mexw64/messenger.mex rename to pymatbridge/messenger/mexw64/messenger.mex diff --git a/messenger/mexw64/messenger.mexw64 b/pymatbridge/messenger/mexw64/messenger.mexw64 similarity index 100% rename from messenger/mexw64/messenger.mexw64 rename to pymatbridge/messenger/mexw64/messenger.mexw64 diff --git a/pymatbridge/messenger/settings.py b/pymatbridge/messenger/settings.py new file mode 100644 index 0000000..5597983 --- /dev/null +++ b/pymatbridge/messenger/settings.py @@ -0,0 +1,36 @@ +import os +import subprocess +import tarfile + +try: # Python 3 Compat + from urllib.request import urlretrieve + string = str +except ImportError: + from urllib import urlretrieve + import string + +__all__= ['get_matlab_env'] + + +def get_matlab_env(matlab='matlab'): + """ + Get the underlying environment variables set for a matlab installation. + + Parameters + ---------- + matlab: str + Path to the matlab binary executable. + If matlab is in the users $PATH, just pass 'matlab' + + Returns + ------- + environment: dict + Mapping of environment variable + """ + command = ' '.join([matlab, '-e']), + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + + envs = (line.decode('utf-8').strip() for line in process.stdout) + + return dict(string.split(env, '=', maxsplit=1) for env in envs) + diff --git a/messenger/src/messenger.c b/pymatbridge/messenger/src/messenger.c similarity index 100% rename from messenger/src/messenger.c rename to pymatbridge/messenger/src/messenger.c diff --git a/pymatbridge/messenger/tests/test_env.py b/pymatbridge/messenger/tests/test_env.py new file mode 100644 index 0000000..e957735 --- /dev/null +++ b/pymatbridge/messenger/tests/test_env.py @@ -0,0 +1,33 @@ +import numpy.testing as npt +import sys +import os +import pkg_resources + +from pymatbridge import messenger + +ROOT = __file__ +CONFIG = os.path.realpath(os.path.join(ROOT, '../../../../config.ini')) +print(CONFIG) +BIN = messenger.get_matlab_bin(config=CONFIG) + +def test_config(): + npt.assert_equal(os.path.isfile(CONFIG), True) + +@npt.decorators.skipif(BIN==None, 'No Matlab Installed') +def test_matlab_bin(): + npt.assert_equal(os.path.isdir(BIN), True) + + mexext = any(m for m in os.listdir(BIN) if m == 'mexext' or m == 'mexext.exe') + mex = any(m for m in os.listdir(BIN) if m == 'mex' or m == 'mex.exe') + + npt.assert_equal(mexext, True) + npt.assert_equal(mex, True) + +@npt.decorators.skipif(BIN==None, 'No Matlab Installed') +def test_matlab_env(): + matlab = os.path.join(messenger.get_matlab_bin(config=CONFIG), 'matlab') + env = messenger.get_matlab_env(matlab=matlab) + arch = env['ARCH'] + + npt.assert_equal(arch.endswith(messenger.get_messenger_dir()[-2:]), True) + diff --git a/pymatbridge/pymatbridge.py b/pymatbridge/pymatbridge.py index 31855fa..8a94f96 100644 --- a/pymatbridge/pymatbridge.py +++ b/pymatbridge/pymatbridge.py @@ -41,7 +41,6 @@ class spmatrix: pass - def encode_ndarray(obj): """Write a numpy array and its shape to base64 buffers""" shape = obj.shape @@ -403,6 +402,7 @@ def _bind_method(self, name, unconditionally=False): class Matlab(_Session): + def __init__(self, executable='matlab', socket_addr=None, id='python-matlab-bridge', log=False, maxtime=60, platform=None, startup_options=None): diff --git a/pymatbridge/version.py b/pymatbridge/version.py deleted file mode 100644 index 0b9ef31..0000000 --- a/pymatbridge/version.py +++ /dev/null @@ -1,90 +0,0 @@ -"""pymatbridge version/release information""" - -# Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" -_version_major = 0 -_version_minor = 5 -_version_micro = 0 #'' # use '' for first of series, number for 1 and above -_version_extra = 'dev' -#_version_extra = '' # Uncomment this for full releases - -# Construct full version string from these. -_ver = [_version_major, _version_minor] -if _version_micro: - _ver.append(_version_micro) -if _version_extra: - _ver.append(_version_extra) - -__version__ = '.'.join(map(str, _ver)) - -CLASSIFIERS = ["Development Status :: 3 - Alpha", - "Environment :: Console", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Topic :: Scientific/Engineering"] - -description = "pymatbridge is a set of python and matlab functions to allow these two systems to talk to each other" - -long_description = """ - -Pymatbridge -=========== - -A python interface to call out to Matlab(R). - - -License information -=================== - -Copyright (c) 2012 -- , Max Jaderberg, Ariel Rokem, Haoxing Zhao -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the Oxford University, Stanford University nor the names of -its contributors may be used to endorse or promote products derived from this -software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" - -NAME = "pymatbridge" -MAINTAINER = "Ariel Rokem" -MAINTAINER_EMAIL = "arokem@gmail.com" -DESCRIPTION = description -LONG_DESCRIPTION = long_description -URL = "https://github.com/arokem/python-matlab-bridge" -DOWNLOAD_URL = "https://github.com/arokem/python-matlab-bridge/archive/master.tar.gz" -LICENSE = "BSD" -AUTHOR = "https://github.com/arokem/python-matlab-bridge/contributors" -AUTHOR_EMAIL = "arokem@gmail.com" -PLATFORMS = "OS Independent" -MAJOR = _version_major -MINOR = _version_minor -MICRO = _version_micro -VERSION = __version__ -PACKAGES = ['pymatbridge'] -PACKAGE_DATA = {"pymatbridge": ["matlab/matlabserver.m", "matlab/messenger.*", - "matlab/usrprog/*", "matlab/util/*.m", - "matlab/util/json_v0.2.2/LICENSE", - "matlab/util/json_v0.2.2/README.md", - "matlab/util/json_v0.2.2/test/*", - "matlab/util/json_v0.2.2/json/*.m", - "matlab/util/json_v0.2.2/json/java/*", - "tests/*.py", "examples/*.ipynb"]} - -REQUIRES = ['pyzmq'] -#EXTRAS_REQUIRE = ['numpy', 'scipy', 'ipython'] - -BIN=['scripts/publish-notebook'] diff --git a/requirements.txt b/requirements.txt index ae3bcc7..a77d8b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ pyzmq -numpy>=1.7 +numpy diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4f550d4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[global] +verbose = 1 + +[build_ext] +inplace = 1 + +[bdist_wheel] +universal = 0 diff --git a/setup.py b/setup.py index ae9622a..e2b546c 100755 --- a/setup.py +++ b/setup.py @@ -1,54 +1,104 @@ -#!/usr/bin/env python -"""Setup file for python-matlab-bridge""" +# -*- coding: utf-8 -*- +""" +Pymatbridge: A python interface to call out to Matlab(R) +""" import os import sys -import shutil -import glob +import filecmp +import itertools +import platform +# if setuptools doesnt exist install it +try: + import pkg_resources +except ImportError: + import ez_setup + ez_setup.use_setuptools() + import pkg_resources -# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly -# update it when the contents of directories change. -if os.path.exists('MANIFEST'): - os.remove('MANIFEST') +from setuptools import setup, find_packages +from setuptools.command.test import test as TestCommand +from setuptools.command.build_ext import build_ext +from distutils import file_util +from pymatbridge.messenger import build_matlab, get_messenger_dir -from distutils.core import setup +from version import __version__, __build__, __release__ +# messenger = pkg_resources.resource_filename('pymatbridge.messenger', get_messenger_dir()) +# matlab = pkg_resources.resource_filename('pymatbridge', 'matlab') +# newfiles = filecmp.cmpfiles(messenger, matlab, os.listdir(messenger), shallow=False)[1:] -# Find the messenger binary file(s) and copy it to /matlab folder. -from messenger.make import get_messenger_dir -messenger_dir = get_messenger_dir() +# for binary in itertools.chain(*newfiles): +# cmd = (os.path.join(messenger, binary), os.path.join(matlab, binary)) +# print('Copying %s' % binary) +# file_util.copy_file(*cmd, update=True) -for f in glob.glob("./messenger/%s/messenger.*" % messenger_dir): - shutil.copy(f, "./pymatbridge/matlab") +def read(*files): + """ + Takes an arbitrary number of file names based from the package root + returns their contents concatenated with two newlines. + """ + return '\n\n'.join([open(f, 'rt').read() for f in files if os.path.isfile(f)]) -# Get version and release info, which is all stored in pymatbridge/version.py -ver_file = os.path.join('pymatbridge', 'version.py') -exec(open(ver_file).read()) +class NoseTestCommand(TestCommand): -opts = dict(name=NAME, - maintainer=MAINTAINER, - maintainer_email=MAINTAINER_EMAIL, - description=DESCRIPTION, - long_description=LONG_DESCRIPTION, - url=URL, - download_url=DOWNLOAD_URL, - license=LICENSE, - classifiers=CLASSIFIERS, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - platforms=PLATFORMS, - version=VERSION, - packages=PACKAGES, - package_data=PACKAGE_DATA, - requires=REQUIRES, - #extras_require=EXTRAS_REQUIRE, - scripts=BIN - ) + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + def run_tests(self): + import nose + args = 'nosetests -v --exe' + if sys.version_info[0:2] == (2, 7): + args += ' ' + args += '--with-cov --cover-package pymatbridge' + nose.run_exit(argv=args.split()) -# Now call the actual setup function -if __name__ == '__main__': - setup(**opts) +class CompileMEX(build_ext): + def run(self): + return build_matlab(messenger='pymatbridge/messenger/src/messenger.c') + +setup( + name="pymatbridge", + maintainer="Ariel Rokem", + maintainer_email="arokem@gmail.com", + description=__doc__, + long_description=read('README.md', 'LICENSE'), + tests_require=['ipython', 'nose', 'coverage', 'numpy', 'pyzmq'], + setup_requires=['wheel'], + cmdclass={ + 'test': NoseTestCommand, + 'messenger': CompileMEX, + }, + license=read('LICENSE'), + scripts='scripts/publish-notebook', + version=__release__, + packages=find_packages(exclude=['tests*']), + zip_safe=False, + requires=['pyzmq', 'numpy'], + keywords='matlab python packaging', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Topic :: Scientific/Engineering', + 'Topic :: Software Development :: Build Tools', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Other Scripting Engines', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: OS Independent', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Science/Research', + ], +) diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..518b2cf --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,3 @@ +nose +coverage +ipython diff --git a/version.py b/version.py new file mode 100644 index 0000000..fcd9f36 --- /dev/null +++ b/version.py @@ -0,0 +1,3 @@ +# Set Version Info +exec(open('pymatbridge/__version__.py').read()) +