Tools: Add arguments for manifest parsing tool

This patch adds two more optional arguments to the mainifest parsing
tool:
1. The manifest list file
    The custom secure partition manifest. It can replace or be appended
    to the default one.
2. The generated file list
    The custom generate file list. It can replace or be appended to
    the default one.

Note that these arguments are also optional individually and the default
values without arguments are not changed. The tool can be used as before
and the functionality is not changed.
For detailed usage, try "python3 tfm_parse_manifest_list.py -h"

Change-Id: I9d4340a2e1ac014090c35b3b55a330105ec8b7e9
Signed-off-by: Kevin Peng <kevin.peng@arm.com>
Co-authored-by: Alan DeMars <ademars@ti.com>
diff --git a/tools/tfm_parse_manifest_list.py b/tools/tfm_parse_manifest_list.py
index 26a57e2..9ea5f7d 100644
--- a/tools/tfm_parse_manifest_list.py
+++ b/tools/tfm_parse_manifest_list.py
@@ -22,7 +22,11 @@
                     "/*********** " + \
                     "WARNING: This is an auto-generated file. Do not edit!" + \
                     " ***********/"
-manifest_list_yaml_file_path = os.path.join('tools', 'tfm_manifest_list.yaml')
+
+DEFAULT_MANIFEST_LIST = os.path.join('tools', 'tfm_manifest_list.yaml')
+DEFAULT_GEN_FILE_LIST = os.path.join('tools', 'tfm_generated_file_list.yaml')
+
+OUT_DIR = None # The root directory that files are generated to
 
 class TemplateLoader(BaseLoader):
     """
@@ -49,85 +53,174 @@
             source = f.read()
         return source, template, False
 
-def load_manifest_list(file):
+def process_manifest(manifest_list_file, append):
     """
-    Load the substitution data from the manifests.
+    Parse the input manifest, generate the data base for genereated files
+    and generate manifest header files.
 
     Parameters
     ----------
-    file : file
-        A yaml file containing the manifest list
+    manifest_list_file:
+        The manifest list to parse.
+    append:
+        To append the manifest to original or not.
 
     Returns
     -------
-    list
-        The list of the contents of the manifest files, as generated by the yaml
-        parser.
-        """
-    db = []
-    manifest_list = yaml.safe_load(file)
-    for item in manifest_list["manifest_list"]:
-        manifest_path = os.path.expandvars(item['manifest'])
-        try:
-            file = open(manifest_path)
-            manifest = yaml.safe_load(file)
-            db.append({"manifest": manifest, "attr": item})
-        except IOError:
-            raise Exception ("Manifest for "+item['name']+" cannot be opened at path "+item['manifest'])
-
-    return db
-
-def generate_manifestfilename(env, out_dir):
+    The manifest header list and the data base.
     """
-    Generate manifestfilename header file.
+
+    db = []
+    manifest_header_list = []
+    manifest_list = []
+
+    if append:
+        # Load the default manifest first
+        with open(DEFAULT_MANIFEST_LIST) as default_manifest_list_yaml_file:
+            manifest_dic = yaml.safe_load(default_manifest_list_yaml_file)
+            manifest_list.extend(manifest_dic["manifest_list"])
+
+    with open(manifest_list_file) as manifest_list_yaml_file:
+        manifest_dic = yaml.safe_load(manifest_list_yaml_file)
+        manifest_list.extend(manifest_dic["manifest_list"])
+
+    templatefile_name = 'secure_fw/services/manifestfilename.template'
+    template = ENV.get_template(templatefile_name)
+
+    for manifest_item in manifest_list:
+        manifest_path = os.path.expandvars(manifest_item['manifest'])
+        file = open(manifest_path)
+        manifest = yaml.safe_load(file)
+
+        db.append({"manifest": manifest, "attr": manifest_item})
+
+        utilities = {}
+        utilities['donotedit_warning']=donotedit_warning
+
+        context = {}
+        context['manifest'] = manifest
+        context['attr'] = manifest_item
+        context['utilities'] = utilities
+
+        manifest_dir, manifest_name = os.path.split(manifest_path)
+        outfile_name = manifest_name.replace('yaml', 'h').replace('json', 'h')
+        context['file_name'] = outfile_name.replace('.h', '')
+        outfile_name = os.path.join(manifest_dir, "psa_manifest", outfile_name)
+
+        manifest_header_list.append(outfile_name)
+
+        if OUT_DIR is not None:
+            outfile_name = os.path.join(OUT_DIR, outfile_name)
+
+        outfile_path = os.path.dirname(outfile_name)
+        if not os.path.exists(outfile_path):
+            os.makedirs(outfile_path)
+
+        print ("Generating " + outfile_name)
+
+        outfile = io.open(outfile_name, "w", newline='\n')
+        outfile.write(template.render(context))
+        outfile.close()
+
+    return manifest_header_list, db
+
+def gen_files(context, gen_file_list, append):
+    """
+    Generate files according to the gen_file_list
 
     Parameters
     ----------
-    env :
-        The instance of Environment.
-    out_dir:
-        The root directory that files are generated to.
+    gen_file_list:
+        The list of files to generate
+    append:
+        To append the manifest to original or not
     """
