From 2b95598948b06adf274c5be75c606248457bf7d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Louis-Francis=20Ratt=C3=A9-Boulianne?= <lfrb@collabora.com>
Date: Sun, 11 Dec 2016 00:27:55 -0500
Subject: [PATCH] Add install and uninstall commands
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Louis-Francis Ratté-Boulianne <lfrb@collabora.com>

Differential Revision: https://phabricator.apertis.org/D5232
---
 doc/man/ade-install.1   |  50 +++++++++++++
 doc/man/ade-uninstall.1 |  50 +++++++++++++
 doc/man/ade.1           |  16 +++++
 tools/ade               | 156 ++++++++++++++++++++++++++++------------
 4 files changed, 227 insertions(+), 45 deletions(-)
 create mode 100644 doc/man/ade-install.1
 create mode 100644 doc/man/ade-uninstall.1

diff --git a/doc/man/ade-install.1 b/doc/man/ade-install.1
new file mode 100644
index 0000000..14b211f
--- /dev/null
+++ b/doc/man/ade-install.1
@@ -0,0 +1,50 @@
+.TH ADE\-INSTALL 1 09/12/2016 0.1612.0 Apertis\ Development\ Tools\ Manual
+
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+
+.SH NAME
+
+ade-install \- Install application
+
+.SH SYNOPSIS
+.sp
+.nf
+\fIade install\fR [--bundle=<bundle>] --simulator
+\fIade install\fR [--bundle=<bundle>] --device [<user>[:<pass>]@]<hostname>[:<port>]
+.fi
+.sp
+.SH DESCRIPTION
+.sp
+Install application to simulator or given device\&.
+.sp
+.SH OPTIONS
+.sp
+.PP
+\fI\-\-bundle\fR
+.RS 4
+Bundle to install\&. If not specified, a bundle is created from project in
+current directory\&.
+.sp
+.RE
+.PP
+\fI\-\-simulator\fR
+.RS 4
+Use simulator as installation target\&.
+.sp
+.RE
+.PP
+\fI\-\-device\fR
+.RS 4
+Use device as installation target (e.g. user@apertis)\&.
+.sp
+.RE
+.SH SEE ALSO
+
+ade(1)
+
+.SH COPYRIGHT
+
+Copyright (c) 2016 Collabora Ltd.
diff --git a/doc/man/ade-uninstall.1 b/doc/man/ade-uninstall.1
new file mode 100644
index 0000000..90bd378
--- /dev/null
+++ b/doc/man/ade-uninstall.1
@@ -0,0 +1,50 @@
+.TH ADE\-UNINSTALL 1 09/12/2016 0.1612.0 Apertis\ Development\ Tools\ Manual
+
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+
+.SH NAME
+
+ade-uninstall \- Remove application
+
+.SH SYNOPSIS
+.sp
+.nf
+\fIade uninstall\fR [--bundle-id=<bundle>] --simulator
+\fIade uninstall\fR [--bundle-id=<bundle>] --device [<user>[:<pass>]@]<hostname>[:<port>]
+.fi
+.sp
+.SH DESCRIPTION
+.sp
+Remove an application from the simulator or given device\&.
+.sp
+.SH OPTIONS
+.sp
+.PP
+\fI\-\-bundle-id\fR
+.RS 4
+Application to remove\&. If not specified, the bundle-id from project in
+current directory is used\&.
+.sp
+.RE
+.PP
+\fI\-\-simulator\fR
+.RS 4
+Use simulator as removal target\&.
+.sp
+.RE
+.PP
+\fI\-\-device\fR
+.RS 4
+Use device as removal target (e.g. user@apertis)\&.
+.sp
+.RE
+.SH SEE ALSO
+
+ade(1)
+
+.SH COPYRIGHT
+
+Copyright (c) 2016 Collabora Ltd.
diff --git a/doc/man/ade.1 b/doc/man/ade.1
index ceeff6f..b7f8b7d 100644
--- a/doc/man/ade.1
+++ b/doc/man/ade.1
@@ -77,6 +77,22 @@ Export application to application bundle\&.
 See \fBade-export(1)\fR
 .sp
 .RE
+.PP
+\fIinstall\fR
+.RS 4
+Install application to simulator or device\&.
+.sp
+See \fBade-install(1)\fR
+.sp
+.RE
+.PP
+\fIuninstall\fR
+.RS 4
+Uninstall application from simulator or device\&.
+.sp
+See \fBade-uninstall(1)\fR
+.sp
+.RE
 .SH SEE ALSO
 
 ade-sysroot(1)
diff --git a/tools/ade b/tools/ade
index 3fa0c49..528270d 100755
--- a/tools/ade
+++ b/tools/ade
@@ -163,6 +163,16 @@ class Simulator:
     def __init__(self):
         pass
 
+    def _exec(self, *args):
+        r = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        return r.stdout.read().decode().strip()
+
+    def install(self, bundle):
+        self._exec('ribchester-bundle', 'remove', bundle.id)
+        self._exec('ribchester-bundle', 'install-app-bundle', bundle.path)
+
+    def uninstall(self, bundle_id):
+        self._exec('ribchester-bundle', 'remove', bundle_id)
 
 class Device:
 
