Skip to content
Snippets Groups Projects
dct_compile_mex_functions.py 9.63 KiB
#!/usr/bin/python

import fnmatch
import os
import sys
import subprocess
import string
import re

from dct_io_xml import DCTConf, DCTOutput

import dct_utils_platform

class MexBuilder(object):

    file_extensions = [ '*.c', '*.cxx', '*.cpp' ]

    @staticmethod
    def getInstanceFromArgs(args, dct_dir = ""):
        if dct_dir is "":
            dct_dir = os.path.abspath(os.path.dirname(args[0]))
            dct_dir = os.path.join(dct_dir, os.path.pardir)
            dct_dir = os.path.abspath(dct_dir)
        confPath = os.path.join(dct_dir, "conf.xml")
        confPath = os.path.abspath(confPath)

        args = args[1:]

        matlab_path = ""
        skip_next = False
        force_compile = False
        fresh_mexopts = False
        verbose = 1
        mfiles_to_consider = [ ]
        unclassified_args = [ ]
        debug = False

        for index in range(len(args)):
            if skip_next is False:
                if args[index] == "-force-compile":
                    force_compile = True
                elif args[index] == "-fresh-mexopts":
                    fresh_mexopts = True
                elif args[index] in ("--verbose", "-v"):
                    verbose = 2
                elif args[index] == "--debug":
                    debug = True
                elif args[index] == "-c":
                    skip_next = True
                    if len(sys.argv) > (index+1):
                        mfiles_to_consider.append(args[index+1])
                    else:
                        raise ValueError("Not enough arguments to '-c' option")
                elif args[index] == "-mp":
                    skip_next = True
                    if len(args) > (index-1):
                        matlab_path = args[index+1]
                    else:
                        raise ValueError("Not enough arguments for '-mp' option")
                elif args[index] in ("--help", "-h"):
                    raise ValueError("")
                else:
                    unclassified_args.append(args[index])
                    print(args[index])
            else:
                skip_next = False

        return MexBuilder(matlab_path = matlab_path, dct_dir = dct_dir, \
                          extraArgs = unclassified_args, \
                          force_compile = force_compile, verbose = verbose, \
                          mfiles_to_consider = mfiles_to_consider, \
                          fresh_mexopts = fresh_mexopts, debug = debug)


    def __init__(self, matlab_path = "", dct_dir = None, outdir = None, \
                 extraArgs = [], force_compile = False, verbose = 1, \
                 mfiles_to_consider = [], fresh_mexopts = False, debug = False):
        """
         - force_compile: if true, compiles even if the file is recent
         - verbose: generates a lot of output
         - mfiles_to_consider: list mex files to compile (if empty, will compile all)
        """
        if dct_dir is None:
            dct_dir = os.getcwd()
        if outdir is None:
            outdir = os.path.join(dct_dir, 'bin', 'mex')
        self.dct_dir = dct_dir
        self.outdir = outdir

        self.c_files = { }
        self.mfiles_to_consider = mfiles_to_consider

        self.force_compile = force_compile
        self.fresh_mexopts = fresh_mexopts
        self.debug = debug

        self.out = DCTOutput(verboseLevel = verbose)

        confPath = os.path.join(dct_dir, "conf.xml")
        confPath = os.path.abspath(confPath)
        self.conf = DCTConf(confPath)

        if matlab_path == "":
            matlab_path = self.conf.getMatlabPath()
        self.matlab_dir = matlab_path

        try:
            self.mex_info = self.conf.getMexFiles()
        except:
            DCTOutput.printWarning("Couldn't get mex files info. Using plain config.")
            self.mex_info = None

    def printHelp(self):
        launchname = os.path.basename(__file__)
        print("\"%s\" builds your mex files in the DCT dir: \"%s\"" %
              (launchname, self.dct_dir) )
        print(" Options:")
        print("  -h | --help : to show this help")
        print("  -v | --verbose : to show more detailed output")
        print("  --debug : to compile with debug symbols and no optimization")
        print("  -c <filename> : to compile only a specific file (wildcards also apply)")
        print("  -mp <matlab_dir> : to specify matlab's directory")
        print("  -force-compile : to force the compilation even if not needed")
        print("  -fresh-mexopts : to force the resetting of mexopts file")

    def findMexs(self):
        """
        def findMexs():

        Finds the mex files (well, the C sources) and saves the files into the object
        """
        self.out.printSubJob("Finding mex files, from: '%s'" % self.dct_dir)
        for root, dir_name, file_names in os.walk(self.dct_dir):
            for file_extension in self.file_extensions:
                for file_name in file_names:
                    if fnmatch.fnmatch(file_name, file_extension):
                        filepath = os.path.join(root, file_name)
                        self.c_files[file_name] = filepath
        self.out.printSubJob("These files were found:")
        for file_name in self.c_files:
            self.out.printSubSubJob('File', file_name + ' ( ' + self.c_files[file_name] + ' )')
        print('')

    def _buildMakeCmd(self, cmd, includes, lib_paths, libs, defines):
        if self.out.verboseLevel > 1:
            cmd.append('-v')
        if self.debug is True:
            cmd.append('-g')
            cmd.append("-DDEBUG")
        for include in includes:
            cmd.append("-I" + include)
        for lib_path in lib_paths:
            cmd.append("-L" + lib_path)
        for lib in libs:
            cmd.append("-l" + lib)
        for define in defines:
            cmd.append("-D" + define)
        self.out.printSubSubJob('File', cmd[1] + ' (cmd: ' + string.join(cmd, ' ') + ' )')
        return subprocess.call(cmd)

    def _compileFile(self, file_path):
        """
        [Internal] def _compileFile(file_path):

        Tries to compile the given file_path.
        """
        compiler_path = os.path.join(self.matlab_dir, "bin", "mex") 
        cmd = [ compiler_path, file_path, '-outdir', self.outdir ]

        includes = [ os.path.join(self.dct_dir, 'zUtil_Cxx', 'include') ]
        lib_paths = []
        libs = []
        defines = []

        if (self.mex_info is not None) \
                and (self.mex_info.isExcluded(file_path) is False):

            props = self.mex_info.getFileProperties(file_path)
            if props is not None:
                # No info at all into the xml file
                includes = includes + props.get("includes")
                lib_paths = lib_paths + props.get("lib_paths")
                libs = libs + props.get("libs")
                defines = defines + props.get("defines")

        return self._buildMakeCmd(cmd, includes, lib_paths, libs, defines)

    def compileFuncs(self):
        """
        def compileFuncs():

        Compiles the mex files into the list.
        """

        if self.fresh_mexopts is True:
            self.out.printSubJob("Resetting mexopts file")
            dct_utils = dct_utils_platform.UtilsFactory.getMachineDep()
            dct_utils.resetMatlabMexopts(self.conf.getMatlabVersion(), \
                                         self.conf.getMatlabPath() )

        listOfErrors = [];

        if not os.path.exists(self.outdir):
            self.out.printSubJob("Creating mex directory")
            os.makedirs(self.outdir)
        self.out.printSubJob("Compiling mex files:")

        if (len(self.mfiles_to_consider) is 0) \
                or ( (len(self.mfiles_to_consider) is 1) \
                     and (self.mfiles_to_consider[0] == 'all') ):
            # No preferences, so let's compile everything
            files_to_compile = self.c_files
        else:
            # Specific files
            files_to_compile = self.mfiles_to_consider

        # Now let's compile
        for file_name in files_to_compile:

            # Check if it is an actual file or a regular expression
            if file_name in self.c_files:
                match_list = [ file_name ]
            else:
                match_list = []
                for cfile in self.c_files:
                    if re.search(file_name, cfile) is not None:
                        match_list.append(cfile)
                if len(match_list) is 0:
                    errorMsg = "No file corresponds to: %s" % file_name
                    DCTOutput.printError(errorMsg)
                    listOfErrors.append(errorMsg);

            # Let's compile each match
            for cfile in match_list:
                file_path = self.c_files[cfile]
                if (self.mex_info is None) \
                        or ( (self.mex_info is not None) \
                             and (self.mex_info.isExcluded(file_path) is False) ):
                    ret_val = self._compileFile(file_path)
                    if ret_val is not 0:
                        listOfErrors.append("Error compiling MEX: %s (Return Value: %d)" % (file_name, ret_val))
        print('')
        if len(listOfErrors) is not 0:
            print("Errors happened during compilation:")
            for errorMsg in listOfErrors:
                DCTOutput.printError('- %s' % errorMsg)

if __name__=="__main__":
    try:
        mex_builder = MexBuilder.getInstanceFromArgs(sys.argv)
        mex_builder.findMexs()
        mex_builder.compileFuncs()
    except ValueError as ex:
        if (len(ex.args) is not 0) and (ex.args[0] is not ""):
            DCTOutput.printError(ex.args)
        MexBuilder("").printHelp();
    except BaseException as ex:
        DCTOutput.printError(ex.args)
        exit(1);