+    file_list = []
 
-    manifest_header_list = []
-    with open(manifest_list_yaml_file_path) as manifest_list_yaml_file:
-        manifest_list = yaml.safe_load(manifest_list_yaml_file)
-        templatefile_name = 'secure_fw/services/manifestfilename.template'
-        template = env.get_template(templatefile_name)
+    if append:
+        # read default file list first
+        with open(DEFAULT_GEN_FILE_LIST) as file_list_yaml_file:
+            file_list_yaml = yaml.safe_load(file_list_yaml_file)
+            file_list.extend(file_list_yaml["file_list"])
 
-        for manifest_file in manifest_list["manifest_list"]:
-            manifest_path = os.path.expandvars(manifest_file['manifest'])
-            file = open(manifest_path)
-            manifest = yaml.safe_load(file)
+    with open(gen_file_list) as file_list_yaml_file:
+        # read list of files that need to be generated from templates using db
+        file_list_yaml = yaml.safe_load(file_list_yaml_file)
+        file_list.extend(file_list_yaml["file_list"])
 
-            utilities = {}
-            utilities['donotedit_warning']=donotedit_warning
+    for file in file_list:
+        outfile_name = os.path.expandvars(file["output"])
+        templatefile_name = outfile_name + '.template'
 
-            context = {}
-            context['manifest'] = manifest
-            context['attr'] = manifest_file
-            context['utilities'] = utilities
+        if OUT_DIR is not None:
+            outfile_name = os.path.join(OUT_DIR, outfile_name)
 
-            manifest_dir, manifest_name = os.path.split(manifest_path)
-            outfile_name = manifest_name.replace('yaml', 'h').replace('json', 'h')
-            context['file_name'] = outfile_name.replace('.h', '')
-            outfile_name = os.path.join(manifest_dir, "psa_manifest", outfile_name)
+        outfile_path = os.path.dirname(outfile_name)
+        if not os.path.exists(outfile_path):
+            os.makedirs(outfile_path)
 
-            manifest_header_list.append(outfile_name)
+        template = ENV.get_template(templatefile_name)
 
-            if out_dir is not None:
-                outfile_name = os.path.join(out_dir, outfile_name)
+        outfile = io.open(outfile_name, "w", newline='\n')
+        outfile.write(template.render(context))
+        outfile.close()
 
-            outfile_path = os.path.dirname(outfile_name)
-            if not os.path.exists(outfile_path):
-                os.makedirs(outfile_path)
+    print ("Generation of files done")
 
-            print ("Generating " + outfile_name)
+def parse_args():
+    parser = argparse.ArgumentParser(description='Parse secure partition manifest list and generate files listed by the file list')
+    parser.add_argument('-o', '--outdir'
+                        , dest='outdir'
+                        , required=False
+                        , default=None
+                        , metavar='out_dir'
+                        , help='The root directory for generated files, the default is TF-M root folder.')
 
-            outfile = io.open(outfile_name, "w", newline='\n')
-            outfile.write(template.render(context))
-            outfile.close()
-    return manifest_header_list
+    parser.add_argument('-m', '--manifest'
+                        , nargs='*'
+                        , dest='manifest_args'
+                        , required=False
+                        , default=[]
+                        , metavar='manifest'
+                        , help='The secure partition manifest list file to parse, the default is '+ DEFAULT_MANIFEST_LIST + '. \
+                                Or the manifest can be append to the default one by explicitly \"append\" it:\
+                                -m manifest_to_append append')
+
+    parser.add_argument('-f', '--file-list'
+                        , nargs='*'
+                        , dest='gen_file_args'
+                        , required=False
+                        , default=[]
+                        , metavar='file-list'
+                        , help='The file descripes the file list to generate, the default is ' + DEFAULT_GEN_FILE_LIST + '. \
+                                Or the file list can be append to the default one by explicitly \"append\" it:\
+                                -f files_to_append append')
+
+    args = parser.parse_args()
+    manifest_args = args.manifest_args
+    gen_file_args = args.gen_file_args
+
+    if len(manifest_args) > 2 or len(gen_file_args) > 2:
+        parser.print_help()
+        exit(1)
+
+    if len(manifest_args) == 2 and (manifest_args[1] != 'append' and manifest_args[1] != ''):
+        parser.print_help()
+        exit(1)
+
+    if len(gen_file_args) == 2 and (gen_file_args[1] != 'append' and gen_file_args[1] != ''):
+        parser.print_help()
+        exit(1)
+
+    return args
+
+ENV = Environment(
+        loader = TemplateLoader(),
+        autoescape = select_autoescape(['html', 'xml']),
+        lstrip_blocks = True,
+        trim_blocks = True,
+        keep_trailing_newline = True
+    )
 
 def main():
     """
@@ -136,79 +229,63 @@
     Generates the output files based on the templates and the manifests.
     """
 
