diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 | 
| commit | 9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc (patch) | |
| tree | dd2a1ddf0476664c2b823409c36cbccd52662ca7 /scripts/swig_bot_lib | |
| parent | 3bd2e91faeb9eeec1aae82c64a3253afff551cfd (diff) | |
Notes
Diffstat (limited to 'scripts/swig_bot_lib')
| -rw-r--r-- | scripts/swig_bot_lib/__init__.py | 0 | ||||
| -rw-r--r-- | scripts/swig_bot_lib/client.py | 205 | ||||
| -rw-r--r-- | scripts/swig_bot_lib/local.py | 131 | ||||
| -rw-r--r-- | scripts/swig_bot_lib/remote.py | 39 | ||||
| -rw-r--r-- | scripts/swig_bot_lib/server.py | 138 | 
5 files changed, 513 insertions, 0 deletions
| diff --git a/scripts/swig_bot_lib/__init__.py b/scripts/swig_bot_lib/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/scripts/swig_bot_lib/__init__.py diff --git a/scripts/swig_bot_lib/client.py b/scripts/swig_bot_lib/client.py new file mode 100644 index 000000000000..9bf55b42b666 --- /dev/null +++ b/scripts/swig_bot_lib/client.py @@ -0,0 +1,205 @@ +#!/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()
\ No newline at end of file diff --git a/scripts/swig_bot_lib/local.py b/scripts/swig_bot_lib/local.py new file mode 100644 index 000000000000..7cca0b3cabbb --- /dev/null +++ b/scripts/swig_bot_lib/local.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +""" +Shared functionality used by `client` and `server` when generating or preparing +to generate SWIG on the local machine. +""" + +# Future imports +from __future__ import absolute_import +from __future__ import print_function + +# Python modules +import argparse +import imp +import io +import logging +import os +import subprocess +import sys +import tempfile +import zipfile + +# LLDB modules +import use_lldb_suite + +# Package imports +from lldbsuite.support import fs + +class LocalConfig(object): +    src_root = None +    target_dir = None +    languages = None +    swig_executable = None + +def pack_archive(bytes_io, src_root, filters): +    logging.info("Creating input file package...") +    zip_file = None +    try: +        # It's possible that a custom-built interpreter will not have the +        # standard zlib module.  If so, we can only store, not compress.  By +        # try to compress since we usually have a standard Python distribution. +        zip_file = zipfile.ZipFile(bytes_io, mode='w', +                                   compression=zipfile.ZIP_DEFLATED) +    except RuntimeError: +        zip_file = zipfile.ZipFile(bytes_io, mode='w', +                                   compression=zipfile.ZIP_STORED) +    archive_entries = [] +    if filters is not None: +        def filter_func(t): +            subfolder = t[0] +            ext = t[1] +            full_path = os.path.normpath(os.path.join(src_root, subfolder)) +            candidates = [os.path.normpath(os.path.join(full_path, f)) +                          for f in os.listdir(full_path)] +            actual = filter( +                lambda f : os.path.isfile(f) and os.path.splitext(f)[1] == ext, +                candidates) +            return (subfolder, map(lambda f : os.path.basename(f), actual)) +        archive_entries = map(filter_func, filters) +    else: +        for (root, dirs, files) in os.walk(src_root): +            logging.debug("Adding files {} from directory {} to output package" +                          .format(files, root)) +            if len(files) > 0: +                rel_root = os.path.relpath(root, src_root) +                archive_entries.append((rel_root, files)) + +    archive_entries = list(archive_entries) +    for entry in archive_entries: +        subfolder = entry[0] +        files = list(entry[1]) +        for file in files: +            rel_path = os.path.normpath(os.path.join(subfolder, file)) +            full_path = os.path.join(src_root, rel_path) +            logging.info("{} -> {}".format(full_path, rel_path)) +            zip_file.write(full_path, rel_path) + +    return zip_file + +def unpack_archive(folder, archive_bytes): +    zip_data = io.BytesIO(archive_bytes) +    logging.debug("Opening zip archive...") +    zip_file = zipfile.ZipFile(zip_data, mode='r') +    zip_file.extractall(folder) +    zip_file.close() + +def generate(options): +    include_folder = os.path.join(options.src_root, "include") +    in_file = os.path.join(options.src_root, "scripts", "lldb.swig") +    include_folder = os.path.normcase(include_folder) + +    for lang in options.languages: +        lang = lang.lower() +        out_dir = os.path.join(options.target_dir, lang.title()) +        if not os.path.exists(out_dir): +            os.makedirs(out_dir) +        out_file = os.path.join(out_dir, "LLDBWrap{}.cpp".format(lang.title())) +        swig_command = [ +            options.swig_executable, +            "-c++", +        ] +        swig_command.append("-" + lang) +        if lang == "python": +            swig_command.append("-threads") + +        swig_command.extend([ +            "-I" + include_folder, +            "-D__STDC_LIMIT_MACROS", +            "-D__STDC_CONSTANT_MACROS", +            "-outdir", out_dir, +            "-o", out_file, +            in_file +        ]) + +        logging.info("generating swig {} bindings into {}" +                     .format(lang, out_dir)) +        logging.debug("swig command line: {}".format(swig_command)) +        try: +            # Execute swig +            swig_output = subprocess.check_output( +                swig_command, stderr=subprocess.STDOUT, universal_newlines=True) + +            logging.info("swig generation succeeded") +            if swig_output is not None and len(swig_output) > 0: +                logging.info("swig output: %s", swig_output) +            return (0, swig_output) +        except subprocess.CalledProcessError as e: +            logging.error("An error occurred executing swig.  returncode={}" +                          .format(e.returncode)) +            logging.error(e.output) +            return (e.returncode, e.output)
\ No newline at end of file diff --git a/scripts/swig_bot_lib/remote.py b/scripts/swig_bot_lib/remote.py new file mode 100644 index 000000000000..590a873d6270 --- /dev/null +++ b/scripts/swig_bot_lib/remote.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +""" +Shared functionality used by `client` and `server` when dealing with +remote transmission +""" + +# Future imports +from __future__ import absolute_import +from __future__ import print_function + +# Python modules +import json +import logging +import os +import socket +import struct +import sys + +# LLDB modules +import use_lldb_suite + +def generate_config(languages): +    config = {"languages": languages} +    return json.dumps(config) + +def parse_config(json_reader): +    json_data = json_reader.read() +    options_dict = json.loads(json_data) +    return options_dict + +def serialize_response_status(status): +    status = {"retcode": status[0], "output": status[1]} +    return json.dumps(status) + +def deserialize_response_status(json_reader): +    json_data = json_reader.read() +    response_dict = json.loads(json_data) +    return (response_dict["retcode"], response_dict["output"]) diff --git a/scripts/swig_bot_lib/server.py b/scripts/swig_bot_lib/server.py new file mode 100644 index 000000000000..cc25cee4d4b1 --- /dev/null +++ b/scripts/swig_bot_lib/server.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +""" +SWIG generation server.  Listens for connections from swig generation clients +and runs swig in the requested fashion, sending back the results. +""" + +# Future imports +from __future__ import absolute_import +from __future__ import print_function + +# Python modules +import argparse +import io +import logging +import os +import select +import shutil +import socket +import struct +import sys +import tempfile +import traceback + +# 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_port = 8537 + +def add_subparser_args(parser): +    parser.add_argument( +        "--port", +        action="store", +        default=default_port, +        help=("The local port to bind to")) + +    parser.add_argument( +        "--swig-executable", +        action="store", +        default=fs.find_executable("swig"), +        dest="swig_executable") + +def finalize_subparser_options(options): +    pass + +def initialize_listening_socket(options): +    logging.debug("Creating socket...") +    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +    logging.info("Binding to ip address '', port {}".format(options.port)) +    s.bind(('', options.port)) + +    logging.debug("Putting socket in listen mode...") +    s.listen() +    return s + +def accept_once(sock, options): +    logging.debug("Waiting for connection...") +    while True: +        rlist, wlist, xlist = select.select([sock], [], [], 0.5) +        if not rlist: +            continue + +        client, addr = sock.accept() +        logging.info("Received connection from {}".format(addr)) +        data_size = struct.unpack("!I", sockutil.recvall(client, 4))[0] +        logging.debug("Expecting {} bytes of data from client" +                      .format(data_size)) +        data = sockutil.recvall(client, data_size) +        logging.info("Received {} bytes of data from client" +                     .format(len(data))) + +        pack_location = None +        try: +            tempfolder = os.path.join(tempfile.gettempdir(), "swig-bot") +            os.makedirs(tempfolder, exist_ok=True) + +            pack_location = tempfile.mkdtemp(dir=tempfolder) +            logging.debug("Extracting archive to {}".format(pack_location)) + +            local.unpack_archive(pack_location, data) +            logging.debug("Successfully unpacked archive...") + +            config_file = os.path.normpath(os.path.join(pack_location, +                                                        "config.json")) +            parsed_config = remote.parse_config(io.open(config_file)) +            config = local.LocalConfig() +            config.languages = parsed_config["languages"] +            config.swig_executable = options.swig_executable +            config.src_root = pack_location +            config.target_dir = os.path.normpath( +                os.path.join(config.src_root, "output")) +            logging.info( +                "Running swig.  languages={}, swig={}, src_root={}, target={}" +                .format(config.languages, config.swig_executable, +                        config.src_root, config.target_dir)) + +            status = local.generate(config) +            logging.debug("Finished running swig.  Packaging up files {}" +                          .format(os.listdir(config.target_dir))) +            zip_data = io.BytesIO() +            zip_file = local.pack_archive(zip_data, config.target_dir, None) +            response_status = remote.serialize_response_status(status) +            logging.debug("Sending response status {}".format(response_status)) +            logging.info("(swig output) -> swig_output.json") +            zip_file.writestr("swig_output.json", response_status) + +            zip_file.close() +            response_data = zip_data.getvalue() +            logging.info("Sending {} byte response".format(len(response_data))) +            client.sendall(struct.pack("!I", len(response_data))) +            client.sendall(response_data) +        finally: +            if pack_location is not None: +                logging.debug("Removing temporary folder {}" +                              .format(pack_location)) +                shutil.rmtree(pack_location) + +def accept_loop(sock, options): +    while True: +        try: +            accept_once(sock, options) +        except Exception as e: +            error = traceback.format_exc() +            logging.error("An error occurred while processing the connection.") +            logging.error(error) + +def run(options): +    print(options) +    sock = initialize_listening_socket(options) +    accept_loop(sock, options) +    return options | 