@@ -224,6 +234,20 @@ class Device:
             raise InvalidDeviceError("Unsupported architecture {}".format(a))
         self.version.arch = triplet.arch
 
+    def install(self, bundle):
+        self._exec('ribchester-bundle', 'remove', bundle.id)
+        bundledir = '/home/user/bundles'
+        self._exec('mkdir', '-p', bundledir)
+        remote_path = os.path.join(bundledir, os.path.basename(bundle.path))
+        with closing(self._connect()) as ssh:
+            with closing(ssh.open_sftp()) as sftp:
+                sftp.put(bundle.path, remote_path)
+        self._exec('ribchester-bundle', 'install-app-bundle', remote_path)
+        self._exec('rm', remote_path)
+
+    def uninstall(self, bundle_id):
+        self._exec('ribchester-bundle', 'remove', bundle_id)
+
 
 class SysrootVersion:
 
@@ -804,7 +828,6 @@ class Ade:
         self.file = None
         self.dest = None
 
-        self.target = None
         self.sdk = False
         self.simulator = False
         self.sysroot = None
@@ -865,24 +888,21 @@ class Ade:
     def get_sysroot_manager(self):
         return SysrootManager(self.path, self.url, self.user, self.password, config=self.config)
 
-    def get_target(self):
-        if self.target:
-            return self.target
-
-        if self.sdk:
+    def get_object(self, classes=[Bundle, Device, Project, SDK, Simulator, Sysroot]):
+        if self.sdk and SDK in classes:
             try:
-                self.target = SDK()
+                obj = SDK()
             except NotInstalledError:
                 self.die("Not running in a SDK distribution")
             except Exception as e:
                 self.die("Invalid SDK installation: {0}".format(e))
-        elif self.simulator:
-            self.target = Simulator()
-        elif self.sysroot:
+        elif self.simulator and Simulator in classes:
+            obj = Simulator()
+        elif self.sysroot and Sysroot in classes:
             try:
                 version = SysrootVersion.from_id(self.sysroot)
-                self.target = self.get_sysroot_manager().get_installed(version)
-                if not self.target:
+                obj = self.get_sysroot_manager().get_installed(version)
+                if not obj:
                     self.die("No sysroot currently installed for {0}{1}{2}" \
                              .format(Colors.OKBLUE, version.get_name(), Colors.ENDC))
             except ValueError:
@@ -890,26 +910,32 @@ class Ade:
             except InvalidSysrootError:
                 self.die("Invalid sysroot installed for {0}{1}{2}" \
                          .format(Colors.OKBLUE, version.get_name(), Colors.ENDC))
-        elif self.device:
+        elif self.device and Device in classes:
             try:
-                self.target = Device.from_uri(self.device)
-                self.target.load_sysroot_version()
+                obj = Device.from_uri(self.device)
+                obj.load_sysroot_version()
             except InvalidDeviceError as e:
                 self.die("Invalid device: {0}".format(e))
-        elif self.project:
+        elif self.project and Project in classes:
             try:
-                self.target = Project()
+                obj = Project()
             except InvalidProjectError as e:
                 self.die("Invalid project: {0}".format(e))
-        elif self.bundle:
+        elif self.bundle and Bundle in classes:
             try:
-                self.target = Bundle.from_file(self.bundle)
+                obj = Bundle.from_file(self.bundle)
             except InvalidBundleError as e:
                 self.die("Invalid bundle: {0}".format(e))
         else:
-            self.die("No target (simulator, sysroot or device) specified")
+            return None
 
-        return self.target
+        return obj
+
+    def get_target(self):
+        target = self.get_object([Simulator, Sysroot, Device])
+        if not target:
+            self.die("No target (simulator, sysroot or device) specified")
+        return target
 
     def do_sysroot_list(self):
         manager = self.get_sysroot_manager()
@@ -1097,38 +1123,38 @@ class Ade:
                   .format(Colors.OKBLUE, self.sysroot_version.get_name(), Colors.ENDC))
 
     def do_info(self):
-        target = self.get_target()
+        obj = self.get_object()
 
-        if isinstance(target, SDK):
+        if isinstance(obj, SDK):
             self.info("* SDK version is {0}{1}{2}"
-                    .format(Colors.WARNING, self.target.version, Colors.ENDC))
+                    .format(Colors.WARNING, self.obj.version, Colors.ENDC))
             if self.format == 'parseable':
-                print("SDKVersion:{0}".format(self.target.version.get_tag()))
-        if isinstance(target, Sysroot):
+                print("SDKVersion:{0}".format(self.obj.version.get_tag()))
+        if isinstance(obj, Sysroot):
             self.info("* Sysroot version {0}{1}{2} is installed at '{3}'"
-                    .format(Colors.WARNING, self.target.version, Colors.ENDC, self.target.path))
+                    .format(Colors.WARNING, self.obj.version, Colors.ENDC, self.obj.path))
             if self.format == 'parseable':