-    parser = argparse.ArgumentParser(description='Parse secure partition manifest list and generate files listed by the file list')
-    parser.add_argument('-o', '--outdir'
-                        , dest='outdir'
-                        , required=False
-                        , default=None
-                        , metavar='out_dir'
-                        , help='The root directory for generated files, the default is TF-M base folder.')
+    global OUT_DIR
 
-    args = parser.parse_args()
+    args = parse_args()
 
-    out_dir = args.outdir
+    manifest_args = args.manifest_args
+    gen_file_args = args.gen_file_args
+    OUT_DIR = args.outdir
+    append_manifest = False
+    append_gen_file = False
+
+    if len(manifest_args) == 2 and manifest_args[1] == 'append':
+        append_manifest = True
+
+    if len(gen_file_args) == 2 and gen_file_args[1] == 'append':
+        append_gen_file = True
+
+    if len(manifest_args) == 0:
+        manifest_list = DEFAULT_MANIFEST_LIST
+    else:
+        """
+        Only convert to abs path when value is not default
+        Because the default value is a fixed relative path to TF-M root folder,
+        it will be various to different execution path if converted to absolute path.
+        The same for gen_file_list
+        """
+        manifest_list = os.path.abspath(args.manifest_args[0])
+    if len(gen_file_args) == 0:
+        gen_file_list = DEFAULT_GEN_FILE_LIST
+    else:
+        gen_file_list = os.path.abspath(args.gen_file_args[0])
 
     # Arguments could be relative path.
-    # Convert to absolute path as we are going to change directory later
-    if out_dir is not None:
-        out_dir = os.path.abspath(out_dir)
+    # Convert to absolute path as we are going to change diretory later
+    if OUT_DIR is not None:
+        OUT_DIR = os.path.abspath(OUT_DIR)
+
     """
-    Relative path to TF-M base folder is supported in the manifests
-    and default value of manifest list and generated file list are relative to TF-M base folder as well,
-    so first change directory to TF-M base folder.
+    Relative path to TF-M root folder is supported in the manifests
+    and default value of manifest list and generated file list are relative to TF-M root folder as well,
+    so first change directory to TF-M root folder.
     By doing this, the script can be executed anywhere
-    The script is located in <TF-M base folder>/tools, so sys.path[0]<location of the script>/.. is TF-M base folder.
+    The script is located in <TF-M root folder>/tools, so sys.path[0]<location of the script>/.. is TF-M root folder.
     """
     os.chdir(os.path.join(sys.path[0], ".."))
 
-    env = Environment(
-        loader = TemplateLoader(),
-        autoescape = select_autoescape(['html', 'xml']),
-        lstrip_blocks = True,
-        trim_blocks = True,
-        keep_trailing_newline = True
-    )
+    manifest_header_list, db = process_manifest(manifest_list, append_manifest)
 
-    # Generate manifestfilename
-    manifest_header_list = generate_manifestfilename(env, out_dir)
     utilities = {}
     context = {}
 
-    with open(manifest_list_yaml_file_path) as manifest_list_yaml_file:
-        # Read manifest list file, build database
-        db = load_manifest_list(manifest_list_yaml_file)
+    utilities['donotedit_warning']=donotedit_warning
+    utilities['manifest_header_list']=manifest_header_list
 
-        utilities['donotedit_warning']=donotedit_warning
-        utilities['manifest_header_list']=manifest_header_list
+    context['manifests'] = db
+    context['utilities'] = utilities
 
-        context['manifests'] = db
-        context['utilities'] = utilities
-
-    with open(os.path.join('tools', 'tfm_generated_file_list.yaml')) \
-                                                    as file_list_yaml_file:
-        # read list of files that need to be generated from templates using db
-        file_list_yaml = yaml.safe_load(file_list_yaml_file)
-        file_list = file_list_yaml["file_list"]
-        for file in file_list:
-            outfile_name = os.path.expandvars(file["output"])
-            templatefile_name = outfile_name + '.template'
-
-            if out_dir is not None:
-                outfile_name = os.path.join(out_dir, outfile_name)
-
-            outfile_path = os.path.dirname(outfile_name)
-            if not os.path.exists(outfile_path):
-                os.makedirs(outfile_path)
-
-            print ("Generating " + outfile_name)
-
-            template = env.get_template(templatefile_name)
-
-            outfile = io.open(outfile_name, "w", newline='\n')
-            outfile.write(template.render(context))
-            outfile.close()
-
-    print ("Generation of files done")
+    gen_files(context, gen_file_list, append_gen_file)
 
 if __name__ == "__main__":
     main()