Externals configuration file

Store configuration of external repositories in a JSON file.

Add command to dump configuration to stdout.

Change-Id: I84d5388d9bd31e4d1c6626260c9418e07446634a
diff --git a/fetch_externals.py b/fetch_externals.py
index 920ea4b..3cd6620 100755
--- a/fetch_externals.py
+++ b/fetch_externals.py
@@ -18,8 +18,11 @@
 # limitations under the License.
 #
 
+import argparse
+import json
 import os
 import subprocess
+import sys
 
 def print_args(args, **kwargs):
     cwd = kwargs['cwd']
@@ -37,37 +40,43 @@
     print_args(args, **kwargs)
     return subprocess.check_output(args, **kwargs)
 
+###############################################################################
+# Git class
+###############################################################################
+
 class Git(object):
-    def __init__(self, path, fetchurl, pushurl=None, revision='origin/master'):
+    def __init__(self, pwd, path, fetchurl, pushurl=None, revision='origin/master'):
+        self.pwd = pwd
         self.path = path
+        self.absolutepath = os.path.join(pwd, path)
         self.fetchurl = fetchurl
         self.pushurl = pushurl
         self.revision = revision
 
     def init(self):
-        if not os.path.exists(self.path):
-            os.makedirs(self.path)
+        if not os.path.exists(self.absolutepath):
+            os.makedirs(self.absolutepath)
 
-        if not os.path.exists(os.path.join(self.path, '.git')):
-            check_output(['git', 'init'], cwd=self.path)
+        if not os.path.exists(os.path.join(self.absolutepath, '.git')):
+            check_output(['git', 'init'], cwd=self.absolutepath)
 
     def remote_add(self, name, fetchurl):
-        remotes = check_output(['git', 'remote'], cwd=self.path).decode('utf-8').split('\n')
+        remotes = check_output(['git', 'remote'], cwd=self.absolutepath).decode('utf-8').split('\n')
         if not name in remotes:
-            check_output(['git', 'remote', 'add', '-m', self.revision, name, self.fetchurl], cwd=self.path)
+            check_output(['git', 'remote', 'add', '-m', self.revision, name, self.fetchurl], cwd=self.absolutepath)
 
             if self.pushurl:
-                check_output(['git', 'remote', 'set-url', '--add', '--push', name, self.pushurl], cwd=self.path)
+                check_output(['git', 'remote', 'set-url', '--add', '--push', name, self.pushurl], cwd=self.absolutepath)
 
     def fetch(self):
-        check_output(['git', 'fetch'], cwd=self.path)
+        check_output(['git', 'fetch'], cwd=self.absolutepath)
 
     def checkout(self, revision):
         rev = self.__get_rev(revision)
-        check_output(['git', 'checkout', rev], stderr=subprocess.STDOUT, cwd=self.path)
+        check_output(['git', 'checkout', rev], stderr=subprocess.STDOUT, cwd=self.absolutepath)
 
     def clone(self):
-        if not os.path.exists(os.path.join(self.path, '.git')):
+        if not os.path.exists(os.path.join(self.absolutepath, '.git')):
             self.init()
             self.remote_add('origin', self.fetchurl)
             self.fetch()
@@ -75,28 +84,103 @@
 
     def rebase(self):
         rev = self.__get_rev(self.revision)
-        check_output(['git', 'rebase', rev], cwd=self.path)
+        check_output(['git', 'rebase', rev], cwd=self.absolutepath)
+
+    def set_sha1(self):
+        self.revision = self.__get_rev(self.revision)
+
+    def get_dict(self):
+        data = {}
+        data['path'] = self.path
+        data['fetchurl'] = self.fetchurl
+
+        if self.pushurl:
+            data['pushurl'] = self.pushurl
+
+        data['revision'] = self.revision
+
+        return data
 
     def __get_rev(self, revision):
         try:
-            rev = check_output(['git', 'rev-parse', 'origin/' + self.revision], cwd=self.path)
+            rev = check_output(['git', 'rev-parse', 'origin/' + self.revision], stderr=subprocess.STDOUT, cwd=self.absolutepath)
         except:
