| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 | # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying# file Copyright.txt or https://cmake.org/licensing for details.import argparseimport codecsimport copyimport loggingimport jsonimport osfrom collections import OrderedDictfrom xml.dom.minidom import parse, parseString, Elementclass VSFlags:    """Flags corresponding to cmIDEFlagTable."""    UserValue = "UserValue"  # (1 << 0)    UserIgnored = "UserIgnored"  # (1 << 1)    UserRequired = "UserRequired"  # (1 << 2)    Continue = "Continue"  #(1 << 3)    SemicolonAppendable = "SemicolonAppendable"  # (1 << 4)    UserFollowing = "UserFollowing"  # (1 << 5)    CaseInsensitive = "CaseInsensitive"  # (1 << 6)    UserValueIgnored = [UserValue, UserIgnored]    UserValueRequired = [UserValue, UserRequired]def vsflags(*args):    """Combines the flags."""    values = []    for arg in args:        __append_list(values, arg)    return valuesdef read_msbuild_xml(path, values=None):    """Reads the MS Build XML file at the path and returns its contents.    Keyword arguments:    values -- The map to append the contents to (default {})    """    if values is None:        values = {}    # Attempt to read the file contents    try:        document = parse(path)    except Exception as e:        logging.exception('Could not read MS Build XML file at %s', path)        return values    # Convert the XML to JSON format    logging.info('Processing MS Build XML file at %s', path)    # Get the rule node    rule = document.getElementsByTagName('Rule')[0]    rule_name = rule.attributes['Name'].value    logging.info('Found rules for %s', rule_name)    # Proprocess Argument values    __preprocess_arguments(rule)    # Get all the values    converted_values = []    __convert(rule, 'EnumProperty', converted_values, __convert_enum)    __convert(rule, 'BoolProperty', converted_values, __convert_bool)    __convert(rule, 'StringListProperty', converted_values,              __convert_string_list)    __convert(rule, 'StringProperty', converted_values, __convert_string)    __convert(rule, 'IntProperty', converted_values, __convert_string)    values[rule_name] = converted_values    return valuesdef read_msbuild_json(path, values=None):    """Reads the MS Build JSON file at the path and returns its contents.    Keyword arguments:    values -- The list to append the contents to (default [])    """    if values is None:        values = []    if not os.path.exists(path):        logging.info('Could not find MS Build JSON file at %s', path)        return values    try:        values.extend(__read_json_file(path))    except Exception as e:        logging.exception('Could not read MS Build JSON file at %s', path)        return values    logging.info('Processing MS Build JSON file at %s', path)    return valuesdef main():    """Script entrypoint."""    # Parse the arguments    parser = argparse.ArgumentParser(        description='Convert MSBuild XML to JSON format')    parser.add_argument(        '-t', '--toolchain', help='The name of the toolchain', required=True)    parser.add_argument(        '-o', '--output', help='The output directory', default='')    parser.add_argument(        '-r',        '--overwrite',        help='Whether previously output should be overwritten',        dest='overwrite',        action='store_true')    parser.set_defaults(overwrite=False)    parser.add_argument(        '-d',        '--debug',        help="Debug tool output",        action="store_const",        dest="loglevel",        const=logging.DEBUG,        default=logging.WARNING)    parser.add_argument(        '-v',        '--verbose',        help="Verbose output",        action="store_const",        dest="loglevel",        const=logging.INFO)    parser.add_argument('input', help='The input files', nargs='+')    args = parser.parse_args()    toolchain = args.toolchain    logging.basicConfig(level=args.loglevel)    logging.info('Creating %s toolchain files', toolchain)    values = {}    # Iterate through the inputs    for input in args.input:        input = __get_path(input)        read_msbuild_xml(input, values)    # Determine if the output directory needs to be created    output_dir = __get_path(args.output)    if not os.path.exists(output_dir):        os.mkdir(output_dir)        logging.info('Created output directory %s', output_dir)    for key, value in values.items():        output_path = __output_path(toolchain, key, output_dir)        if os.path.exists(output_path) and not args.overwrite:            logging.info('Comparing previous output to current')            __merge_json_values(value, read_msbuild_json(output_path))        else:            logging.info('Original output will be overwritten')        logging.info('Writing MS Build JSON file at %s', output_path)        __write_json_file(output_path, value)############################################################################################ private joining functionsdef __merge_json_values(current, previous):    """Merges the values between the current and previous run of the script."""    for value in current:        name = value['name']        # Find the previous value        previous_value = __find_and_remove_value(previous, value)        if previous_value is not None:            flags = value['flags']            previous_flags = previous_value['flags']            if flags != previous_flags:                logging.warning(                    'Flags for %s are different. Using previous value.', name)                value['flags'] = previous_flags        else:            logging.warning('Value %s is a new value', name)    for value in previous:        name = value['name']        logging.warning(            'Value %s not present in current run. Appending value.', name)        current.append(value)def __find_and_remove_value(list, compare):    """Finds the value in the list that corresponds with the value of compare."""    # next throws if there are no matches    try:        found = next(value for value in list                     if value['name'] == compare['name'] and value['switch'] ==                     compare['switch'])    except:        return None    list.remove(found)    return founddef __normalize_switch(switch, separator):    new = switch    if switch.startswith("/") or switch.startswith("-"):      new = switch[1:]    if new and separator:      new = new + separator    return new############################################################################################ private xml functionsdef __convert(root, tag, values, func):    """Converts the tag type found in the root and converts them using the func    and appends them to the values.    """    elements = root.getElementsByTagName(tag)    for element in elements:        converted = func(element)        # Append to the list        __append_list(values, converted)def __convert_enum(node):    """Converts an EnumProperty node to JSON format."""    name = __get_attribute(node, 'Name')    logging.debug('Found EnumProperty named %s', name)    converted_values = []    for value in node.getElementsByTagName('EnumValue'):        converted = __convert_node(value)        converted['value'] = converted['name']        converted['name'] = name        # Modify flags when there is an argument child        __with_argument(value, converted)        converted_values.append(converted)    return converted_valuesdef __convert_bool(node):    """Converts an BoolProperty node to JSON format."""    converted = __convert_node(node, default_value='true')    # Check for a switch for reversing the value    reverse_switch = __get_attribute(node, 'ReverseSwitch')    if reverse_switch:        __with_argument(node, converted)        converted_reverse = copy.deepcopy(converted)        converted_reverse['switch'] = reverse_switch        converted_reverse['value'] = 'false'        return [converted_reverse, converted]    # Modify flags when there is an argument child    __with_argument(node, converted)    return __check_for_flag(converted)def __convert_string_list(node):    """Converts a StringListProperty node to JSON format."""    converted = __convert_node(node)    # Determine flags for the string list    flags = vsflags(VSFlags.UserValue)    # Check for a separator to determine if it is semicolon appendable    # If not present assume the value should be ;    separator = __get_attribute(node, 'Separator', default_value=';')    if separator == ';':        flags = vsflags(flags, VSFlags.SemicolonAppendable)    converted['flags'] = flags    return __check_for_flag(converted)def __convert_string(node):    """Converts a StringProperty node to JSON format."""    converted = __convert_node(node, default_flags=vsflags(VSFlags.UserValue))    return __check_for_flag(converted)def __convert_node(node, default_value='', default_flags=vsflags()):    """Converts a XML node to a JSON equivalent."""    name = __get_attribute(node, 'Name')    logging.debug('Found %s named %s', node.tagName, name)    converted = {}    converted['name'] = name    switch = __get_attribute(node, 'Switch')    separator = __get_attribute(node, 'Separator')    converted['switch'] = __normalize_switch(switch, separator)    converted['comment'] = __get_attribute(node, 'DisplayName')    converted['value'] = default_value    # Check for the Flags attribute in case it was created during preprocessing    flags = __get_attribute(node, 'Flags')    if flags:        flags = flags.split(',')    else:        flags = default_flags    converted['flags'] = flags    return converteddef __check_for_flag(value):    """Checks whether the value has a switch value.    If not then returns None as it should not be added.    """    if value['switch']:        return value    else:        logging.warning('Skipping %s which has no command line switch',                        value['name'])        return Nonedef __with_argument(node, value):    """Modifies the flags in value if the node contains an Argument."""    arguments = node.getElementsByTagName('Argument')    if arguments:        logging.debug('Found argument within %s', value['name'])        value['flags'] = vsflags(VSFlags.UserValueIgnored, VSFlags.Continue)def __preprocess_arguments(root):    """Preprocesses occurrences of Argument within the root.    Argument XML values reference other values within the document by name. The    referenced value does not contain a switch. This function will add the    switch associated with the argument.    """    # Set the flags to require a value    flags = ','.join(vsflags(VSFlags.UserValueRequired))    # Search through the arguments    arguments = root.getElementsByTagName('Argument')    for argument in arguments:        reference = __get_attribute(argument, 'Property')        found = None        # Look for the argument within the root's children        for child in root.childNodes:            # Ignore Text nodes            if isinstance(child, Element):                name = __get_attribute(child, 'Name')                if name == reference:                    found = child                    break        if found is not None:            logging.info('Found property named %s', reference)            # Get the associated switch            switch = __get_attribute(argument.parentNode, 'Switch')            # See if there is already a switch associated with the element.            if __get_attribute(found, 'Switch'):                logging.debug('Copying node %s', reference)                clone = found.cloneNode(True)                root.insertBefore(clone, found)                found = clone            found.setAttribute('Switch', switch)            found.setAttribute('Flags', flags)        else:            logging.warning('Could not find property named %s', reference)def __get_attribute(node, name, default_value=''):    """Retrieves the attribute of the given name from the node.    If not present then the default_value is used.    """    if node.hasAttribute(name):        return node.attributes[name].value.strip()    else:        return default_value############################################################################################ private path functionsdef __get_path(path):    """Gets the path to the file."""    if not os.path.isabs(path):        path = os.path.join(os.getcwd(), path)    return os.path.normpath(path)def __output_path(toolchain, rule, output_dir):    """Gets the output path for a file given the toolchain, rule and output_dir"""    filename = '%s_%s.json' % (toolchain, rule)    return os.path.join(output_dir, filename)############################################################################################ private JSON file functionsdef __read_json_file(path):    """Reads a JSON file at the path."""    with open(path, 'r') as f:        return json.load(f)def __write_json_file(path, values):    """Writes a JSON file at the path with the values provided."""    # Sort the keys to ensure ordering    sort_order = ['name', 'switch', 'comment', 'value', 'flags']    sorted_values = [        OrderedDict(            sorted(                value.items(), key=lambda value: sort_order.index(value[0])))        for value in values    ]    with open(path, 'w') as f:        json.dump(sorted_values, f, indent=2, separators=(',', ': '))        f.write("\n")############################################################################################ private list helpersdef __append_list(append_to, value):    """Appends the value to the list."""    if value is not None:        if isinstance(value, list):            append_to.extend(value)        else:            append_to.append(value)############################################################################################ main entry pointif __name__ == "__main__":    main()
 |