blob: b2cf9e1e4ea0b65e29121216dd9d596145e0b600 [file] [log] [blame]
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +08001#!/usr/bin/python -Bu
2#
3# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Factory toolkit installer.
8
9The factory toolkit is a self-extracting shellball containing factory test
10related files and this installer. This installer is invoked when the toolkit
11is deployed and is responsible for installing files.
12"""
13
14
15import argparse
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080016from contextlib import contextmanager
Hung-Te Lineb7632b2016-07-29 15:38:34 +080017import glob
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080018import os
Wei-Ning Huang4855e792015-06-11 15:33:39 +080019import shutil
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080020import sys
Jon Salz4f3ade52014-02-20 17:55:09 +080021import tempfile
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080022
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080023import factory_common # pylint: disable=W0611
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +080024from cros.factory.test import event_log
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080025from cros.factory.test.env import paths
Jon Salz25590302014-07-11 16:07:20 +080026from cros.factory.tools import install_symlinks
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +080027from cros.factory.utils import sys_utils
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080028from cros.factory.utils.process_utils import Spawn
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080029
30
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080031INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
Rong Chang6d65fad2014-08-22 16:00:12 +080032MAKESELF_SHELL = '/bin/sh'
33TOOLKIT_NAME = 'install_factory_toolkit.run'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080034
Jon Salz4f3ade52014-02-20 17:55:09 +080035# Short and sweet help header for the executable generated by makeself.
36HELP_HEADER = """
37Installs the factory toolkit, transforming a test image into a factory test
38image. You can:
39
40- Install the factory toolkit on a CrOS device that is running a test
41 image. To do this, copy install_factory_toolkit.run to the device and
42 run it. The factory tests will then come up on the next boot.
43
44 rsync -a install_factory_toolkit.run crosdevice:/tmp
45 ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
46
47- Modify a test image, turning it into a factory test image. When you
48 use the image on a device, the factory tests will come up.
49
50 install_factory_toolkit.run chromiumos_test_image.bin
51"""
52
53HELP_HEADER_ADVANCED = """
54- (advanced) Modify a mounted stateful partition, turning it into a factory
55 test image. This is equivalent to the previous command:
56
57 mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
58 install_factory_toolkit.run /mnt/stateful
59 umount /mnt/stateful
60
61- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
62
63 # Unpack but don't actually install
64 install_factory_toolkit.run --target /tmp/toolkit --noexec
65 # Edit some files in /tmp/toolkit
66 emacs /tmp/toolkit/whatever
67 # Repack
68 install_factory_toolkit.run -- --repack /tmp/toolkit \\
69 --pack-into /path/to/new/install_factory_toolkit.run
70"""
71
72# The makeself-generated header comes next. This is a little confusing,
73# so explain.
74HELP_HEADER_MAKESELF = """
75For complete usage information and advanced operations, run
76"install_factory_toolkit.run -- --help" (note the extra "--").
77
78Following is the help message from makeself, which was used to create
79this self-extracting archive.
80
81-----
82"""
83
Vic Yangdb1e20e2014-10-05 12:10:33 +080084SERVER_FILE_MASK = [
85 # Exclude Umpire server but keep Umpire client
86 '--include', 'py/umpire/__init__.*',
87 '--include', 'py/umpire/common.*',
88 '--include', 'py/umpire/client',
89 '--include', 'py/umpire/client/**',
90 '--exclude', 'py/umpire/**',
91
92 # Lumberjack is only used on Umpire server
93 '--exclude', 'py/lumberjack'
94]
95
Jon Salz4f3ade52014-02-20 17:55:09 +080096
Hung-Te Lin7b596ff2015-01-16 20:19:15 +080097class FactoryToolkitInstaller(object):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080098 """Factory toolkit installer.
99
100 Args:
101 src: Source path containing usr/ and var/.
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800102 dest: Installation destination path. Set this to the mount point of the
103 stateful partition if patching a test image.
104 no_enable: True to not install the tag file.
105 system_root: The path to the root of the file system. This must be left
106 as its default value except for unit testing.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800107 """
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800108
Jon Salzb7e44262014-05-07 15:53:37 +0800109 # Whether to sudo when rsyncing; set to False for testing.
110 _sudo = True
111
Peter Ammon948b7172014-07-15 12:43:06 -0700112 def __init__(self, src, dest, no_enable, enable_presenter,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800113 enable_device, non_cros=False, device_id=None, system_root='/',
114 apps=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800115 self._src = src
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800116 self._system_root = system_root
117 if dest == self._system_root:
118 self._usr_local_dest = os.path.join(dest, 'usr', 'local')
Jon Salz4f3ade52014-02-20 17:55:09 +0800119
120 # Make sure we're on a CrOS device.
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800121 if not non_cros and not sys_utils.InCrOSDevice():
Jon Salz4f3ade52014-02-20 17:55:09 +0800122 sys.stderr.write(
Vic Yang196d5242014-08-05 13:51:35 +0800123 "ERROR: You're not on a CrOS device (for more details, please\n"
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800124 'check sys_utils.py:InCrOSDevice), so you must specify a test\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800125 'image or a mounted stateful partition on which to install the\n'
126 'factory toolkit. Please run\n'
127 '\n'
128 ' install_factory_toolkit.run -- --help\n'
129 '\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800130 'for help.\n'
131 '\n'
132 'If you want to install the presenter on a non-CrOS host,\n'
133 'please run\n'
134 '\n'
135 ' install_factory_toolkit.run -- \\\n'
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800136 ' --non-cros --enable-presenter\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800137 '\n')
Jon Salz4f3ade52014-02-20 17:55:09 +0800138 sys.exit(1)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800139 if os.getuid() != 0:
Jon Salz4f3ade52014-02-20 17:55:09 +0800140 raise Exception('You must be root to install the factory toolkit on a '
141 'CrOS device.')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800142 else:
143 self._usr_local_dest = os.path.join(dest, 'dev_image')
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800144 if not os.path.exists(self._usr_local_dest):
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800145 raise Exception(
146 'The destination path %s is not a stateful partition!' % dest)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800147
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800148 self._dest = dest
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800149 self._usr_local_src = os.path.join(src, 'usr', 'local')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800150 self._no_enable = no_enable
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800151 self._tag_file = os.path.join(self._usr_local_dest, 'factory', 'enabled')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800152
Peter Ammon948b7172014-07-15 12:43:06 -0700153 self._enable_presenter = enable_presenter
154 self._presenter_tag_file = os.path.join(self._usr_local_dest, 'factory',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800155 'init', 'run_goofy_presenter')
Vic Yang7039f422014-07-07 15:38:13 -0700156
157 self._enable_device = enable_device
Ricky Liangd7716912014-07-10 11:52:24 +0800158 self._device_tag_file = os.path.join(self._usr_local_dest, 'factory',
159 'init', 'run_goofy_device')
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800160 self._device_id = device_id
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800161 self._apps = apps
Vic Yang7039f422014-07-07 15:38:13 -0700162
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800163 if not os.path.exists(self._usr_local_src):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800164 raise Exception(
165 'This installer must be run from within the factory toolkit!')
166
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800167 def WarningMessage(self, target_test_image=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800168 with open(os.path.join(self._src, 'VERSION')) as f:
169 ret = f.read()
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800170 if target_test_image:
Jon Salz4f3ade52014-02-20 17:55:09 +0800171 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800172 '\n'
173 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800174 '*** You are about to patch the factory toolkit into:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800175 '*** %s\n'
176 '***' % target_test_image)
177 else:
Jon Salz4f3ade52014-02-20 17:55:09 +0800178 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800179 '\n'
180 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800181 '*** You are about to install the factory toolkit to:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800182 '*** %s\n'
183 '***' % self._dest)
184 if self._dest == self._system_root:
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800185 if self._no_enable:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800186 ret += ('\n*** Factory tests will be disabled after this process is '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800187 'done, but\n*** you can enable them by creating the factory '
188 'enabled tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800189 else:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800190 ret += ('\n*** After this process is done, your device will start '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800191 'factory\n*** tests on the next reboot.\n***\n*** Factory '
192 'tests can be disabled by deleting the factory enabled\n*** '
193 'tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800194 return ret
195
Vic Yang7039f422014-07-07 15:38:13 -0700196 def _SetTagFile(self, name, path, enabled):
197 """Install or remove a tag file."""
198 if enabled:
199 print '*** Installing %s enabled tag...' % name
200 Spawn(['touch', path], sudo=True, log=True, check_call=True)
Ricky Liang8c88a122014-07-11 21:21:22 +0800201 Spawn(['chmod', 'go+r', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700202 else:
203 print '*** Removing %s enabled tag...' % name
204 Spawn(['rm', '-f', path], sudo=True, log=True, check_call=True)
205
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800206 def _SetDeviceID(self):
207 if self._device_id is not None:
208 with open(os.path.join(event_log.DEVICE_ID_PATH), 'w') as f:
209 f.write(self._device_id)
210
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800211 def _EnableApp(self, app, enabled):
212 """Enable / disable @app.
213
214 In factory/init/startup, a main app is considered disabled if and only:
215 1. file "factory/init/main.d/disable-@app" exists OR
216 2. file "factory/init/main.d/enable-@app" doesn't exist AND
217 file "factory/init/main.d/@app.sh" is not executable.
218
219 Therefore, we enable an app by removing file "disable-@app" and creating
220 file "enable-@app", and vise versa.
221 """
222 app_enable = os.path.join(self._usr_local_dest,
223 'factory', 'init', 'main.d', 'enable-' + app)
224 app_disable = os.path.join(self._usr_local_dest,
225 'factory', 'init', 'main.d', 'disable-' + app)
226 if enabled:
227 print '*** Enabling {app} ***'.format(app=app)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800228 Spawn(['rm', '-f', app_disable], sudo=self._sudo, log=True,
229 check_call=True)
230 Spawn(['touch', app_enable], sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800231 else:
232 print '*** Disabling {app} ***'.format(app=app)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800233 Spawn(['touch', app_disable], sudo=self._sudo, log=True, check_call=True)
234 Spawn(['rm', '-f', app_enable], sudo=self._sudo, log=True,
235 check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800236
237 def _EnableApps(self):
238 if not self._apps:
239 return
240
241 app_list = []
242 for app in self._apps:
243 if app[0] == '+':
244 app_list.append((app[1:], True))
245 elif app[0] == '-':
246 app_list.append((app[1:], False))
247 else:
248 raise ValueError(
249 'Use +{app} to enable and -{app} to disable'.format(app=app))
250
251 for app, enabled in app_list:
252 self._EnableApp(app, enabled)
253
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800254 def InstallFactorySubDir(self, sub_dirs):
255 """Install only the specified directories under factory folder."""
256
257 def _InstallOneSubDir(sub_dir_name):
258 sub_dir_dest = os.path.join(self._usr_local_dest, 'factory', sub_dir_name)
259 sub_dir_src = os.path.join(self._src, 'usr', 'local', 'factory',
260 sub_dir_name)
261 try:
262 Spawn(['mkdir', '-p', sub_dir_dest], sudo=True, log=True,
263 check_call=True)
264 except OSError as e:
265 print str(e)
266 return
267
268 Spawn(['rsync', '-a', '--force', '-v',
269 sub_dir_src + '/', sub_dir_dest],
270 sudo=self._sudo, log=True, check_call=True)
271 Spawn(['chown', '-R', 'root', sub_dir_dest],
272 sudo=self._sudo, log=True, check_call=True)
273 Spawn(['chmod', '-R', 'go+rX', sub_dir_dest],
274 sudo=self._sudo, log=True, check_call=True)
275
276 for sub_dir_name in sub_dirs:
277 _InstallOneSubDir(sub_dir_name)
278
279 self._SetTagFile('factory', self._tag_file, not self._no_enable)
280 self._EnableApps()
281
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800282 def Install(self):
283 print '*** Installing factory toolkit...'
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800284 for src, dest in ((self._usr_local_src, self._usr_local_dest),):
Jon Salzb7e44262014-05-07 15:53:37 +0800285 # Change the source directory to root, and add group/world read
286 # permissions. This is necessary because when the toolkit was
287 # unpacked, the user may not have been root so the permessions
288 # may be hosed. This is skipped for testing.
Peter Ammon5ac58422014-06-09 14:45:50 -0700289 # --force is necessary to allow goofy directory from prior
290 # toolkit installations to be overwritten by the goofy symlink.
Ricky Liang5e95be22014-07-09 12:52:07 +0800291 try:
292 if self._sudo:
293 Spawn(['chown', '-R', 'root', src],
294 sudo=True, log=True, check_call=True)
295 Spawn(['chmod', '-R', 'go+rX', src],
296 sudo=True, log=True, check_call=True)
297 print '*** %s -> %s' % (src, dest)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800298 Spawn(['rsync', '-a', '--force'] + SERVER_FILE_MASK +
Vic Yangdb1e20e2014-10-05 12:10:33 +0800299 [src + '/', dest], sudo=self._sudo, log=True,
300 check_output=True, cwd=src)
Ricky Liang5e95be22014-07-09 12:52:07 +0800301 finally:
302 # Need to change the source directory back to the original user, or the
303 # script in makeself will fail to remove the temporary source directory.
304 if self._sudo:
305 myuser = os.environ.get('USER')
306 Spawn(['chown', '-R', myuser, src],
307 sudo=True, log=True, check_call=True)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800308
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800309 print '*** Ensure SSH keys file permission...'
310 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
311 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
312 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
313 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
314 if ssh_private_keys:
315 Spawn(['chmod', '600'] + ssh_private_keys, log=True, check_call=True,
316 sudo=self._sudo)
317
Jon Salz25590302014-07-11 16:07:20 +0800318 print '*** Installing symlinks...'
319 install_symlinks.InstallSymlinks(
320 '../factory/bin',
321 os.path.join(self._usr_local_dest, 'bin'),
322 install_symlinks.MODE_FULL,
323 sudo=self._sudo)
324
Vic Yang7039f422014-07-07 15:38:13 -0700325 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700326 self._SetTagFile('presenter', self._presenter_tag_file,
327 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700328 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800329
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800330 self._SetDeviceID()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800331 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800332
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800333 print '*** Installation completed.'
334
335
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800336@contextmanager
337def DummyContext(arg):
338 """A context manager that simply yields its argument."""
339 yield arg
340
341
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800342def PrintBuildInfo(src_root):
343 """Print build information."""
344 info_file = os.path.join(src_root, 'REPO_STATUS')
345 if not os.path.exists(info_file):
346 raise OSError('Build info file not found!')
347 with open(info_file, 'r') as f:
348 print f.read()
349
350
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800351def PackFactoryToolkit(src_root, output_path, enable_device, enable_presenter):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800352 """Packs the files containing this script into a factory toolkit."""
353 with open(os.path.join(src_root, 'VERSION'), 'r') as f:
354 version = f.read().strip()
Jon Salz4f3ade52014-02-20 17:55:09 +0800355 with tempfile.NamedTemporaryFile() as help_header:
Hung-Te Lin56b18402015-01-16 14:52:30 +0800356 help_header.write(version + '\n' + HELP_HEADER + HELP_HEADER_MAKESELF)
Jon Salz4f3ade52014-02-20 17:55:09 +0800357 help_header.flush()
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800358 cmd = [os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
Jon Salz4f3ade52014-02-20 17:55:09 +0800359 '--help-header', help_header.name,
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800360 src_root, output_path, version, INSTALLER_PATH, '--in-exe']
361 if not enable_device:
362 cmd.append('--no-enable-device')
363 if not enable_presenter:
364 cmd.append('--no-enable-presenter')
365 Spawn(cmd, check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800366 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800367 ' Factory toolkit generated at %s.\n'
368 '\n'
369 ' To install factory toolkit on a live device running a test image,\n'
370 ' copy this to the device and execute it as root.\n'
371 '\n'
372 ' Alternatively, the factory toolkit can be used to patch a test\n'
373 ' image. For more information, run:\n'
374 ' %s --help\n'
375 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800376
377
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800378def ExtractOverlord(src_root, output_dir):
379 output_dir = os.path.join(output_dir, 'overlord')
380 try:
381 os.makedirs(output_dir)
382 except OSError as e:
383 print str(e)
384 return
385
386 # Copy overlord binary and resource files
387 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
388 os.path.join(output_dir, 'overlordd'))
389 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
390 os.path.join(output_dir, 'app'))
391
392 # Give overlordd execution permission
393 os.chmod(os.path.join(output_dir, 'overlordd'), 0755)
394 print "Extarcted overlord under '%s'" % output_dir
395
396
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800397def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800398 import logging
399 logging.basicConfig(level=logging.INFO)
400
401 # In order to determine which usage message to show, first determine
402 # whether we're in the self-extracting archive. Do this first
403 # because we need it to even parse the arguments.
404 if '--in-exe' in sys.argv:
405 sys.argv = [x for x in sys.argv if x != '--in-exe']
406 in_archive = True
407 else:
408 in_archive = False
409
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800410 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800411 description=HELP_HEADER + HELP_HEADER_ADVANCED,
412 usage=('install_factory_toolkit.run -- [options]' if in_archive
413 else None),
414 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800415 parser.add_argument(
416 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800417 help='A test image or the mount point of the stateful partition. '
418 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800419 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800420 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800421 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800422 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800423 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800424 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800425 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800426 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800427 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800428 help='Repack from previously unpacked toolkit')
Vic Yang7039f422014-07-07 15:38:13 -0700429
Peter Ammon948b7172014-07-15 12:43:06 -0700430 parser.add_argument('--enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800431 action='store_true',
432 help='Run goofy in presenter mode on startup')
Peter Ammon948b7172014-07-15 12:43:06 -0700433 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800434 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800435 parser.set_defaults(enable_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700436
Vic Yang70fdae92015-02-17 19:21:08 -0800437 parser.add_argument('--non-cros', dest='non_cros',
438 action='store_true',
439 help='Install on non-ChromeOS host.')
440
Vic Yang7039f422014-07-07 15:38:13 -0700441 parser.add_argument('--enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800442 action='store_true',
443 help='Run goofy in device mode on startup')
Vic Yang7039f422014-07-07 15:38:13 -0700444 parser.add_argument('--no-enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800445 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800446 parser.set_defaults(enable_device=False)
Vic Yang423c2722014-09-24 16:05:06 +0800447
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800448 parser.add_argument('--device-id', dest='device_id', type=str, default=None,
449 help='Set device ID for this device')
450
Rong Chang6d65fad2014-08-22 16:00:12 +0800451 parser.add_argument('--exe-path', dest='exe_path',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800452 nargs='?', default=None,
453 help='Current self-extracting archive pathname')
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800454 parser.add_argument('--extract-overlord', dest='extract_overlord',
455 metavar='OUTPUT_DIR', type=str, default=None,
456 help='Extract overlord from the toolkit')
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800457 parser.add_argument('--install-dirs', nargs='+', default=None,
458 help=('Install only the specified directories under '
459 'factory folder. Can be used with --apps to '
460 'enable / disable some apps. Defaults to install '
461 'all folders.'))
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800462 parser.add_argument('--apps', type=lambda s: s.split(','), default=None,
463 help=('Enable or disable some apps under '
464 'factory/init/main.d/. Use prefix "-" to disable, '
465 'prefix "+" to enable, and use "," to seperate. '
466 'For example: --apps="-goofy,+whale_servo"'))
Vic Yang7039f422014-07-07 15:38:13 -0700467
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800468 args = parser.parse_args()
469
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +0800470 src_root = paths.FACTORY_PATH
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800471 for _ in xrange(3):
472 src_root = os.path.dirname(src_root)
473
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800474 if args.extract_overlord is not None:
475 ExtractOverlord(src_root, args.extract_overlord)
476 return
477
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800478 # --pack-into may be called directly so this must be done before changing
479 # working directory to OLDPWD.
480 if args.pack_into and args.repack is None:
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800481 PackFactoryToolkit(src_root, args.pack_into, args.enable_device,
482 args.enable_presenter)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800483 return
484
Jon Salz4f3ade52014-02-20 17:55:09 +0800485 if not in_archive:
486 # If you're not in the self-extracting archive, you're not allowed to
487 # do anything except the above --pack-into call.
488 parser.error('Not running from install_factory_toolkit.run; '
489 'only --pack-into (without --repack) is allowed')
490
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800491 # Change to original working directory in case the user specifies
492 # a relative path.
493 # TODO: Use USER_PWD instead when makeself is upgraded
494 os.chdir(os.environ['OLDPWD'])
495
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800496 if args.repack:
497 if args.pack_into is None:
498 parser.error('Must specify --pack-into when using --repack.')
499 Spawn([os.path.join(args.repack, INSTALLER_PATH),
500 '--pack-into', args.pack_into], check_call=True, log=True)
501 return
502
503 if args.build_info:
504 PrintBuildInfo(src_root)
505 return
506
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800507 if not os.path.exists(args.dest):
508 parser.error('Destination %s does not exist!' % args.dest)
509
510 patch_test_image = os.path.isfile(args.dest)
511
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800512 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800513 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800514
Hung-Te Lin56b18402015-01-16 14:52:30 +0800515 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800516 src=src_root, dest=dest, no_enable=args.no_enable,
517 enable_presenter=args.enable_presenter,
518 enable_device=args.enable_device, non_cros=args.non_cros,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800519 device_id=args.device_id,
520 apps=args.apps)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800521
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800522 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800523
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800524 if not args.yes:
525 answer = raw_input('*** Continue? [y/N] ')
526 if not answer or answer[0] not in 'yY':
527 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800528
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800529 if args.install_dirs:
530 installer.InstallFactorySubDir(args.install_dirs)
531 else:
532 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800533
534if __name__ == '__main__':
Hung-Te Lin5a2a1c42016-11-10 12:43:40 +0800535 # makself inteprets "LICENSE" environment variable string as license text and
536 # will prompt user to accept before installation. For factory toolkit, we
537 # don't want any user interaction in installation and the license is already
538 # covered by ebuild or download platform like CPFE.
539 os.putenv('LICENSE', '')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800540 main()