ManifestXml: add include support

Having the ability to include other manifests is a very practical feature
to ease the managment of manifest. It allows to divide a manifest into separate
files, and create different environment depending  on what we want to release

You can have unlimited recursion of include, the manifest configs will simply be concatenated
as if it was in a single file.

command "repo manifest" will create a single manifest, and not recreate the manifest hierarchy

for example:
Our developement manifest will look like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <default revision="platform/android/main" remote="intel"/>
  <include name="server.xml"/> <!-- The Server configuration -->
  <include name="aosp.xml" />  <!-- All the AOSP projects -->
  <include name="bsp.xml" />   <!-- The BSP projects that we release in source form -->
  <include name="bsp-priv.xml" /> <!-- The source of the BSP projects we release in binary form -->
</manifest>

Our release manifest will look like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <default revision="platform/android/release-ext" remote="intel"/>
  <include name="server.xml"/> <!-- The Server configuration -->
  <include name="aosp.xml" />  <!-- All the AOSP projects -->
  <include name="bsp.xml" />   <!-- The BSP projects that we release in source form -->
  <include name="bsp-ext.xml" /> <!-- The PREBUILT version of the BSP projects we release in binary form -->
</manifest>

And it is also easy to create and maintain feature branch with a manifest that looks like:

<?xml version='1.0' encoding='UTF-8'?>
<manifest>
  <default revision="feature_branch_foobar" remote="intel"/>
  <include name="server.xml"/> <!-- The Server configuration -->
  <include name="aosp.xml" />  <!-- All the AOSP projects -->
  <include name="bsp.xml" />   <!-- The BSP projects that we release in source form -->
  <include name="bsp-priv.xml" /> <!-- The source of the BSP projects we release in binary form -->
</manifest>

Signed-off-by: Brian Harring <brian.harring@intel.com>
Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
Change-Id: I833a30d303039e485888768e6b81561b7665e89d
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 5378965..9f4585b 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -63,6 +63,9 @@
     <!ELEMENT repo-hooks (EMPTY)>
     <!ATTLIST repo-hooks in-project CDATA #REQUIRED>
     <!ATTLIST repo-hooks enabled-list CDATA #REQUIRED>
+
+    <!ELEMENT include      (EMPTY)>
+    <!ATTLIST include name CDATA #REQUIRED>
   ]>
 
 A description of the elements and their attributes follows.
@@ -192,6 +195,16 @@
 the user can remove a project, and possibly replace it with their
 own definition.
 
+Element include
+---------------
+
+This element provides the capability of including another manifest
+file into the originating manifest.  Normal rules apply for the
+target manifest to include- it must be a usable manifest on it's own.
+
+Attribute `name`; the manifest to include, specified relative to
+the manifest repositories root.
+
 
 Local Manifest
 ==============
diff --git a/manifest_xml.py b/manifest_xml.py
index ca65e33..2927fd1 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -298,18 +298,41 @@
 
       self._loaded = True
 
-  def _ParseManifest(self, is_root_file):
-    root = xml.dom.minidom.parse(self.manifestFile)
+  def _ParseManifestObject(self, path):
+    root = xml.dom.minidom.parse(path)
     if not root or not root.childNodes:
-      raise ManifestParseError(
-          "no root node in %s" %
-          self.manifestFile)
+      raise ManifestParseError("no root node in %s" % (path,))
 
     config = root.childNodes[0]
     if config.nodeName != 'manifest':
-      raise ManifestParseError(
-          "no <manifest> in %s" %
-          self.manifestFile)
+      raise ManifestParseError("no <manifest> in %s" % (path,))
+
+    return config
+
+  def _ParseManifest(self, is_root_file):
+    config = self._ParseManifestObject(self.manifestFile)
+
+    for node in config.childNodes:
+        if node.nodeName == 'include':
+            name = self._reqatt(node, 'name')
+            fp = os.path.join(self.manifestProject.worktree, name)
+            if not os.path.isfile(fp):
+                raise ManifestParseError, \
+                    "include %s doesn't exist or isn't a file" % \
+                    (name,)
+            try:
+                subconfig = self._ParseManifestObject(fp)
+            # should isolate this to the exact exception, but that's
+            # tricky.  actual parsing implementation may vary.
+            except (KeyboardInterrupt, RuntimeError, SystemExit):
+                raise
+            except Exception, e:
+                raise ManifestParseError(
+                    "failed parsing included manifest %s: %s", (name, e))
+
+            for sub_node in subconfig.childNodes:
+                config.appendChild(sub_node.cloneNode(True))
+
 
     for node in config.childNodes:
       if node.nodeName == 'remove-project':