Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cros / commands / cros_deploy.py
1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """cros deploy: Deploy the packages onto the target device."""
6
7 import os
8 import logging
9 import urlparse
10
11 from chromite import cros
12 from chromite.buildbot import portage_utilities
13 from chromite.lib import cros_build_lib
14 from chromite.lib import remote_access
15
16
17 @cros.CommandDecorator('deploy')
18 class DeployCommand(cros.CrosCommand):
19   """Deploy the requested packages to the target device.
20
21   This command assumes the requested packages are already built in the
22   chroot. This command needs to run inside the chroot for inspecting
23   the installed packages.
24
25   Note: If the rootfs on your device is read-only, this command
26   remounts it as read-write. If the rootfs verification is enabled on
27   your device, this command disables it.
28   """
29
30   EPILOG = """
31 To deploy packages:
32   cros deploy device power_manager cherrypy
33   cros deploy device /path/to/package
34
35 To uninstall packages:
36   cros deploy --unmerge cherrypy
37
38 For more information of cros build usage:
39   cros build -h
40 """
41
42   DEVICE_BASE_DIR = '/usr/local/tmp/cros-deploy'
43   # This is defined in src/platform/dev/builder.py
44   STRIPPED_PACKAGES_DIR = 'stripped-packages'
45
46   # Override base class property to enable stats upload.
47   upload_stats = True
48
49   def __init__(self, options):
50     """Initializes DeployCommand."""
51     cros.CrosCommand.__init__(self, options)
52     self.emerge = True
53     self.strip = True
54     self.clean_binpkg = True
55     self.ssh_hostname = None
56     self.ssh_port = None
57     self.ssh_username = None
58     self.ssh_private_key = None
59     # The installation root of packages.
60     self.root = None
61
62   @classmethod
63   def AddParser(cls, parser):
64     """Add a parser."""
65     super(cls, DeployCommand).AddParser(parser)
66     parser.add_argument(
67         'device', help='IP[:port] address of the target device.')
68     parser.add_argument(
69         'packages', help='Packages to install. You can specify '
70         '[category/]package[:slot] or the path to the binary package.',
71         nargs='+')
72     parser.add_argument(
73         '--board', default=None, help='The board to use. By default it is '
74         'automatically detected. You can override the detected board with '
75         'this option.')
76     parser.add_argument(
77         '--no-strip', dest='strip', action='store_false', default=True,
78         help='Do not run strip_package to filter out preset paths in the '
79         'package. Stripping removes debug symbol files and reduces the size '
80         'of the package significantly. Defaults to always strip.')
81     parser.add_argument(
82         '--unmerge',  dest='emerge', action='store_false', default=True,
83         help='Unmerge requested packages.')
84     parser.add_argument(
85         '--root', default='/',
86         help="Package installation root, e.g. '/' or '/usr/local'"
87         "(default: '/').")
88     parser.add_argument(
89         '--no-clean-binpkg', dest='clean_binpkg', action='store_false',
90         default=True, help='Do not clean outdated binary packages. '
91         ' Defaults to always clean.')
92     parser.add_argument(
93         '--emerge-args', default=None,
94         help='Extra arguments to pass to emerge.')
95     parser.add_argument(
96         '--private-key', type='path', default=None,
97         help='SSH identify file (private key).')
98
99   def GetLatestPackage(self, board, pkg):
100     """Returns the path to the latest |pkg| for |board|."""
101     sysroot = cros_build_lib.GetSysroot(board=board)
102     matches = portage_utilities.FindPackageNameMatches(
103         pkg, board=board)
104     if not matches:
105       raise ValueError('Package %s is not installed!' % pkg)
106
107     idx = 0
108     if len(matches) > 1:
109       # Ask user to pick among multiple matches.
110       idx = cros_build_lib.GetChoice(
111           'Multiple matches found for %s: ' % pkg,
112           [os.path.join(x.category, x.pv) for x in matches])
113
114     cpv = matches[idx]
115     packages_dir = None
116     if self.strip:
117       try:
118         cros_build_lib.RunCommand(
119             ['strip_package', '--board', board,
120              os.path.join(cpv.category, '%s' % (cpv.pv))])
121         packages_dir = self.STRIPPED_PACKAGES_DIR
122       except cros_build_lib.RunCommandError:
123         logging.error('Cannot strip package %s', pkg)
124         raise
125
126     return portage_utilities.GetBinaryPackagePath(
127         cpv.category, cpv.package, cpv.version, sysroot=sysroot,
128         packages_dir=packages_dir)
129
130   def _Emerge(self, device, board, pkg, root, extra_args=None):
131     """Copies |pkg| to |device| and emerges it.
132
133     Args:
134       device: A ChromiumOSDevice object.
135       board: The board to use for retrieving |pkg|.
136       pkg: A package name.
137       root: The installation root of |pkg|.
138       extra_args: Extra arguments to pass to emerge.
139     """
140     if os.path.isfile(pkg):
141       latest_pkg = pkg
142     else:
143       latest_pkg = self.GetLatestPackage(board, pkg)
144
145     if not latest_pkg:
146       cros_build_lib.Die('Missing package %s.' % pkg)
147
148     pkgroot = os.path.join(device.work_dir, 'packages')
149     pkg_name = os.path.basename(latest_pkg)
150     pkg_dirname = os.path.basename(os.path.dirname(latest_pkg))
151     pkg_dir = os.path.join(pkgroot, pkg_dirname)
152     device.RunCommand(['mkdir', '-p', pkg_dir], remote_sudo=True)
153
154     logging.info('Copying %s to device...', latest_pkg)
155     device.CopyToDevice(latest_pkg, pkg_dir, remote_sudo=True)
156
157     portage_tmpdir = os.path.join(device.work_dir, 'portage-tmp')
158     device.RunCommand(['mkdir', '-p', portage_tmpdir], remote_sudo=True)
159     logging.info('Use portage temp dir %s', portage_tmpdir)
160
161     logging.info('Installing %s...', latest_pkg)
162     pkg_path = os.path.join(pkg_dir, pkg_name)
163
164     # We set PORTAGE_CONFIGROOT to '/usr/local' because by default all
165     # chromeos-base packages will be skipped due to the configuration
166     # in /etc/protage/make.profile/package.provided. However, there is
167     # a known bug that /usr/local/etc/portage is not setup properly
168     # (crbug.com/312041). This does not affect `cros deploy` because
169     # we do not use the preset PKGDIR.
170     extra_env = {
171         'FEATURES': '-sandbox',
172         'PKGDIR': pkgroot,
173         'PORTAGE_CONFIGROOT': '/usr/local',
174         'PORTAGE_TMPDIR': portage_tmpdir,
175         'PORTDIR': device.work_dir,
176         'CONFIG_PROTECT': '-*',
177     }
178     cmd = ['emerge', '--usepkg', pkg_path]
179     cmd.append('--root=%s' % root)
180     if extra_args:
181       cmd.append(extra_args)
182
183     try:
184       # Always showing the emerge output for clarity.
185       device.RunCommand(cmd, extra_env=extra_env, remote_sudo=True,
186                         capture_output=False, debug_level=logging.INFO)
187     except Exception:
188       logging.error('Failed to emerge package %s', pkg)
189       raise
190     else:
191       logging.info('%s has been installed.', pkg)
192     finally:
193       # Free up the space for other packages.
194       device.RunCommand(['rm', '-rf', portage_tmpdir, pkg_dir],
195                         error_code_ok=True, remote_sudo=True)
196
197   def _Unmerge(self, device, pkg, root):
198     """Unmerges |pkg| on |device|.
199
200     Args:
201       device: A RemoteDevice object.
202       pkg: A package name.
203       root: The installation root of |pkg|.
204     """
205     logging.info('Unmerging %s...', pkg)
206     cmd = ['qmerge', '--yes']
207     # Check if qmerge is available on the device. If not, use emerge.
208     if device.RunCommand(
209         ['qmerge', '--version'], error_code_ok=True).returncode != 0:
210       cmd = ['emerge']
211
212     cmd.extend(['--unmerge', pkg, '--root=%s' % root])
213     try:
214       # Always showing the qmerge/emerge output for clarity.
215       device.RunCommand(cmd, capture_output=False, remote_sudo=True,
216                         debug_level=logging.INFO)
217     except Exception:
218       logging.error('Failed to unmerge package %s', pkg)
219       raise
220     else:
221       logging.info('%s has been uninstalled.', pkg)
222
223   def _IsPathWritable(self, device, path):
224     """Returns True if |path| on |device| is writable."""
225     tmp_file = os.path.join(path, 'tmp.cros_flash')
226     result = device.RunCommand(['touch', tmp_file], remote_sudo=True,
227                                error_code_ok=True, capture_output=True)
228
229     if result.returncode != 0:
230       return False
231
232     device.RunCommand(['rm', tmp_file], error_code_ok=True, remote_sudo=True)
233
234     return True
235
236   def _ReadOptions(self):
237     """Processes options and set variables."""
238     self.emerge = self.options.emerge
239     self.strip = self.options.strip
240     self.clean_binpkg = self.options.clean_binpkg
241     self.root = self.options.root
242     device = self.options.device
243     # pylint: disable=E1101
244     if urlparse.urlparse(device).scheme == '':
245       # For backward compatibility, prepend ssh:// ourselves.
246       device = 'ssh://%s' % device
247
248     parsed = urlparse.urlparse(device)
249
250     if parsed.scheme == 'ssh':
251       self.ssh_hostname = parsed.hostname
252       self.ssh_username = parsed.username
253       self.ssh_port = parsed.port
254       self.ssh_private_key = self.options.private_key
255     else:
256       cros_build_lib.Die('Does not support device %s' % self.options.device)
257
258   def Run(self):
259     """Run cros deploy."""
260     cros_build_lib.AssertInsideChroot()
261     self._ReadOptions()
262     try:
263       with remote_access.ChromiumOSDeviceHandler(
264           self.ssh_hostname, port=self.ssh_port, username=self.ssh_username,
265           private_key=self.ssh_private_key,
266           base_dir=self.DEVICE_BASE_DIR) as device:
267         board = cros_build_lib.GetBoard(device_board=device.board,
268                                         override_board=self.options.board)
269         logging.info('Board is %s', board)
270
271         if self.clean_binpkg:
272           logging.info('Cleaning outdated binary packages for %s', board)
273           portage_utilities.CleanOutdatedBinaryPackages(board)
274
275         if not self._IsPathWritable(device, self.root):
276           # Only remounts rootfs if the given root is not writable.
277           if not device.MountRootfsReadWrite():
278             cros_build_lib.Die('Cannot remount rootfs as read-write. Exiting.')
279
280         for pkg in self.options.packages:
281           if self.emerge:
282             self._Emerge(device, board, pkg, self.root,
283                          extra_args=self.options.emerge_args)
284           else:
285             self._Unmerge(device, pkg, self.root)
286
287     except (Exception, KeyboardInterrupt) as e:
288       logging.error(e)
289       logging.error('Cros Deploy terminated before completing!')
290       if self.options.debug:
291         raise
292     else:
293       logging.info('Cros Deploy completed successfully.')