442 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			442 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
"""
 | 
						|
                     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
 | 
						|
import platform
 | 
						|
 | 
						|
 | 
						|
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
 | 
						|
    is_darwin = options.target_platform == "Darwin"
 | 
						|
    gen_deps = options.generate_dependency_file
 | 
						|
    darwin_extras = ["-D__APPLE__"] if is_darwin else []
 | 
						|
    deps_args = ["-MMD", "-MF", temp_dep_file_path] if gen_deps else []
 | 
						|
    command = ([
 | 
						|
            options.swig_executable,
 | 
						|
            "-c++",
 | 
						|
            "-shadow",
 | 
						|
            "-python",
 | 
						|
            "-threads",
 | 
						|
            "-I" + os.path.normpath(os.path.join(options.src_root, "include")),
 | 
						|
            "-I" + os.path.curdir,
 | 
						|
            "-D__STDC_LIMIT_MACROS",
 | 
						|
            "-D__STDC_CONSTANT_MACROS"
 | 
						|
        ]
 | 
						|
        + darwin_extras
 | 
						|
        + deps_args
 | 
						|
        + [
 | 
						|
            "-outdir", config_build_dir,
 | 
						|
            "-o", settings.output_file,
 | 
						|
            settings.input_file
 | 
						|
        ]
 | 
						|
    )
 | 
						|
    logging.info("running swig with: %r", command)
 | 
						|
 | 
						|
    # Execute swig
 | 
						|
    process = subprocess.Popen(
 | 
						|
        command,
 | 
						|
        stdout=subprocess.PIPE,
 | 
						|
        stderr=subprocess.PIPE,
 | 
						|
    )
 | 
						|
    # 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 = [sys.executable] + script_and_args
 | 
						|
    process = subprocess.Popen(command)
 | 
						|
    script_stdout, script_stderr = process.communicate()
 | 
						|
    return_code = process.returncode
 | 
						|
    if return_code != 0:
 | 
						|
        logging.error("failed to run %r: %r", command, script_stderr)
 | 
						|
        sys.exit(return_code)
 | 
						|
    else:
 | 
						|
        logging.info("ran script %r'", command)
 | 
						|
        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)
 | 
						|
 | 
						|
    run_python_script([script_path, config_build_dir])
 | 
						|
 | 
						|
 | 
						|
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)
 |