diff --git a/ewp_parser.py b/ewp_parser.py new file mode 100644 index 0000000..289a2be --- /dev/null +++ b/ewp_parser.py @@ -0,0 +1,221 @@ +#---Imports--------------------------------------------------------------------- + +import os +import xml.etree.ElementTree as ETree +import argparse +import json + + +#---definitions----------------------------------------------------------------- + + +#---ewp parsing----------------------------------------------------------------- + +def parse_ewp(ewp_file, arg_file=None): + """ + Parse an ewp file, returning a list of defines, includes and files to compile. + The first valid configuration in the file is used. An additionnal argvars file + can be used to directly expand the variables present in the various fields + + :param ewp_file: the ewp file to parse + :param arg_file: additionnal argvars file + :returns: tuple of defines, includes and files to compile + """ + + #parse ewp file for defines and include paths + ewp_base_path = os.path.dirname(os.path.abspath(ewp_file)) + ewp_tree = ETree.parse(ewp_file) + + for config in ewp_tree.findall(".//configuration"): + raw_defines = list_option_values(config, "CCDefines") + raw_includes = list_option_values(config, "CCIncludePath2") + + if 0 != len(raw_defines) and 0 != len(raw_includes): + break + + raw_files = list_project_files(ewp_tree) + + #parse argvars, if any, for global values + if None != arg_file: + arg_tree = ETree.parse(arg_file) + + variables = list_variables(arg_tree) + else: + variables = {} + + variables["PROJ_DIR"] = ewp_base_path + + #expand variables in the defines and include paths + defines = expand_variables(raw_defines, variables) + includes = expand_variables(raw_includes, variables) + files = expand_variables(raw_files, variables) + norm_files = [os.path.normpath(file) for file in files] + + return (defines, includes, norm_files) + + +def list_option_values(config, option_name): + """ + Tool function to list the compilation options with the given name defined in + an ewp file's configuration + + :param config: xml tree representing an ewp configuration + :param option_name: the name of the options to list + :returns: a list of the compilations options + """ + found = False + values = [] + + for option in config.findall('settings/data/option'): + for child in option: + if (child.text == option_name): + found = True + + if found and (child.tag == "state") and (None != child.text): + values.append(normalize_path(child.text)) + + if found: + break + + return values + + +def list_variables(arg_tree): + """ + Tool function to list the variables defined in an argvars file + + :param argvars: xml tree representing an argvars file + :returns: a list of the defined variables + """ + variables = {} + + for variable in arg_tree.findall(".//variable"): + name = variable.find("name") + value = variable.find("value") + if None != name: + variables[name.text] = value.text + + return variables + + +def list_project_files(ewp_tree): + """ + Tool function to generate a list of the file to compile from a ewp file + + :param ewp_tree: xml tree representing a ewp file + :returns: a list of the file to compile + """ + raw_files = [file.find("name").text for file in ewp_tree.findall(".//file")] + return [os.path.abspath(normalize_path(file)) for file in raw_files] + + +def expand_variables(raw_values, variables): + """ + Tool function to expand the given values (paths, defines, ...) using the + global project variables. These variables are usually stored in the + custom_argvars file. Unknown variables will be left as-is + + :param raw_values: list of non-expanded values + :param variables: dictionnay of the variables and their associated values + :returns: the list of values with the variables expanded + """ + values = [] + + for raw_value in raw_values: + value = raw_value + for (key, var) in variables.items(): + value = value.replace("$" + key + "$", var) + values.append(value) + + return values + + +#---compile_commands generation------------------------------------------------- + +def generate_compile_commands(output_file, root_path, options, files): + """ + Generates a json compilation database according to this standard: + https://clang.llvm.org/docs/JSONCompilationDatabase.html, using the given + options and list of files. The options must be full, valid, compiler options + + :param output_file: the name of the file to generate + :param options: a list of the compilation options + :param files: a list of files + """ + with open(output_file, "w") as output: + commands = [] + for file in files: + commands.append( + {"directory": root_path, "arguments": options, "file": file}) + + json.dump(commands, output, indent=True) + + +def normalize_path(path): + """ + Normalizes the given path. Handles mixed-convention paths (dos/unix), which + the standard library functions don't + + :param path: the path to normalize + :returns: the normalized path + """ + return os.path.normpath(path.replace("\\", "/")) + + +#---main function--------------------------------------------------------------- + +if "__main__" == __name__: + + #setup command line + parser = argparse.ArgumentParser( + prog="Ewp2CompileCommands", + description="Parse the .ewp file of an IAR project and generates the " + "corresponding compile_commands.json") + + parser.add_argument("ewp_file", type=str, + help="path to the project's ewp file") + parser.add_argument("-a", "--argvars", type=str, + help="path to the project's argvars file") + parser.add_argument("-f", "--files", type=str, + help="list of files to use instead of the ones in the " + "ewp file") + parser.add_argument("-o", "--options", type=str, + help="list of options to add to project options") + parser.add_argument("-r", "--root", type=str, + help="path to project root. current path is used if not " + "specified") + + #get command line parameters + args = parser.parse_args() + + ewp_file = args.ewp_file + arg_file = args.argvars + file_list = args.files + opt_list = args.options + root_path = args.root + + #parse ewp and argvars files + (defs, incs, files) = parse_ewp(ewp_file, arg_file) + + #replace files if necessary + if None != file_list: + with open(file_list, "r") as f: + raw_files = [line.strip() for line in f.readlines()] + files = [os.path.abspath(normalize_path(file)) for file in raw_files] + + #parse additionnal options + opts = [] + if None != opt_list: + with open(opt_list, "r") as f: + opts = [line.strip() for line in f.readlines()] + + #set default root path if necessary + if None == root_path: + root_path = os.getcwd() + + #generate compile_commands + defs = ["-D" + define for define in defs] + incs = ["-I" + inc for inc in incs] + generate_compile_commands("compile_commands.json", root_path, + defs + incs + opts, files) +