Tools: Support out-of-tree secure partition build

Support TF-M to build mulitple scure partition whose source code folder
is maintained outside TF-M repo.

 - Rename TFM_EXTRA_MANIFEST_LIST_PATH to TFM_EXTRA_MANIFEST_LIST_FILES
   to indicate the out-of-tree secure partition manifest list files.
 - Add config TFM_EXTRA_PARTITION_PATHS to indicate the out-of-tree
   secure partition directories
 - Add attribute output_path in manifest list to enable out-of-tree
   partition to specify the directory of its generated files.
 - Support relative manifest path in the manifest list of an out-of-tree
   secure partition.
   For example, a manifest path can be set as a relative path to the
   manifest list file. Therefore, the manifest path can be independent
   to out-of-tree secure partitionsourece code path.

Change-Id: I743b458c405d2a9af43d4f234e52f80cdb545103
Signed-off-by: David Hu <david.hu@arm.com>
diff --git a/tools/tfm_parse_manifest_list.py b/tools/tfm_parse_manifest_list.py
index d2eae65..9f49067 100644
--- a/tools/tfm_parse_manifest_list.py
+++ b/tools/tfm_parse_manifest_list.py
@@ -68,7 +68,7 @@
 
     return partition_manifest
 
-def process_partition_manifests(manifest_list_files):
+def process_partition_manifests(manifest_list_files, extra_manifests_list):
     """
     Parse the input manifest, generate the data base for genereated files
     and generate manifest header files.
@@ -77,6 +77,8 @@
     ----------
     manifest_list_files:
         The manifest lists to parse.
+    extra_manifests_list:
+        The extra manifest list to parse and its original path.
 
     Returns
     -------
@@ -92,6 +94,24 @@
             manifest_list.extend(manifest_dic["manifest_list"])
             manifest_list_yaml_file.close()
 
+    # Out-of-tree secure partition build
+    if extra_manifests_list is not None:
+        for i, item in enumerate(extra_manifests_list):
+            # Skip if current item is the original manifest path
+            if os.path.isdir(item):
+                continue
+
+            # The manifest list file generated by configure_file()
+            with open(item) as manifest_list_yaml_file:
+                manifest_dic = yaml.safe_load(manifest_list_yaml_file)
+                extra_manifest_dic = manifest_dic["manifest_list"]
+                for dict in extra_manifest_dic:
+                    # Append original directory of out-of-tree partition's
+                    # manifest list source code
+                    dict['extra_path'] = extra_manifests_list[i + 1]
+                    manifest_list.append(dict)
+                manifest_list_yaml_file.close()
+
     pid_list = []
     no_pid_manifest_idx = []
     for i, manifest_item in enumerate(manifest_list):
@@ -114,27 +134,44 @@
     for manifest_item in manifest_list:
         # Replace environment variables in the manifest path
         manifest_path = os.path.expandvars(manifest_item['manifest'])
+
+        # Handle out-of-tree secure partition manifest file path
+        if 'extra_path' in manifest_item:
+            if not os.path.isabs(manifest_path):
+                # manifest file path provided by manifest list is relative to
+                # manifest list path
+                manifest_path = os.path.join(manifest_item['extra_path'], manifest_path).replace('\\', '/')
+
         file = open(manifest_path)
         manifest = manifest_validation(yaml.safe_load(file))
         file.close()
 
         manifest_dir, manifest_name = os.path.split(manifest_path)
         manifest_out_basename = manifest_name.replace('.yaml', '').replace('.json', '')
-        manifest_head_file = os.path.join(manifest_dir, "psa_manifest", manifest_out_basename + '.h').replace('\\', '/')
-        intermedia_file = os.path.join(manifest_dir, "auto_generated", 'intermedia_' + manifest_out_basename + '.c').replace('\\', '/')
-        load_info_file = os.path.join(manifest_dir, "auto_generated", 'load_info_' + manifest_out_basename + '.c').replace('\\', '/')
 
         if OUT_DIR is not None:
-            """
-            Remove the `source_path` portion of the filepaths, so that it can be
-            interpreted as a relative path from the OUT_DIR.
-            """
-            if 'source_path' in manifest_item:
-                # Replace environment variables in the source path
-                source_path = os.path.expandvars(manifest_item['source_path'])
-                manifest_head_file = os.path.relpath(manifest_head_file, start = source_path)
-                intermedia_file = os.path.relpath(intermedia_file, start = source_path)
-                load_info_file = os.path.relpath(load_info_file, start = source_path)
+            if 'output_path' in manifest_item:
+                # Build up generated files directory accroding to the relative
+                # path specified in output_path by the partition
+                output_path = os.path.expandvars(manifest_item['output_path'])
+                manifest_head_file = os.path.join(output_path, "psa_manifest", manifest_out_basename + '.h')
+                intermedia_file = os.path.join(output_path, "auto_generated", 'intermedia_' + manifest_out_basename + '.c')
+                load_info_file = os.path.join(output_path, "auto_generated", 'load_info_' + manifest_out_basename + '.c')
+            else:
+                manifest_head_file = os.path.join(manifest_dir, "psa_manifest", manifest_out_basename + '.h')
+                intermedia_file = os.path.join(manifest_dir, "auto_generated", 'intermedia_' + manifest_out_basename + '.c')
+                load_info_file = os.path.join(manifest_dir, "auto_generated", 'load_info_' + manifest_out_basename + '.c')
+
+                """
+                Remove the `source_path` portion of the filepaths, so that it can be
+                interpreted as a relative path from the OUT_DIR.
+                """
+                if 'source_path' in manifest_item:
+                    # Replace environment variables in the source path
+                    source_path = os.path.expandvars(manifest_item['source_path'])
+                    manifest_head_file = os.path.relpath(manifest_head_file, start = source_path)
+                    intermedia_file = os.path.relpath(intermedia_file, start = source_path)
+                    load_info_file = os.path.relpath(load_info_file, start = source_path)
 
             manifest_head_file = os.path.join(OUT_DIR, manifest_head_file).replace('\\', '/')
             intermedia_file = os.path.join(OUT_DIR, intermedia_file).replace('\\', '/')
@@ -355,6 +392,14 @@
                         , metavar='file-list'
                         , help='These files descripe the file list to generate')
 
+    parser.add_argument('-e', '--extra-manifest'
+                        , nargs='*'
+                        , dest='extra_manifests_args'
+                        , required=False
+                        , default=None
+                        , metavar='out-of-tree-manifest-list'
+                        , help='Optional. Manifest lists and original paths for out-of-tree secure partitions.')
+
     args = parser.parse_args()
     manifest_args = args.manifest_args
     gen_file_args = args.gen_file_args
@@ -382,11 +427,17 @@
 
     manifest_args = args.manifest_args
     gen_file_args = args.gen_file_args
+    extra_manifests_args = args.extra_manifests_args
     OUT_DIR = args.outdir
 
     manifest_list = [os.path.abspath(x) for x in args.manifest_args]
     gen_file_list = [os.path.abspath(x) for x in args.gen_file_args]
 
+    if extra_manifests_args is not None:
+        extra_manifests_list = [os.path.abspath(x) for x in extra_manifests_args]
+    else:
+        extra_manifests_list = None
+
     """
     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,
@@ -396,7 +447,7 @@
     """
     os.chdir(os.path.join(sys.path[0], ".."))
 
-    partition_list = process_partition_manifests(manifest_list)
+    partition_list = process_partition_manifests(manifest_list, extra_manifests_list)
 
     utilities = {}
     utilities['donotedit_warning'] = donotedit_warning