diff options
Diffstat (limited to 'scripts/Python/prepare_binding_Python.py')
| -rw-r--r-- | scripts/Python/prepare_binding_Python.py | 435 | 
1 files changed, 435 insertions, 0 deletions
| diff --git a/scripts/Python/prepare_binding_Python.py b/scripts/Python/prepare_binding_Python.py new file mode 100644 index 000000000000..1996841baf18 --- /dev/null +++ b/scripts/Python/prepare_binding_Python.py @@ -0,0 +1,435 @@ +""" +                     The LLVM Compiler Infrastructure + +This file is distributed under the University of Illinois Open Source +License. See LICENSE.TXT for details. + +Python binding preparation script. +""" + +# Python modules: +from __future__ import print_function + +import logging +import os +import re +import shutil +import subprocess +import sys + + +class SwigSettings(object): +    """Provides a single object to represent swig files and settings.""" +    def __init__(self): +        self.extensions_file = None +        self.header_files = None +        self.input_file = None +        self.interface_files = None +        self.output_file = None +        self.safecast_file = None +        self.typemaps_file = None +        self.wrapper_file = None + +    @classmethod +    def _any_files_newer(cls, files, check_mtime): +        """Returns if any of the given files has a newer modified time. + +        @param cls the class +        @param files a list of zero or more file paths to check +        @param check_mtime the modification time to use as a reference. + +        @return True if any file's modified time is newer than check_mtime. +        """ +        for path in files: +            path_mtime = os.path.getmtime(path) +            if path_mtime > check_mtime: +                # This path was modified more recently than the +                # check_mtime. +                return True +        # If we made it here, nothing was newer than the check_mtime +        return False + +    @classmethod +    def _file_newer(cls, path, check_mtime): +        """Tests how recently a file has been modified. + +        @param cls the class +        @param path a file path to check +        @param check_mtime the modification time to use as a reference. + +        @return True if the file's modified time is newer than check_mtime. +        """ +        path_mtime = os.path.getmtime(path) +        return path_mtime > check_mtime + +    def output_out_of_date(self): +        """Returns whether the output file is out of date. + +        Compares output file time to all the input files. + +        @return True if any of the input files are newer than +        the output file, or if the output file doesn't exist; +        False otherwise. +        """ +        if not os.path.exists(self.output_file): +            logging.info("will generate, missing binding output file") +            return True +        output_mtime = os.path.getmtime(self.output_file) +        if self._any_files_newer(self.header_files, output_mtime): +            logging.info("will generate, header files newer") +            return True +        if self._any_files_newer(self.interface_files, output_mtime): +            logging.info("will generate, interface files newer") +            return True +        if self._file_newer(self.input_file, output_mtime): +            logging.info("will generate, swig input file newer") +            return True +        if self._file_newer(self.extensions_file, output_mtime): +            logging.info("will generate, swig extensions file newer") +            return True +        if self._file_newer(self.wrapper_file, output_mtime): +            logging.info("will generate, swig wrapper file newer") +            return True +        if self._file_newer(self.typemaps_file, output_mtime): +            logging.info("will generate, swig typemaps file newer") +            return True +        if self._file_newer(self.safecast_file, output_mtime): +            logging.info("will generate, swig safecast file newer") +            return True + +        # If we made it here, nothing is newer than the output file. +        # Thus, the output file is not out of date. +        return False + + +def get_header_files(options): +    """Returns a list of paths to C++ header files for the LLDB API. + +    These are the files that define the C++ API that will be wrapped by Python. + +    @param options the dictionary of options parsed from the command line. + +    @return a list of full paths to the include files used to define the public +    LLDB C++ API. +    """ + +    header_file_paths = [] +    header_base_dir = os.path.join(options.src_root, "include", "lldb") + +    # Specify the include files in include/lldb that are not easy to +    # grab programatically. +    for header in [ +            "lldb-defines.h", +            "lldb-enumerations.h", +            "lldb-forward.h", +            "lldb-types.h"]: +        header_file_paths.append(os.path.normcase( +            os.path.join(header_base_dir, header))) + +    # Include the main LLDB.h file. +    api_dir = os.path.join(header_base_dir, "API") +    header_file_paths.append(os.path.normcase( +        os.path.join(api_dir, "LLDB.h"))) + +    filename_regex = re.compile(r"^SB.+\.h$") + +    # Include all the SB*.h files in the API dir. +    for filename in os.listdir(api_dir): +        if filename_regex.match(filename): +            header_file_paths.append( +                os.path.normcase(os.path.join(api_dir, filename))) + +    logging.debug("found public API header file paths: %s", header_file_paths) +    return header_file_paths + + +def get_interface_files(options): +    """Returns a list of interface files used as input to swig. + +    @param options the options dictionary parsed from the command line args. + +    @return a list of full paths to the interface (.i) files used to describe +    the public API language binding. +    """ +    interface_file_paths = [] +    interface_dir = os.path.join(options.src_root, "scripts", "interface") + +    for filepath in [f for f in os.listdir(interface_dir) +                     if os.path.splitext(f)[1] == ".i"]: +        interface_file_paths.append( +            os.path.normcase(os.path.join(interface_dir, filepath))) + +    logging.debug("found swig interface files: %s", interface_file_paths) +    return interface_file_paths + + +def remove_ignore_enoent(filename): +    """Removes given file, ignoring error if it doesn't exist. + +    @param filename the path of the file to remove. +    """ +    try: +        os.remove(filename) +    except OSError as error: +        import errno +        if error.errno != errno.ENOENT: +            raise + + +def do_swig_rebuild(options, dependency_file, config_build_dir, settings): +    """Generates Python bindings file from swig. + +    This method will do a sys.exit() if something fails.  If it returns to +    the caller, it succeeded. + +    @param options the parsed command line options structure. +    @param dependency_file path to the bindings dependency file +    to be generated; otherwise, None if a dependency file is not +    to be generated. +    @param config_build_dir used as the output directory used by swig +    @param settings the SwigSettings that specify a number of aspects used +    to configure building the Python binding with swig (mostly paths) +    """ +    if options.generate_dependency_file: +        temp_dep_file_path = dependency_file + ".tmp" + +    # Build the SWIG args list +    command = [ +        options.swig_executable, +        "-c++", +        "-shadow", +        "-python", +        "-threads", +        "-I\"%s\"" % os.path.normcase( +            os.path.join(options.src_root, "include")), +        "-I\"%s\"" % os.path.normcase("./."), +        "-D__STDC_LIMIT_MACROS", +        "-D__STDC_CONSTANT_MACROS"] +    if options.generate_dependency_file: +        command.append("-MMD -MF \"%s\"" % temp_dep_file_path) +    command.extend([ +        "-outdir", "\"%s\"" % config_build_dir, +        "-o", "\"%s\"" % settings.output_file, +        "\"%s\"" % settings.input_file +        ]) +    logging.info("running swig with: %s", command) + +    # Execute swig +    process = subprocess.Popen( +        ' '.join(command), +        stdout=subprocess.PIPE, +        stderr=subprocess.PIPE, +        shell=True) +    # Wait for SWIG process to terminate +    swig_stdout, swig_stderr = process.communicate() +    return_code = process.returncode +    if return_code != 0: +        logging.error( +            "swig failed with error code %d: stdout=%s, stderr=%s", +            return_code, +            swig_stdout, +            swig_stderr) +        logging.error( +            "command line:\n%s", ' '.join(command)) +        sys.exit(return_code) + +    logging.info("swig generation succeeded") +    if swig_stdout is not None and len(swig_stdout) > 0: +        logging.info("swig output: %s", swig_stdout) + +    # Move the depedency file we just generated to the proper location. +    if options.generate_dependency_file: +        if os.path.exists(temp_dep_file_path): +            shutil.move(temp_dep_file_path, dependency_file) +        else: +            logging.error( +                "failed to generate Python binding depedency file '%s'", +                temp_dep_file_path) +            if os.path.exists(dependency_file): +                # Delete the old one. +                os.remove(dependency_file) +            sys.exit(-10) + + +def run_python_script(script_and_args): +    """Runs a python script, logging appropriately. + +    If the command returns anything non-zero, it is registered as +    an error and exits the program. + +    @param script_and_args the python script to execute, along with +    the command line arguments to pass to it. +    """ +    command_line = "%s %s" % (sys.executable, script_and_args) +    process = subprocess.Popen(command_line, shell=True) +    script_stdout, script_stderr = process.communicate() +    return_code = process.returncode +    if return_code != 0: +        logging.error("failed to run '%s': %s", command_line, script_stderr) +        sys.exit(return_code) +    else: +        logging.info("ran script '%s'", command_line) +        if script_stdout is not None: +            logging.info("output: %s", script_stdout) + + +def do_modify_python_lldb(options, config_build_dir): +    """Executes the modify-python-lldb.py script. + +    @param options the parsed command line arguments +    @param config_build_dir the directory where the Python output was created. +    """ +    script_path = os.path.normcase( +        os.path.join( +            options.src_root, +            "scripts", +            "Python", +            "modify-python-lldb.py")) + +    if not os.path.exists(script_path): +        logging.error("failed to find python script: '%s'", script_path) +        sys.exit(-11) + +    script_invocation = "%s %s" % (script_path, config_build_dir) +    run_python_script(script_invocation) + + +def get_python_module_path(options): +    """Returns the location where the lldb Python module should be placed. + +    @param options dictionary of options parsed from the command line. + +    @return the directory where the lldb module should be placed. +    """ +    if options.framework: +        # Caller wants to use the OS X framework packaging. + +        # We are packaging in an OS X-style framework bundle. The +        # module dir will be within the +        # LLDB.framework/Resources/Python subdirectory. +        return os.path.join( +            options.target_dir, +            "LLDB.framework", +            "Resources", +            "Python", +            "lldb") +    else: +        from distutils.sysconfig import get_python_lib + +        if options.prefix is not None: +            module_path = get_python_lib(True, False, options.prefix) +        else: +            module_path = get_python_lib(True, False) +        return os.path.normcase( +            os.path.join(module_path, "lldb")) + + +def main(options): +    """Pepares the Python language binding to LLDB. + +    @param options the parsed command line argument dictionary +    """ +    # Setup generated dependency file options. +    if options.generate_dependency_file: +        dependency_file = os.path.normcase(os.path.join( +            options.target_dir, "LLDBWrapPython.cpp.d")) +    else: +        dependency_file = None + +    # Keep track of all the swig-related settings. +    settings = SwigSettings() + +    # Determine the final binding file path. +    settings.output_file = os.path.normcase( +        os.path.join(options.target_dir, "LLDBWrapPython.cpp")) + +    # Touch the output file (but don't really generate it) if python +    # is disabled. +    disable_python = os.getenv("LLDB_DISABLE_PYTHON", None) +    if disable_python is not None and disable_python == "1": +        remove_ignore_enoent(settings.output_file) +        # Touch the file. +        open(settings.output_file, 'w').close() +        logging.info( +            "Created empty python binding file due to LLDB_DISABLE_PYTHON " +            "being set") +        return + +    # We also check the GCC_PREPROCESSOR_DEFINITIONS to see if it +    # contains LLDB_DISABLE_PYTHON.  If so, we skip generating +    # the binding. +    gcc_preprocessor_defs = os.getenv("GCC_PREPROCESSOR_DEFINITIONS", None) +    if gcc_preprocessor_defs is not None: +        if re.search(r"LLDB_DISABLE_PYTHON", gcc_preprocessor_defs): +            remove_ignore_enoent(settings.output_file) +            # Touch the file +            open(settings.output_file, 'w').close() +            logging.info( +                "Created empty python binding file due to " +                "finding LLDB_DISABLE_PYTHON in GCC_PREPROCESSOR_DEFINITIONS") +            return + +    # Setup paths used during swig invocation. +    settings.input_file = os.path.normcase( +        os.path.join(options.src_root, "scripts", "lldb.swig")) +    scripts_python_dir = os.path.dirname(os.path.realpath(__file__)) +    settings.extensions_file = os.path.normcase( +        os.path.join(scripts_python_dir, "python-extensions.swig")) +    settings.wrapper_file = os.path.normcase( +        os.path.join(scripts_python_dir, "python-wrapper.swig")) +    settings.typemaps_file = os.path.normcase( +        os.path.join(scripts_python_dir, "python-typemaps.swig")) +    settings.safecast_file = os.path.normcase( +        os.path.join(scripts_python_dir, "python-swigsafecast.swig")) + +    settings.header_files = get_header_files(options) +    settings.interface_files = get_interface_files(options) + +    generate_output = settings.output_out_of_date() + +    # Determine where to put the module. +    python_module_path = get_python_module_path(options) +    logging.info("python module path: %s", python_module_path) + +    # Handle the configuration build dir. +    if options.config_build_dir is not None: +        config_build_dir = options.config_build_dir +    else: +        config_build_dir = python_module_path + +    # Allow missing/non-link _lldb.so to force regeneration. +    if not generate_output: +        # Ensure the _lldb.so file exists. +        so_path = os.path.join(python_module_path, "_lldb.so") +        if not os.path.exists(so_path) or not os.path.islink(so_path): +            logging.info("_lldb.so doesn't exist or isn't a symlink") +            generate_output = True + +    # Allow missing __init__.py to force regeneration. +    if not generate_output: +        # Ensure the __init__.py for the lldb module can be found. +        init_path = os.path.join(python_module_path, "__init__.py") +        if not os.path.exists(init_path): +            logging.info("__init__.py doesn't exist") +            generate_output = True + +    if not generate_output: +        logging.info( +            "Skipping Python binding generation: everything is up to date") +        return + +    # Generate the Python binding with swig. +    logging.info("Python binding is out of date, regenerating") +    do_swig_rebuild(options, dependency_file, config_build_dir, settings) +    if options.generate_dependency_file: +        return + +    # Post process the swig-generated file. +    do_modify_python_lldb(options, config_build_dir) + + +# This script can be called by another Python script by calling the main() +# function directly +if __name__ == "__main__": +    print("Script cannot be called directly.") +    sys.exit(-1) | 