-            rev = check_output(['git', 'rev-parse', self.revision], cwd=self.path)
+            rev = check_output(['git', 'rev-parse', self.revision], cwd=self.absolutepath)
 
         return rev.decode('utf-8').strip()
 
-basedir = os.path.dirname(os.path.realpath(__file__))
+###############################################################################
+# Externals class
+###############################################################################
 
-externals = [
-    Git(os.path.join(basedir, 'core_software'), "https://review.mlplatform.org/ml/ethos-u/ethos-u-core-software", pushurl='ssh://review.mlplatform.org:29418/ml/ethos-u/ethos-u-core-software', revision='master'),
-    Git(os.path.join(basedir, 'core_software/core_driver'), "https://review.mlplatform.org/ml/ethos-u/ethos-u-core-driver", pushurl='ssh://review.mlplatform.org:29418/ml/ethos-u/ethos-u-core-driver', revision='master'),
-    Git(os.path.join(basedir, 'core_software/cmsis'), 'https://github.com/ARM-software/CMSIS_5.git', revision='master'),
-    Git(os.path.join(basedir, 'core_software/tensorflow'), 'https://github.com/tensorflow/tensorflow', revision='master'),
-    Git(os.path.join(basedir, 'linux_driver_stack'), "https://review.mlplatform.org/ml/ethos-u/ethos-u-linux-driver-stack", pushurl='ssh://review.mlplatform.org:29418/ml/ethos-u/ethos-u-linux-driver-stack', revision='master'),
-    Git(os.path.join(basedir, 'vela'), "https://review.mlplatform.org/ml/ethos-u/ethos-u-vela", pushurl='ssh://review.mlplatform.org:29418/ml/ethos-u/ethos-u-vela', revision='master')
-]
+class Externals:
+    def __init__(self, config):
+        self.externals = []
+        self.load_config(config)
 
-for external in externals:
-    external.clone()
-    external.fetch()
-    external.rebase()
+    def load_config(self, config):
+        self.pwd = os.path.dirname(os.path.realpath(config))
+
+        with open(config, 'r') as fp:
+            data = json.load(fp)
+
+        for ext in data['externals']:
+            git = Git(self.pwd, ext['path'], ext['fetchurl'], ext.get('pushurl', None), ext['revision'])
+            self.externals.append(git)
+
+    def fetch(self):
+        for ext in self.externals:
+            ext.clone()
+            ext.fetch()
+            ext.checkout(ext.revision)
+
+    def set_sha1(self):
+        for ext in self.externals:
+            ext.set_sha1()
+
+    def get_dict(self):
+        data = { 'externals': [] }
+        for ext in self.externals:
+            data['externals'].append(ext.get_dict())
+
+        return data
+
+    def dump(self, fhandle):
+        fhandle.write(json.dumps(self.get_dict(), sort_keys=False, separators=(',', ': '), indent=4))
+
+###############################################################################
+# Handle functions
+###############################################################################
+
+def handle_fetch(args):
+    externals = Externals(args.configuration)
+    externals.fetch()
+
+def handle_dump(args):
+    externals = Externals(args.configuration)
+
+    if args.sha1:
+        externals.set_sha1()
+
+    externals.dump(sys.stdout)
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Fetch external repositories.')
+    parser.add_argument('-c', '--configuration', default='externals.json', help='Externals configuration file')
+    subparsers = parser.add_subparsers()
+
+    subparser = subparsers.add_parser('fetch', description='Fetch external repositories.')
+    subparser.set_defaults(func=handle_fetch)
+
+    subparser = subparsers.add_parser('dump', description='Dump configuration.')
+    subparser.set_defaults(func=handle_dump)
+    subparser.add_argument('-s', '--sha1', action='store_true', help='Replace revision with current SHA-1')
+
+    args = parser.parse_args()
+
+    if 'func' not in args:
+        parser.print_help()
+        sys.exit(2)
+
+    sys.exit(args.func(args))
+