blob: 76361d087442ee4f3e42a45af98d9f1c4db008e2 [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
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +080017import getpass
Hung-Te Lineb7632b2016-07-29 15:38:34 +080018import glob
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080019import os
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
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +080023import time
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080024
You-Cheng Syu70e99ad2017-03-09 17:15:00 +080025import factory_common # pylint: disable=unused-import
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +080026from cros.factory.test import event_log
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080027from cros.factory.test.env import paths
Jon Salz25590302014-07-11 16:07:20 +080028from cros.factory.tools import install_symlinks
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +080029from cros.factory.utils import file_utils
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +080030from cros.factory.utils import sys_utils
Youcheng Syuac391772017-04-20 09:08:58 +000031from cros.factory.utils.process_utils import Spawn
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080032
33
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080034INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +080035VERSION_PATH = 'usr/local/factory/TOOLKIT_VERSION'
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):
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800170 ret = file_utils.ReadFile(os.path.join(self._src, VERSION_PATH))
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800171 if target_test_image:
Jon Salz4f3ade52014-02-20 17:55:09 +0800172 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800173 '\n'
174 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800175 '*** You are about to patch the factory toolkit into:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800176 '*** %s\n'
177 '***' % target_test_image)
178 else:
Jon Salz4f3ade52014-02-20 17:55:09 +0800179 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800180 '\n'
181 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800182 '*** You are about to install the factory toolkit to:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800183 '*** %s\n'
184 '***' % self._dest)
185 if self._dest == self._system_root:
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800186 if self._no_enable:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800187 ret += ('\n*** Factory tests will be disabled after this process is '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800188 'done, but\n*** you can enable them by creating the factory '
189 'enabled tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800190 else:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800191 ret += ('\n*** After this process is done, your device will start '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800192 'factory\n*** tests on the next reboot.\n***\n*** Factory '
193 'tests can be disabled by deleting the factory enabled\n*** '
194 'tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800195 return ret
196
Vic Yang7039f422014-07-07 15:38:13 -0700197 def _SetTagFile(self, name, path, enabled):
198 """Install or remove a tag file."""
199 if enabled:
200 print '*** Installing %s enabled tag...' % name
Youcheng Syuac391772017-04-20 09:08:58 +0000201 Spawn(['touch', path], sudo=True, log=True, check_call=True)
202 Spawn(['chmod', 'go+r', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700203 else:
204 print '*** Removing %s enabled tag...' % name
Youcheng Syuac391772017-04-20 09:08:58 +0000205 Spawn(['rm', '-f', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700206
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800207 def _SetDeviceID(self):
208 if self._device_id is not None:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800209 file_utils.WriteFile(os.path.join(event_log.DEVICE_ID_PATH),
210 self._device_id)
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800211
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800212 def _EnableApp(self, app, enabled):
213 """Enable / disable @app.
214
215 In factory/init/startup, a main app is considered disabled if and only:
216 1. file "factory/init/main.d/disable-@app" exists OR
217 2. file "factory/init/main.d/enable-@app" doesn't exist AND
218 file "factory/init/main.d/@app.sh" is not executable.
219
220 Therefore, we enable an app by removing file "disable-@app" and creating
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800221 file "enable-@app", and vice versa.
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800222 """
223 app_enable = os.path.join(self._usr_local_dest,
224 'factory', 'init', 'main.d', 'enable-' + app)
225 app_disable = os.path.join(self._usr_local_dest,
226 'factory', 'init', 'main.d', 'disable-' + app)
227 if enabled:
228 print '*** Enabling {app} ***'.format(app=app)
Youcheng Syuac391772017-04-20 09:08:58 +0000229 Spawn(['rm', '-f', app_disable], sudo=self._sudo, log=True,
230 check_call=True)
231 Spawn(['touch', app_enable], sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800232 else:
233 print '*** Disabling {app} ***'.format(app=app)
Youcheng Syuac391772017-04-20 09:08:58 +0000234 Spawn(['touch', app_disable], sudo=self._sudo, log=True, check_call=True)
235 Spawn(['rm', '-f', app_enable], sudo=self._sudo, log=True,
236 check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800237
238 def _EnableApps(self):
239 if not self._apps:
240 return
241
242 app_list = []
243 for app in self._apps:
244 if app[0] == '+':
245 app_list.append((app[1:], True))
246 elif app[0] == '-':
247 app_list.append((app[1:], False))
248 else:
249 raise ValueError(
250 'Use +{app} to enable and -{app} to disable'.format(app=app))
251
252 for app, enabled in app_list:
253 self._EnableApp(app, enabled)
254
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800255 def InstallFactorySubDir(self, sub_dirs):
256 """Install only the specified directories under factory folder."""
257
258 def _InstallOneSubDir(sub_dir_name):
259 sub_dir_dest = os.path.join(self._usr_local_dest, 'factory', sub_dir_name)
260 sub_dir_src = os.path.join(self._src, 'usr', 'local', 'factory',
261 sub_dir_name)
262 try:
Youcheng Syuac391772017-04-20 09:08:58 +0000263 Spawn(['mkdir', '-p', sub_dir_dest], sudo=True, log=True,
264 check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800265 except OSError as e:
266 print str(e)
267 return
268
Youcheng Syuac391772017-04-20 09:08:58 +0000269 Spawn(['rsync', '-a', '--force', '-v',
270 sub_dir_src + '/', sub_dir_dest],
271 sudo=self._sudo, log=True, check_call=True)
272 Spawn(['chown', '-R', 'root', sub_dir_dest],
273 sudo=self._sudo, log=True, check_call=True)
274 Spawn(['chmod', '-R', 'go+rX', sub_dir_dest],
275 sudo=self._sudo, log=True, check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800276
277 for sub_dir_name in sub_dirs:
278 _InstallOneSubDir(sub_dir_name)
279
280 self._SetTagFile('factory', self._tag_file, not self._no_enable)
281 self._EnableApps()
282
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800283 def Install(self):
284 print '*** Installing factory toolkit...'
Mao Huangf75aa3e2016-11-24 11:44:17 +0800285
286 # --no-owner and --no-group will set owner/group to the current user/group
287 # running the command. This is important if we're running with sudo, so
288 # the destination will be changed to root/root instead of the user/group
289 # before sudo (doesn't matter if sudo is not present). --force is also
290 # necessary to allow goofy directory from prior toolkit installations to
291 # be overwritten by the goofy symlink.
292 print '*** %s -> %s' % (self._usr_local_src, self._usr_local_dest)
Youcheng Syuac391772017-04-20 09:08:58 +0000293 Spawn(['rsync', '-a', '--no-owner', '--no-group', '--chmod=ugo+rX',
294 '--force'] + SERVER_FILE_MASK + [self._usr_local_src + '/',
295 self._usr_local_dest],
296 sudo=self._sudo, log=True, check_output=True, cwd=self._usr_local_src)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800297
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800298 print '*** Ensure SSH keys file permission...'
299 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
300 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
301 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
302 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
303 if ssh_private_keys:
Youcheng Syuac391772017-04-20 09:08:58 +0000304 Spawn(['chmod', '600'] + ssh_private_keys, log=True, check_call=True,
305 sudo=self._sudo)
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800306
Jon Salz25590302014-07-11 16:07:20 +0800307 print '*** Installing symlinks...'
308 install_symlinks.InstallSymlinks(
309 '../factory/bin',
310 os.path.join(self._usr_local_dest, 'bin'),
311 install_symlinks.MODE_FULL,
312 sudo=self._sudo)
313
Vic Yang7039f422014-07-07 15:38:13 -0700314 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700315 self._SetTagFile('presenter', self._presenter_tag_file,
316 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700317 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800318
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800319 self._SetDeviceID()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800320 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800321
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800322 print '*** Installation completed.'
323
324
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800325@contextmanager
326def DummyContext(arg):
327 """A context manager that simply yields its argument."""
328 yield arg
329
330
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800331def PrintBuildInfo(src_root):
332 """Print build information."""
333 info_file = os.path.join(src_root, 'REPO_STATUS')
334 if not os.path.exists(info_file):
335 raise OSError('Build info file not found!')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800336 print file_utils.ReadFile(info_file)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800337
338
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800339def PackFactoryToolkit(src_root, output_path, initial_version,
340 enable_device, enable_presenter):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800341 """Packs the files containing this script into a factory toolkit."""
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800342 if initial_version is None:
343 complete_version = '%s repacked by %s@%s at %s\n' % (
344 file_utils.ReadFile(os.path.join(src_root, VERSION_PATH)),
345 getpass.getuser(), os.uname()[1], time.strftime('%Y-%m-%d %H:%M:%S'))
346 initial_version = complete_version.splitlines()[0]
347 else:
348 complete_version = initial_version + '\n'
349 modified_times = len(complete_version.splitlines()) - 1
350 if modified_times == 0:
351 modified_msg = ''
352 else:
353 modified_msg = ' (modified %d times)' % modified_times
Jon Salz4f3ade52014-02-20 17:55:09 +0800354 with tempfile.NamedTemporaryFile() as help_header:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800355 help_header.write(initial_version + '\n' +
356 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,
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800360 src_root, output_path, initial_version + modified_msg,
361 INSTALLER_PATH, '--in-exe']
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800362 if not enable_device:
363 cmd.append('--no-enable-device')
364 if not enable_presenter:
365 cmd.append('--no-enable-presenter')
Youcheng Syuac391772017-04-20 09:08:58 +0000366 Spawn(cmd, check_call=True, log=True)
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800367 with file_utils.TempDirectory() as tmp_dir:
368 version_path = os.path.join(tmp_dir, VERSION_PATH)
369 os.makedirs(os.path.dirname(version_path))
370 file_utils.WriteFile(version_path, complete_version)
You-Cheng Syuc0682732017-05-10 12:17:05 +0800371 Spawn([cmd[0], '--lsm', version_path, '--append', tmp_dir, output_path],
372 check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800373 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800374 ' Factory toolkit generated at %s.\n'
375 '\n'
376 ' To install factory toolkit on a live device running a test image,\n'
377 ' copy this to the device and execute it as root.\n'
378 '\n'
379 ' Alternatively, the factory toolkit can be used to patch a test\n'
380 ' image. For more information, run:\n'
381 ' %s --help\n'
382 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800383
384
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800385def ExtractOverlord(src_root, output_dir):
386 output_dir = os.path.join(output_dir, 'overlord')
387 try:
388 os.makedirs(output_dir)
389 except OSError as e:
390 print str(e)
391 return
392
393 # Copy overlord binary and resource files
394 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
395 os.path.join(output_dir, 'overlordd'))
396 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
397 os.path.join(output_dir, 'app'))
398
399 # Give overlordd execution permission
400 os.chmod(os.path.join(output_dir, 'overlordd'), 0755)
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800401 print "Extracted overlord under '%s'" % output_dir
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800402
403
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800404def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800405 import logging
406 logging.basicConfig(level=logging.INFO)
407
408 # In order to determine which usage message to show, first determine
409 # whether we're in the self-extracting archive. Do this first
410 # because we need it to even parse the arguments.
411 if '--in-exe' in sys.argv:
412 sys.argv = [x for x in sys.argv if x != '--in-exe']
413 in_archive = True
414 else:
415 in_archive = False
416
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800417 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800418 description=HELP_HEADER + HELP_HEADER_ADVANCED,
419 usage=('install_factory_toolkit.run -- [options]' if in_archive
420 else None),
421 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800422 parser.add_argument(
423 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800424 help='A test image or the mount point of the stateful partition. '
425 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800426 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800427 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800428 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800429 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800430 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800431 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800432 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800433 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800434 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800435 help='Repack from previously unpacked toolkit')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800436 parser.add_argument('--version', metavar='VERSION',
437 help='String to write into TOOLKIT_VERSION when packing')
Vic Yang7039f422014-07-07 15:38:13 -0700438
Peter Ammon948b7172014-07-15 12:43:06 -0700439 parser.add_argument('--enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800440 action='store_true',
441 help='Run goofy in presenter mode on startup')
Peter Ammon948b7172014-07-15 12:43:06 -0700442 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800443 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800444 parser.set_defaults(enable_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700445
Vic Yang70fdae92015-02-17 19:21:08 -0800446 parser.add_argument('--non-cros', dest='non_cros',
447 action='store_true',
448 help='Install on non-ChromeOS host.')
449
Vic Yang7039f422014-07-07 15:38:13 -0700450 parser.add_argument('--enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800451 action='store_true',
452 help='Run goofy in device mode on startup')
Vic Yang7039f422014-07-07 15:38:13 -0700453 parser.add_argument('--no-enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800454 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800455 parser.set_defaults(enable_device=False)
Vic Yang423c2722014-09-24 16:05:06 +0800456
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800457 parser.add_argument('--device-id', dest='device_id', type=str, default=None,
458 help='Set device ID for this device')
459
Rong Chang6d65fad2014-08-22 16:00:12 +0800460 parser.add_argument('--exe-path', dest='exe_path',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800461 nargs='?', default=None,
462 help='Current self-extracting archive pathname')
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800463 parser.add_argument('--extract-overlord', dest='extract_overlord',
464 metavar='OUTPUT_DIR', type=str, default=None,
465 help='Extract overlord from the toolkit')
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800466 parser.add_argument('--install-dirs', nargs='+', default=None,
467 help=('Install only the specified directories under '
468 'factory folder. Can be used with --apps to '
469 'enable / disable some apps. Defaults to install '
470 'all folders.'))
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800471 parser.add_argument('--apps', type=lambda s: s.split(','), default=None,
472 help=('Enable or disable some apps under '
473 'factory/init/main.d/. Use prefix "-" to disable, '
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800474 'prefix "+" to enable, and use "," to separate. '
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800475 'For example: --apps="-goofy,+whale_servo"'))
Vic Yang7039f422014-07-07 15:38:13 -0700476
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800477 args = parser.parse_args()
478
Peter Shihad166772017-05-31 11:36:17 +0800479 src_root = paths.FACTORY_DIR
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800480 for _ in xrange(3):
481 src_root = os.path.dirname(src_root)
482
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800483 if args.extract_overlord is not None:
484 ExtractOverlord(src_root, args.extract_overlord)
485 return
486
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800487 # --pack-into may be called directly so this must be done before changing
488 # working directory to OLDPWD.
489 if args.pack_into and args.repack is None:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800490 PackFactoryToolkit(src_root, args.pack_into, args.version,
491 args.enable_device, args.enable_presenter)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800492 return
493
Jon Salz4f3ade52014-02-20 17:55:09 +0800494 if not in_archive:
495 # If you're not in the self-extracting archive, you're not allowed to
496 # do anything except the above --pack-into call.
497 parser.error('Not running from install_factory_toolkit.run; '
498 'only --pack-into (without --repack) is allowed')
499
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800500 # Change to original working directory in case the user specifies
501 # a relative path.
502 # TODO: Use USER_PWD instead when makeself is upgraded
503 os.chdir(os.environ['OLDPWD'])
504
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800505 if args.repack:
506 if args.pack_into is None:
507 parser.error('Must specify --pack-into when using --repack.')
Youcheng Syuac391772017-04-20 09:08:58 +0000508 Spawn([os.path.join(args.repack, INSTALLER_PATH),
509 '--pack-into', args.pack_into], check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800510 return
511
512 if args.build_info:
513 PrintBuildInfo(src_root)
514 return
515
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800516 if not os.path.exists(args.dest):
517 parser.error('Destination %s does not exist!' % args.dest)
518
519 patch_test_image = os.path.isfile(args.dest)
520
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800521 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800522 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800523
Hung-Te Lin56b18402015-01-16 14:52:30 +0800524 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800525 src=src_root, dest=dest, no_enable=args.no_enable,
526 enable_presenter=args.enable_presenter,
527 enable_device=args.enable_device, non_cros=args.non_cros,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800528 device_id=args.device_id,
529 apps=args.apps)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800530
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800531 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800532
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800533 if not args.yes:
534 answer = raw_input('*** Continue? [y/N] ')
535 if not answer or answer[0] not in 'yY':
536 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800537
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800538 if args.install_dirs:
539 installer.InstallFactorySubDir(args.install_dirs)
540 else:
541 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800542
543if __name__ == '__main__':
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800544 # makself interprets "LICENSE" environment variable string as license text and
Hung-Te Lin5a2a1c42016-11-10 12:43:40 +0800545 # will prompt user to accept before installation. For factory toolkit, we
546 # don't want any user interaction in installation and the license is already
547 # covered by ebuild or download platform like CPFE.
548 os.putenv('LICENSE', '')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800549 main()