blob: 508339484a77c37c53181af43226ff487b726c4f [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
You-Cheng Syuc6216912017-03-09 21:34:26 +080019import pipes
Wei-Ning Huang4855e792015-06-11 15:33:39 +080020import shutil
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080021import sys
Jon Salz4f3ade52014-02-20 17:55:09 +080022import tempfile
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080023
You-Cheng Syu70e99ad2017-03-09 17:15:00 +080024import factory_common # pylint: disable=unused-import
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +080025from cros.factory.test import event_log
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080026from cros.factory.test.env import paths
Jon Salz25590302014-07-11 16:07:20 +080027from cros.factory.tools import install_symlinks
You-Cheng Syuc6216912017-03-09 21:34:26 +080028from cros.factory.utils import file_utils
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +080029from cros.factory.utils import sys_utils
You-Cheng Syuc6216912017-03-09 21:34:26 +080030from cros.factory.utils import process_utils
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080031
32
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080033INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
Rong Chang6d65fad2014-08-22 16:00:12 +080034MAKESELF_SHELL = '/bin/sh'
35TOOLKIT_NAME = 'install_factory_toolkit.run'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080036
Jon Salz4f3ade52014-02-20 17:55:09 +080037# Short and sweet help header for the executable generated by makeself.
38HELP_HEADER = """
39Installs the factory toolkit, transforming a test image into a factory test
40image. You can:
41
42- Install the factory toolkit on a CrOS device that is running a test
43 image. To do this, copy install_factory_toolkit.run to the device and
44 run it. The factory tests will then come up on the next boot.
45
46 rsync -a install_factory_toolkit.run crosdevice:/tmp
47 ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
48
49- Modify a test image, turning it into a factory test image. When you
50 use the image on a device, the factory tests will come up.
51
52 install_factory_toolkit.run chromiumos_test_image.bin
53"""
54
55HELP_HEADER_ADVANCED = """
56- (advanced) Modify a mounted stateful partition, turning it into a factory
57 test image. This is equivalent to the previous command:
58
59 mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
60 install_factory_toolkit.run /mnt/stateful
61 umount /mnt/stateful
62
63- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
64
65 # Unpack but don't actually install
66 install_factory_toolkit.run --target /tmp/toolkit --noexec
67 # Edit some files in /tmp/toolkit
68 emacs /tmp/toolkit/whatever
69 # Repack
70 install_factory_toolkit.run -- --repack /tmp/toolkit \\
71 --pack-into /path/to/new/install_factory_toolkit.run
72"""
73
74# The makeself-generated header comes next. This is a little confusing,
75# so explain.
76HELP_HEADER_MAKESELF = """
77For complete usage information and advanced operations, run
78"install_factory_toolkit.run -- --help" (note the extra "--").
79
80Following is the help message from makeself, which was used to create
81this self-extracting archive.
82
83-----
84"""
85
Vic Yangdb1e20e2014-10-05 12:10:33 +080086SERVER_FILE_MASK = [
87 # Exclude Umpire server but keep Umpire client
88 '--include', 'py/umpire/__init__.*',
89 '--include', 'py/umpire/common.*',
90 '--include', 'py/umpire/client',
91 '--include', 'py/umpire/client/**',
92 '--exclude', 'py/umpire/**',
93
94 # Lumberjack is only used on Umpire server
95 '--exclude', 'py/lumberjack'
96]
97
Jon Salz4f3ade52014-02-20 17:55:09 +080098
Hung-Te Lin7b596ff2015-01-16 20:19:15 +080099class FactoryToolkitInstaller(object):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800100 """Factory toolkit installer.
101
102 Args:
103 src: Source path containing usr/ and var/.
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800104 dest: Installation destination path. Set this to the mount point of the
105 stateful partition if patching a test image.
106 no_enable: True to not install the tag file.
107 system_root: The path to the root of the file system. This must be left
108 as its default value except for unit testing.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800109 """
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800110
Jon Salzb7e44262014-05-07 15:53:37 +0800111 # Whether to sudo when rsyncing; set to False for testing.
112 _sudo = True
113
Peter Ammon948b7172014-07-15 12:43:06 -0700114 def __init__(self, src, dest, no_enable, enable_presenter,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800115 enable_device, non_cros=False, device_id=None, system_root='/',
116 apps=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800117 self._src = src
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800118 self._system_root = system_root
119 if dest == self._system_root:
120 self._usr_local_dest = os.path.join(dest, 'usr', 'local')
Jon Salz4f3ade52014-02-20 17:55:09 +0800121
122 # Make sure we're on a CrOS device.
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800123 if not non_cros and not sys_utils.InCrOSDevice():
Jon Salz4f3ade52014-02-20 17:55:09 +0800124 sys.stderr.write(
Vic Yang196d5242014-08-05 13:51:35 +0800125 "ERROR: You're not on a CrOS device (for more details, please\n"
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800126 'check sys_utils.py:InCrOSDevice), so you must specify a test\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800127 'image or a mounted stateful partition on which to install the\n'
128 'factory toolkit. Please run\n'
129 '\n'
130 ' install_factory_toolkit.run -- --help\n'
131 '\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800132 'for help.\n'
133 '\n'
134 'If you want to install the presenter on a non-CrOS host,\n'
135 'please run\n'
136 '\n'
137 ' install_factory_toolkit.run -- \\\n'
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800138 ' --non-cros --enable-presenter\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800139 '\n')
Jon Salz4f3ade52014-02-20 17:55:09 +0800140 sys.exit(1)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800141 if os.getuid() != 0:
Jon Salz4f3ade52014-02-20 17:55:09 +0800142 raise Exception('You must be root to install the factory toolkit on a '
143 'CrOS device.')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800144 else:
145 self._usr_local_dest = os.path.join(dest, 'dev_image')
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800146 if not os.path.exists(self._usr_local_dest):
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800147 raise Exception(
148 'The destination path %s is not a stateful partition!' % dest)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800149
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800150 self._dest = dest
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800151 self._usr_local_src = os.path.join(src, 'usr', 'local')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800152 self._no_enable = no_enable
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800153 self._tag_file = os.path.join(self._usr_local_dest, 'factory', 'enabled')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800154
Peter Ammon948b7172014-07-15 12:43:06 -0700155 self._enable_presenter = enable_presenter
156 self._presenter_tag_file = os.path.join(self._usr_local_dest, 'factory',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800157 'init', 'run_goofy_presenter')
Vic Yang7039f422014-07-07 15:38:13 -0700158
159 self._enable_device = enable_device
Ricky Liangd7716912014-07-10 11:52:24 +0800160 self._device_tag_file = os.path.join(self._usr_local_dest, 'factory',
161 'init', 'run_goofy_device')
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800162 self._device_id = device_id
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800163 self._apps = apps
Vic Yang7039f422014-07-07 15:38:13 -0700164
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800165 if not os.path.exists(self._usr_local_src):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800166 raise Exception(
167 'This installer must be run from within the factory toolkit!')
168
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800169 def WarningMessage(self, target_test_image=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800170 with open(os.path.join(self._src, 'VERSION')) as f:
171 ret = f.read()
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800172 if target_test_image:
Jon Salz4f3ade52014-02-20 17:55:09 +0800173 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800174 '\n'
175 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800176 '*** You are about to patch the factory toolkit into:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800177 '*** %s\n'
178 '***' % target_test_image)
179 else:
Jon Salz4f3ade52014-02-20 17:55:09 +0800180 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800181 '\n'
182 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800183 '*** You are about to install the factory toolkit to:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800184 '*** %s\n'
185 '***' % self._dest)
186 if self._dest == self._system_root:
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800187 if self._no_enable:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800188 ret += ('\n*** Factory tests will be disabled after this process is '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800189 'done, but\n*** you can enable them by creating the factory '
190 'enabled tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800191 else:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800192 ret += ('\n*** After this process is done, your device will start '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800193 'factory\n*** tests on the next reboot.\n***\n*** Factory '
194 'tests can be disabled by deleting the factory enabled\n*** '
195 'tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800196 return ret
197
Vic Yang7039f422014-07-07 15:38:13 -0700198 def _SetTagFile(self, name, path, enabled):
199 """Install or remove a tag file."""
200 if enabled:
201 print '*** Installing %s enabled tag...' % name
You-Cheng Syuc6216912017-03-09 21:34:26 +0800202 process_utils.Spawn(['touch', path],
203 sudo=True, log=True, check_call=True)
204 process_utils.Spawn(['chmod', 'go+r', path],
205 sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700206 else:
207 print '*** Removing %s enabled tag...' % name
You-Cheng Syuc6216912017-03-09 21:34:26 +0800208 process_utils.Spawn(['rm', '-f', path],
209 sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700210
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800211 def _SetDeviceID(self):
212 if self._device_id is not None:
213 with open(os.path.join(event_log.DEVICE_ID_PATH), 'w') as f:
214 f.write(self._device_id)
215
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800216 def _EnableApp(self, app, enabled):
217 """Enable / disable @app.
218
219 In factory/init/startup, a main app is considered disabled if and only:
220 1. file "factory/init/main.d/disable-@app" exists OR
221 2. file "factory/init/main.d/enable-@app" doesn't exist AND
222 file "factory/init/main.d/@app.sh" is not executable.
223
224 Therefore, we enable an app by removing file "disable-@app" and creating
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800225 file "enable-@app", and vice versa.
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800226 """
227 app_enable = os.path.join(self._usr_local_dest,
228 'factory', 'init', 'main.d', 'enable-' + app)
229 app_disable = os.path.join(self._usr_local_dest,
230 'factory', 'init', 'main.d', 'disable-' + app)
231 if enabled:
232 print '*** Enabling {app} ***'.format(app=app)
You-Cheng Syuc6216912017-03-09 21:34:26 +0800233 process_utils.Spawn(['rm', '-f', app_disable],
234 sudo=self._sudo, log=True, check_call=True)
235 process_utils.Spawn(['touch', app_enable],
236 sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800237 else:
238 print '*** Disabling {app} ***'.format(app=app)
You-Cheng Syuc6216912017-03-09 21:34:26 +0800239 process_utils.Spawn(['touch', app_disable],
240 sudo=self._sudo, log=True, check_call=True)
241 process_utils.Spawn(['rm', '-f', app_enable],
242 sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800243
244 def _EnableApps(self):
245 if not self._apps:
246 return
247
248 app_list = []
249 for app in self._apps:
250 if app[0] == '+':
251 app_list.append((app[1:], True))
252 elif app[0] == '-':
253 app_list.append((app[1:], False))
254 else:
255 raise ValueError(
256 'Use +{app} to enable and -{app} to disable'.format(app=app))
257
258 for app, enabled in app_list:
259 self._EnableApp(app, enabled)
260
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800261 def InstallFactorySubDir(self, sub_dirs):
262 """Install only the specified directories under factory folder."""
263
264 def _InstallOneSubDir(sub_dir_name):
265 sub_dir_dest = os.path.join(self._usr_local_dest, 'factory', sub_dir_name)
266 sub_dir_src = os.path.join(self._src, 'usr', 'local', 'factory',
267 sub_dir_name)
268 try:
You-Cheng Syuc6216912017-03-09 21:34:26 +0800269 process_utils.Spawn(['mkdir', '-p', sub_dir_dest],
270 sudo=True, log=True, check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800271 except OSError as e:
272 print str(e)
273 return
274
You-Cheng Syuc6216912017-03-09 21:34:26 +0800275 process_utils.Spawn(['rsync', '-a', '--force', '-v',
276 sub_dir_src + '/', sub_dir_dest],
277 sudo=self._sudo, log=True, check_call=True)
278 process_utils.Spawn(['chown', '-R', 'root', sub_dir_dest],
279 sudo=self._sudo, log=True, check_call=True)
280 process_utils.Spawn(['chmod', '-R', 'go+rX', sub_dir_dest],
281 sudo=self._sudo, log=True, check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800282
283 for sub_dir_name in sub_dirs:
284 _InstallOneSubDir(sub_dir_name)
285
286 self._SetTagFile('factory', self._tag_file, not self._no_enable)
287 self._EnableApps()
288
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800289 def Install(self):
290 print '*** Installing factory toolkit...'
Mao Huangf75aa3e2016-11-24 11:44:17 +0800291
292 # --no-owner and --no-group will set owner/group to the current user/group
293 # running the command. This is important if we're running with sudo, so
294 # the destination will be changed to root/root instead of the user/group
295 # before sudo (doesn't matter if sudo is not present). --force is also
296 # necessary to allow goofy directory from prior toolkit installations to
297 # be overwritten by the goofy symlink.
298 print '*** %s -> %s' % (self._usr_local_src, self._usr_local_dest)
You-Cheng Syuc6216912017-03-09 21:34:26 +0800299 process_utils.Spawn(['rsync', '-a', '--no-owner', '--no-group',
300 '--chmod=ugo+rX', '--force'] + SERVER_FILE_MASK +
301 [self._usr_local_src + '/', self._usr_local_dest],
302 sudo=self._sudo, log=True, check_output=True,
303 cwd=self._usr_local_src)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800304
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800305 print '*** Ensure SSH keys file permission...'
306 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
307 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
308 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
309 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
310 if ssh_private_keys:
You-Cheng Syuc6216912017-03-09 21:34:26 +0800311 process_utils.Spawn(['chmod', '600'] + ssh_private_keys,
312 log=True, check_call=True, sudo=self._sudo)
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800313
Jon Salz25590302014-07-11 16:07:20 +0800314 print '*** Installing symlinks...'
315 install_symlinks.InstallSymlinks(
316 '../factory/bin',
317 os.path.join(self._usr_local_dest, 'bin'),
318 install_symlinks.MODE_FULL,
319 sudo=self._sudo)
320
Vic Yang7039f422014-07-07 15:38:13 -0700321 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700322 self._SetTagFile('presenter', self._presenter_tag_file,
323 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700324 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800325
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800326 self._SetDeviceID()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800327 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800328
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800329 print '*** Installation completed.'
330
331
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800332@contextmanager
333def DummyContext(arg):
334 """A context manager that simply yields its argument."""
335 yield arg
336
337
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800338def PrintBuildInfo(src_root):
339 """Print build information."""
340 info_file = os.path.join(src_root, 'REPO_STATUS')
341 if not os.path.exists(info_file):
342 raise OSError('Build info file not found!')
343 with open(info_file, 'r') as f:
344 print f.read()
345
346
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800347def PackFactoryToolkit(src_root, output_path, enable_device, enable_presenter):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800348 """Packs the files containing this script into a factory toolkit."""
You-Cheng Syuc6216912017-03-09 21:34:26 +0800349
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800350 with open(os.path.join(src_root, 'VERSION'), 'r') as f:
351 version = f.read().strip()
You-Cheng Syuc6216912017-03-09 21:34:26 +0800352
Jon Salz4f3ade52014-02-20 17:55:09 +0800353 with tempfile.NamedTemporaryFile() as help_header:
Hung-Te Lin56b18402015-01-16 14:52:30 +0800354 help_header.write(version + '\n' + HELP_HEADER + HELP_HEADER_MAKESELF)
Jon Salz4f3ade52014-02-20 17:55:09 +0800355 help_header.flush()
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800356 cmd = [os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
Jon Salz4f3ade52014-02-20 17:55:09 +0800357 '--help-header', help_header.name,
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800358 src_root, output_path, version, INSTALLER_PATH, '--in-exe']
359 if not enable_device:
360 cmd.append('--no-enable-device')
361 if not enable_presenter:
362 cmd.append('--no-enable-presenter')
You-Cheng Syuc6216912017-03-09 21:34:26 +0800363 process_utils.Spawn(cmd, check_call=True, log=True)
364
365 excluded_files = ['MD5SUM', 'TOOLKIT_VERSION', 'factory.par']
366 tar_arguments = '--owner=0 --group=0 --mode=g-rwx,o-rwx --mtime=0 %s' % (
367 ' '.join(['--exclude=%s' % pipes.quote(fn) for fn in excluded_files]))
368 md5sum = process_utils.CheckOutput(
369 'tar -cC %s %s . | md5sum' % (
370 pipes.quote(os.path.join(src_root, 'usr', 'local', 'factory')),
371 tar_arguments),
372 shell=True, log=True).split()[0]
373 # Call makeself twice to append MD5SUM because we don't want to change the
374 # files in src_root (and it might be read only) or copy files to a temporary
375 # directory.
376 with file_utils.TempDirectory() as tmp_dir:
377 file_path = os.path.join(tmp_dir, 'usr', 'local', 'factory', 'MD5SUM')
378 os.makedirs(os.path.dirname(file_path))
379 with open(file_path, 'w') as f:
380 f.write('%s\n' % md5sum)
381 cmd = [cmd[0], '--append', tmp_dir, output_path]
382 process_utils.Spawn(cmd, check_call=True, log=True)
383
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800384 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800385 ' Factory toolkit generated at %s.\n'
386 '\n'
387 ' To install factory toolkit on a live device running a test image,\n'
388 ' copy this to the device and execute it as root.\n'
389 '\n'
390 ' Alternatively, the factory toolkit can be used to patch a test\n'
391 ' image. For more information, run:\n'
392 ' %s --help\n'
393 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800394
395
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800396def ExtractOverlord(src_root, output_dir):
397 output_dir = os.path.join(output_dir, 'overlord')
398 try:
399 os.makedirs(output_dir)
400 except OSError as e:
401 print str(e)
402 return
403
404 # Copy overlord binary and resource files
405 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
406 os.path.join(output_dir, 'overlordd'))
407 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
408 os.path.join(output_dir, 'app'))
409
410 # Give overlordd execution permission
411 os.chmod(os.path.join(output_dir, 'overlordd'), 0755)
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800412 print "Extracted overlord under '%s'" % output_dir
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800413
414
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800415def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800416 import logging
417 logging.basicConfig(level=logging.INFO)
418
419 # In order to determine which usage message to show, first determine
420 # whether we're in the self-extracting archive. Do this first
421 # because we need it to even parse the arguments.
422 if '--in-exe' in sys.argv:
423 sys.argv = [x for x in sys.argv if x != '--in-exe']
424 in_archive = True
425 else:
426 in_archive = False
427
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800428 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800429 description=HELP_HEADER + HELP_HEADER_ADVANCED,
430 usage=('install_factory_toolkit.run -- [options]' if in_archive
431 else None),
432 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800433 parser.add_argument(
434 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800435 help='A test image or the mount point of the stateful partition. '
436 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800437 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800438 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800439 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800440 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800441 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800442 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800443 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800444 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800445 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800446 help='Repack from previously unpacked toolkit')
Vic Yang7039f422014-07-07 15:38:13 -0700447
Peter Ammon948b7172014-07-15 12:43:06 -0700448 parser.add_argument('--enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800449 action='store_true',
450 help='Run goofy in presenter mode on startup')
Peter Ammon948b7172014-07-15 12:43:06 -0700451 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800452 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800453 parser.set_defaults(enable_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700454
Vic Yang70fdae92015-02-17 19:21:08 -0800455 parser.add_argument('--non-cros', dest='non_cros',
456 action='store_true',
457 help='Install on non-ChromeOS host.')
458
Vic Yang7039f422014-07-07 15:38:13 -0700459 parser.add_argument('--enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800460 action='store_true',
461 help='Run goofy in device mode on startup')
Vic Yang7039f422014-07-07 15:38:13 -0700462 parser.add_argument('--no-enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800463 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800464 parser.set_defaults(enable_device=False)
Vic Yang423c2722014-09-24 16:05:06 +0800465
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800466 parser.add_argument('--device-id', dest='device_id', type=str, default=None,
467 help='Set device ID for this device')
468
Rong Chang6d65fad2014-08-22 16:00:12 +0800469 parser.add_argument('--exe-path', dest='exe_path',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800470 nargs='?', default=None,
471 help='Current self-extracting archive pathname')
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800472 parser.add_argument('--extract-overlord', dest='extract_overlord',
473 metavar='OUTPUT_DIR', type=str, default=None,
474 help='Extract overlord from the toolkit')
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800475 parser.add_argument('--install-dirs', nargs='+', default=None,
476 help=('Install only the specified directories under '
477 'factory folder. Can be used with --apps to '
478 'enable / disable some apps. Defaults to install '
479 'all folders.'))
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800480 parser.add_argument('--apps', type=lambda s: s.split(','), default=None,
481 help=('Enable or disable some apps under '
482 'factory/init/main.d/. Use prefix "-" to disable, '
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800483 'prefix "+" to enable, and use "," to separate. '
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800484 'For example: --apps="-goofy,+whale_servo"'))
Vic Yang7039f422014-07-07 15:38:13 -0700485
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800486 args = parser.parse_args()
487
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +0800488 src_root = paths.FACTORY_PATH
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800489 for _ in xrange(3):
490 src_root = os.path.dirname(src_root)
491
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800492 if args.extract_overlord is not None:
493 ExtractOverlord(src_root, args.extract_overlord)
494 return
495
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800496 # --pack-into may be called directly so this must be done before changing
497 # working directory to OLDPWD.
498 if args.pack_into and args.repack is None:
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800499 PackFactoryToolkit(src_root, args.pack_into, args.enable_device,
500 args.enable_presenter)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800501 return
502
Jon Salz4f3ade52014-02-20 17:55:09 +0800503 if not in_archive:
504 # If you're not in the self-extracting archive, you're not allowed to
505 # do anything except the above --pack-into call.
506 parser.error('Not running from install_factory_toolkit.run; '
507 'only --pack-into (without --repack) is allowed')
508
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800509 # Change to original working directory in case the user specifies
510 # a relative path.
511 # TODO: Use USER_PWD instead when makeself is upgraded
512 os.chdir(os.environ['OLDPWD'])
513
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800514 if args.repack:
515 if args.pack_into is None:
516 parser.error('Must specify --pack-into when using --repack.')
You-Cheng Syuc6216912017-03-09 21:34:26 +0800517 process_utils.Spawn([os.path.join(args.repack, INSTALLER_PATH),
518 '--pack-into', args.pack_into],
519 check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800520 return
521
522 if args.build_info:
523 PrintBuildInfo(src_root)
524 return
525
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800526 if not os.path.exists(args.dest):
527 parser.error('Destination %s does not exist!' % args.dest)
528
529 patch_test_image = os.path.isfile(args.dest)
530
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800531 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800532 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800533
Hung-Te Lin56b18402015-01-16 14:52:30 +0800534 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800535 src=src_root, dest=dest, no_enable=args.no_enable,
536 enable_presenter=args.enable_presenter,
537 enable_device=args.enable_device, non_cros=args.non_cros,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800538 device_id=args.device_id,
539 apps=args.apps)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800540
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800541 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800542
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800543 if not args.yes:
544 answer = raw_input('*** Continue? [y/N] ')
545 if not answer or answer[0] not in 'yY':
546 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800547
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800548 if args.install_dirs:
549 installer.InstallFactorySubDir(args.install_dirs)
550 else:
551 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800552
553if __name__ == '__main__':
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800554 # makself interprets "LICENSE" environment variable string as license text and
Hung-Te Lin5a2a1c42016-11-10 12:43:40 +0800555 # will prompt user to accept before installation. For factory toolkit, we
556 # don't want any user interaction in installation and the license is already
557 # covered by ebuild or download platform like CPFE.
558 os.putenv('LICENSE', '')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800559 main()