forked from OSchip/llvm-project
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)
|