| #!/usr/bin/env python |
| |
| """ |
| SWIG generation client. Supports both local and remote generation of SWIG |
| bindings for multiple languages. |
| """ |
| |
| # Future imports |
| from __future__ import absolute_import |
| from __future__ import print_function |
| |
| # Python modules |
| import argparse |
| import io |
| import logging |
| import os |
| import socket |
| import struct |
| import sys |
| |
| # LLDB modules |
| import use_lldb_suite |
| from lldbsuite.support import fs |
| from lldbsuite.support import sockutil |
| |
| # package imports |
| from . import local |
| from . import remote |
| |
| default_ip = "127.0.0.1" |
| default_port = 8537 |
| |
| |
| def add_subparser_args(parser): |
| """Returns options processed from the provided command line. |
| |
| @param args the command line to process. |
| """ |
| |
| # A custom action used by the --local command line option. It can be |
| # used with either 0 or 1 argument. If used with 0 arguments, it |
| # searches for a copy of swig located on the physical machine. If |
| # used with 1 argument, the argument is the path to a swig executable. |
| class FindLocalSwigAction(argparse.Action): |
| |
| def __init__(self, option_strings, dest, **kwargs): |
| super(FindLocalSwigAction, self).__init__( |
| option_strings, dest, nargs='?', **kwargs) |
| |
| def __call__(self, parser, namespace, values, option_string=None): |
| swig_exe = None |
| if values is None: |
| swig_exe = fs.find_executable('swig') |
| else: |
| swig_exe = values |
| setattr(namespace, self.dest, os.path.normpath(swig_exe)) |
| |
| # A custom action used by the --remote command line option. It can be |
| # used with either 0 or 1 arguments. If used with 0 arguments it chooses |
| # a default connection string. If used with one argument it is a string |
| # of the form `ip_address[:port]`. If the port is unspecified, the |
| # default port is used. |
| class RemoteIpAction(argparse.Action): |
| |
| def __init__(self, option_strings, dest, **kwargs): |
| super(RemoteIpAction, self).__init__( |
| option_strings, dest, nargs='?', **kwargs) |
| |
| def __call__(self, parser, namespace, values, option_string=None): |
| ip_port = None |
| if values is None: |
| ip_port = (default_ip, default_port) |
| else: |
| result = values.split(':') |
| if len(result) == 1: |
| ip_port = (result[0], default_port) |
| elif len(result) == 2: |
| ip_port = (result[0], int(result[1])) |
| else: |
| raise ValueError("Invalid connection string") |
| setattr(namespace, self.dest, ip_port) |
| |
| parser.add_argument( |
| "--local", |
| action=FindLocalSwigAction, |
| dest="swig_executable", |
| help=( |
| "Run the copy of swig at the specified location, or search PATH" |
| "if the location is omitted")) |
| |
| parser.add_argument( |
| "--remote", |
| action=RemoteIpAction, |
| help=( |
| "Use the given connection string to connect to a remote " |
| "generation service")) |
| |
| parser.add_argument( |
| "--src-root", |
| required=True, |
| help="The root folder of the LLDB source tree.") |
| |
| parser.add_argument( |
| "--target-dir", |
| default=os.getcwd(), |
| help=( |
| "Specifies the build dir where the language binding " |
| "should be placed")) |
| |
| parser.add_argument( |
| "--language", |
| dest="languages", |
| action="append", |
| help="Specifies the language to generate bindings for") |
| |
| |
| def finalize_subparser_options(options): |
| if options.languages is None: |
| options.languages = ['python'] |
| |
| if options.remote is None and options.swig_executable is None: |
| logging.error("Must specify either --local or --remote") |
| sys.exit(-3) |
| |
| return options |
| |
| |
| def establish_remote_connection(ip_port): |
| logging.debug("Creating socket...") |
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| logging.info("Connecting to server {} on port {}" |
| .format(ip_port[0], ip_port[1])) |
| s.connect(ip_port) |
| logging.info("Connection established...") |
| return s |
| |
| |
| def transmit_request(connection, packed_input): |
| logging.info("Sending {} bytes of compressed data." |
| .format(len(packed_input))) |
| connection.sendall(struct.pack("!I", len(packed_input))) |
| connection.sendall(packed_input) |
| logging.info("Awaiting response.") |
| response_len = struct.unpack("!I", sockutil.recvall(connection, 4))[0] |
| logging.debug("Expecting {} byte response".format(response_len)) |
| response = sockutil.recvall(connection, response_len) |
| return response |
| |
| |
| def handle_response(options, connection, response): |
| logging.debug("Received {} byte response.".format(len(response))) |
| logging.debug("Creating output directory {}" |
| .format(options.target_dir)) |
| os.makedirs(options.target_dir, exist_ok=True) |
| |
| logging.info("Unpacking response archive into {}" |
| .format(options.target_dir)) |
| local.unpack_archive(options.target_dir, response) |
| response_file_path = os.path.normpath( |
| os.path.join(options.target_dir, "swig_output.json")) |
| if not os.path.isfile(response_file_path): |
| logging.error("Response file '{}' does not exist." |
| .format(response_file_path)) |
| return |
| try: |
| response = remote.deserialize_response_status( |
| io.open(response_file_path)) |
| if response[0] != 0: |
| logging.error("An error occurred during generation. Status={}" |
| .format(response[0])) |
| logging.error(response[1]) |
| else: |
| logging.info("SWIG generation successful.") |
| if len(response[1]) > 0: |
| logging.info(response[1]) |
| finally: |
| os.unlink(response_file_path) |
| |
| |
| def run(options): |
| if options.remote is None: |
| logging.info("swig bot client using local swig installation at '{}'" |
| .format(options.swig_executable)) |
| if not os.path.isfile(options.swig_executable): |
| logging.error("Swig executable '{}' does not exist." |
| .format(options.swig_executable)) |
| config = local.LocalConfig() |
| config.languages = options.languages |
| config.src_root = options.src_root |
| config.target_dir = options.target_dir |
| config.swig_executable = options.swig_executable |
| local.generate(config) |
| else: |
| logging.info("swig bot client using remote generation with server '{}'" |
| .format(options.remote)) |
| connection = None |
| try: |
| config = remote.generate_config(options.languages) |
| logging.debug("Generated config json {}".format(config)) |
| inputs = [("include/lldb", ".h"), |
| ("include/lldb/API", ".h"), |
| ("scripts", ".swig"), |
| ("scripts/Python", ".swig"), |
| ("scripts/interface", ".i")] |
| zip_data = io.BytesIO() |
| packed_input = local.pack_archive( |
| zip_data, options.src_root, inputs) |
| logging.info("(null) -> config.json") |
| packed_input.writestr("config.json", config) |
| packed_input.close() |
| connection = establish_remote_connection(options.remote) |
| response = transmit_request(connection, zip_data.getvalue()) |
| handle_response(options, connection, response) |
| finally: |
| if connection is not None: |
| connection.close() |