https://github.com/Kitware/CMake
Revision bc3697fa51e087953ec0e40a0a865e87c896e3a8 authored by Marc Chevrier on 01 February 2018, 13:49:05 UTC, committed by Brad King on 02 February 2018, 12:41:44 UTC
Fix logic added by commit 2ee10119ea (swig: fix incremental build in case of removed interface files, 2017-11-06). Name the extra targets added for Makefile generators using both the module name and .i base name to avoid collisions across modules. Also make sure the extra targets added for all .i files in a module are added as dependencies instead of just the last one. Fixes: #17704
1 parent a53697a
Tip revision: bc3697fa51e087953ec0e40a0a865e87c896e3a8 authored by Marc Chevrier on 01 February 2018, 13:49:05 UTC
UseSWIG: Restore support for like-named .i files in different modules
UseSWIG: Restore support for like-named .i files in different modules
Tip revision: bc3697f
cmConvertMSBuildXMLToJSON.py
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
import argparse
import codecs
import copy
import logging
import json
import os
from collections import OrderedDict
from xml.dom.minidom import parse, parseString, Element
class 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 values
def read_msbuild_xml(path, values={}):
"""Reads the MS Build XML file at the path and returns its contents.
Keyword arguments:
values -- The map to append the contents to (default {})
"""
# 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 values
def read_msbuild_json(path, values=[]):
"""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 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 values
def 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 functions
def __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 found
###########################################################################################
# private xml functions
def __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_values
def __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:
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
converted['switch'] = __get_attribute(node, 'Switch')
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 converted
def __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 None
def __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 functions
def __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 functions
def __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=(',', ': '))
###########################################################################################
# private list helpers
def __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 point
if __name__ == "__main__":
main()
Computing file changes ...