-                print("SysrootVersion:{0}".format(self.target.version.get_tag()))
-                print("SysrootPath:{0}".format(self.target.path))
-        elif isinstance(target, Device):
+                print("SysrootVersion:{0}".format(self.obj.version.get_tag()))
+                print("SysrootPath:{0}".format(self.obj.path))
+        elif isinstance(obj, Device):
             self.info("* Device has image version {0}{1}{2}"
-                    .format(Colors.WARNING, self.target.version, Colors.ENDC))
+                    .format(Colors.WARNING, self.obj.version, Colors.ENDC))
             if self.format == 'parseable':
-                print("ImageVersion:{0}".format(self.target.version.get_tag()))
-        elif isinstance(target, Project):
-            self.info("* Project: {0}{1}{2}".format(Colors.WARNING, self.target.name, Colors.ENDC))
-            self.info("* BundleId: {0}".format(self.target.bundle_id))
-            self.info("* Version: {0}".format(self.target.version))
+                print("ImageVersion:{0}".format(self.obj.version.get_tag()))
+        elif isinstance(obj, Project):
+            self.info("* Project: {0}{1}{2}".format(Colors.WARNING, self.obj.name, Colors.ENDC))
+            self.info("* BundleId: {0}".format(self.obj.bundle_id))
+            self.info("* Version: {0}".format(self.obj.version))
             if self.format == 'parseable':
-                print("ProjectName:{0}".format(self.target.name))
-                print("BundleId:{0}".format(self.target.bundle_id))
-                print("AppVersion:{0}".format(self.target.version))
-        elif isinstance(target, Bundle):
-            self.info("* BundleId: {0}".format(self.target.id))
-            self.info("* Version: {0}".format(self.target.version))
+                print("ProjectName:{0}".format(self.obj.name))
+                print("BundleId:{0}".format(self.obj.bundle_id))
+                print("AppVersion:{0}".format(self.obj.version))
+        elif isinstance(obj, Bundle):
+            self.info("* BundleId: {0}".format(self.obj.id))
+            self.info("* Version: {0}".format(self.obj.version))
             if self.format == 'parseable':
-                print("BundleId:{0}".format(self.target.id))
-                print("AppVersion:{0}".format(self.target.version))
+                print("BundleId:{0}".format(self.obj.id))
+                print("AppVersion:{0}".format(self.obj.version))
 
     def do_configure(self):
         target = self.get_target()
@@ -1138,7 +1164,7 @@ class Ade:
             sysroot = target
         elif isinstance(target, Device):
             sdk = self.get_sdk()
-            version = self.target.version
+            version = target.version
             if sdk and version.is_compatible(sdk.version):
                 sysroot = None # Native compilation; SDK version matches device image version
             else:
@@ -1173,6 +1199,32 @@ class Ade:
         except Exception as e:
             self.die("Couldn't create bundle: {0}".format(e))
 
+    def do_install(self):
+        target = self.get_target()
+        try:
+            with tempfile.TemporaryDirectory() as tmpdir:
+                if self.bundle:
+                    bundle = Bundle.from_file(self.bundle)
+                else:
+                    project = Project()
+                    bundle = Bundle.from_project(project, tmpdir)
+
+                target.install(bundle)
+        except InvalidBundleError as e:
+            self.die("Invalid bundle: {0}".format(e))
+        except Exception as e:
+            self.die("Couldn't install application: {0}".format(e))
+
+    def do_uninstall(self):
+        target = self.get_target()
+        try:
+            if not self.bundle_id:
+                project = Project()
+                self.bundle_id = project.bundle_id
+            target.uninstall(self.bundle_id)
+        except Exception as e:
+            self.die("Couldn't uninstall application: {0}".format(e))
+
     def info(self, message):
         if self.format == 'friendly':
             print(message)
@@ -1267,6 +1319,20 @@ if __name__ == '__main__':
     export_parser = subparsers.add_parser('export', help="Create an application bundle")
     export_parser.add_argument('--dest',  help="Bundle destination directory")
 
+    # Install parser
+    install_parser = subparsers.add_parser('install', help="Install an application")
+    install_parser.add_argument('--bundle',  help="Path to bundle to install")
+    group = install_parser.add_mutually_exclusive_group()
+    group.add_argument('--simulator', help="Use simulator as target", action='store_true')
+    group.add_argument('--device', help="Use device as target (e.g. user@apertis)")
+
+    # Uninstall parser
+    uninstall_parser = subparsers.add_parser('uninstall', help="Uninstall an application")
+    uninstall_parser.add_argument('--bundle-id',  help="Bundle to uninstall")
+    group = uninstall_parser.add_mutually_exclusive_group()
+    group.add_argument('--simulator', help="Use simulator as target", action='store_true')
+    group.add_argument('--device', help="Use device as target (e.g. user@apertis)")
+
     argcomplete.autocomplete(root_parser)
 
     obj = Ade()
-- 
GitLab