blob: 34786d4dc3596a06381ef392002632bd39348f13 [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/**',
Peter Shihac904782017-06-14 15:24:09 +080093 '--exclude', 'bin/umpire',
94 '--exclude', 'bin/umpired',
Vic Yangdb1e20e2014-10-05 12:10:33 +080095]
96
Jon Salz4f3ade52014-02-20 17:55:09 +080097
Hung-Te Lin7b596ff2015-01-16 20:19:15 +080098class FactoryToolkitInstaller(object):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080099 """Factory toolkit installer.
100
101 Args:
102 src: Source path containing usr/ and var/.
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800103 dest: Installation destination path. Set this to the mount point of the
104 stateful partition if patching a test image.
105 no_enable: True to not install the tag file.
106 system_root: The path to the root of the file system. This must be left
107 as its default value except for unit testing.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800108 """
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800109
Jon Salzb7e44262014-05-07 15:53:37 +0800110 # Whether to sudo when rsyncing; set to False for testing.
111 _sudo = True
112
Peter Ammon948b7172014-07-15 12:43:06 -0700113 def __init__(self, src, dest, no_enable, enable_presenter,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800114 enable_device, non_cros=False, device_id=None, system_root='/',
115 apps=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800116 self._src = src
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800117 self._system_root = system_root
118 if dest == self._system_root:
119 self._usr_local_dest = os.path.join(dest, 'usr', 'local')
Jon Salz4f3ade52014-02-20 17:55:09 +0800120
121 # Make sure we're on a CrOS device.
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800122 if not non_cros and not sys_utils.InCrOSDevice():
Jon Salz4f3ade52014-02-20 17:55:09 +0800123 sys.stderr.write(
Vic Yang196d5242014-08-05 13:51:35 +0800124 "ERROR: You're not on a CrOS device (for more details, please\n"
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800125 'check sys_utils.py:InCrOSDevice), so you must specify a test\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800126 'image or a mounted stateful partition on which to install the\n'
127 'factory toolkit. Please run\n'
128 '\n'
129 ' install_factory_toolkit.run -- --help\n'
130 '\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800131 'for help.\n'
132 '\n'
133 'If you want to install the presenter on a non-CrOS host,\n'
134 'please run\n'
135 '\n'
136 ' install_factory_toolkit.run -- \\\n'
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800137 ' --non-cros --enable-presenter\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800138 '\n')
Jon Salz4f3ade52014-02-20 17:55:09 +0800139 sys.exit(1)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800140 if os.getuid() != 0:
Jon Salz4f3ade52014-02-20 17:55:09 +0800141 raise Exception('You must be root to install the factory toolkit on a '
142 'CrOS device.')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800143 else:
144 self._usr_local_dest = os.path.join(dest, 'dev_image')
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800145 if not os.path.exists(self._usr_local_dest):
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800146 raise Exception(
147 'The destination path %s is not a stateful partition!' % dest)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800148
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800149 self._dest = dest
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800150 self._usr_local_src = os.path.join(src, 'usr', 'local')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800151 self._no_enable = no_enable
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800152 self._tag_file = os.path.join(self._usr_local_dest, 'factory', 'enabled')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800153
Peter Ammon948b7172014-07-15 12:43:06 -0700154 self._enable_presenter = enable_presenter
155 self._presenter_tag_file = os.path.join(self._usr_local_dest, 'factory',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800156 'init', 'run_goofy_presenter')
Vic Yang7039f422014-07-07 15:38:13 -0700157
158 self._enable_device = enable_device
Ricky Liangd7716912014-07-10 11:52:24 +0800159 self._device_tag_file = os.path.join(self._usr_local_dest, 'factory',
160 'init', 'run_goofy_device')
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800161 self._device_id = device_id
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800162 self._apps = apps
Vic Yang7039f422014-07-07 15:38:13 -0700163
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800164 if not os.path.exists(self._usr_local_src):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800165 raise Exception(
166 'This installer must be run from within the factory toolkit!')
167
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800168 def WarningMessage(self, target_test_image=None):
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800169 ret = file_utils.ReadFile(os.path.join(self._src, VERSION_PATH))
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
Youcheng Syuac391772017-04-20 09:08:58 +0000200 Spawn(['touch', path], sudo=True, log=True, check_call=True)
201 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
Youcheng Syuac391772017-04-20 09:08:58 +0000204 Spawn(['rm', '-f', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700205
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800206 def _SetDeviceID(self):
207 if self._device_id is not None:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800208 file_utils.WriteFile(os.path.join(event_log.DEVICE_ID_PATH),
209 self._device_id)
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800210
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
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800220 file "enable-@app", and vice versa.
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800221 """
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)
Youcheng Syuac391772017-04-20 09:08:58 +0000228 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)
Youcheng Syuac391772017-04-20 09:08:58 +0000233 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:
Youcheng Syuac391772017-04-20 09:08:58 +0000262 Spawn(['mkdir', '-p', sub_dir_dest], sudo=True, log=True,
263 check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800264 except OSError as e:
265 print str(e)
266 return
267
Youcheng Syuac391772017-04-20 09:08:58 +0000268 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)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800275
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...'
Mao Huangf75aa3e2016-11-24 11:44:17 +0800284
285 # --no-owner and --no-group will set owner/group to the current user/group
286 # running the command. This is important if we're running with sudo, so
287 # the destination will be changed to root/root instead of the user/group
288 # before sudo (doesn't matter if sudo is not present). --force is also
289 # necessary to allow goofy directory from prior toolkit installations to
290 # be overwritten by the goofy symlink.
291 print '*** %s -> %s' % (self._usr_local_src, self._usr_local_dest)
Youcheng Syuac391772017-04-20 09:08:58 +0000292 Spawn(['rsync', '-a', '--no-owner', '--no-group', '--chmod=ugo+rX',
293 '--force'] + SERVER_FILE_MASK + [self._usr_local_src + '/',
294 self._usr_local_dest],
295 sudo=self._sudo, log=True, check_output=True, cwd=self._usr_local_src)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800296
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800297 print '*** Ensure SSH keys file permission...'
298 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
299 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
300 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
301 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
302 if ssh_private_keys:
Youcheng Syuac391772017-04-20 09:08:58 +0000303 Spawn(['chmod', '600'] + ssh_private_keys, log=True, check_call=True,
304 sudo=self._sudo)
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800305
Jon Salz25590302014-07-11 16:07:20 +0800306 print '*** Installing symlinks...'
307 install_symlinks.InstallSymlinks(
308 '../factory/bin',
309 os.path.join(self._usr_local_dest, 'bin'),
310 install_symlinks.MODE_FULL,
311 sudo=self._sudo)
312
Vic Yang7039f422014-07-07 15:38:13 -0700313 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700314 self._SetTagFile('presenter', self._presenter_tag_file,
315 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700316 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800317
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800318 self._SetDeviceID()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800319 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800320
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800321 print '*** Installation completed.'
322
323
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800324@contextmanager
325def DummyContext(arg):
326 """A context manager that simply yields its argument."""
327 yield arg
328
329
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800330def PrintBuildInfo(src_root):
331 """Print build information."""
332 info_file = os.path.join(src_root, 'REPO_STATUS')
333 if not os.path.exists(info_file):
334 raise OSError('Build info file not found!')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800335 print file_utils.ReadFile(info_file)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800336
337
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800338def PackFactoryToolkit(src_root, output_path, initial_version,
339 enable_device, enable_presenter):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800340 """Packs the files containing this script into a factory toolkit."""
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800341 if initial_version is None:
342 complete_version = '%s repacked by %s@%s at %s\n' % (
343 file_utils.ReadFile(os.path.join(src_root, VERSION_PATH)),
344 getpass.getuser(), os.uname()[1], time.strftime('%Y-%m-%d %H:%M:%S'))
345 initial_version = complete_version.splitlines()[0]
346 else:
347 complete_version = initial_version + '\n'
348 modified_times = len(complete_version.splitlines()) - 1
349 if modified_times == 0:
350 modified_msg = ''
351 else:
352 modified_msg = ' (modified %d times)' % modified_times
Jon Salz4f3ade52014-02-20 17:55:09 +0800353 with tempfile.NamedTemporaryFile() as help_header:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800354 help_header.write(initial_version + '\n' +
355 HELP_HEADER + HELP_HEADER_MAKESELF)
Jon Salz4f3ade52014-02-20 17:55:09 +0800356 help_header.flush()
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800357 cmd = [os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
Jon Salz4f3ade52014-02-20 17:55:09 +0800358 '--help-header', help_header.name,
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800359 src_root, output_path, initial_version + modified_msg,
360 INSTALLER_PATH, '--in-exe']
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800361 if not enable_device:
362 cmd.append('--no-enable-device')
363 if not enable_presenter:
364 cmd.append('--no-enable-presenter')
Youcheng Syuac391772017-04-20 09:08:58 +0000365 Spawn(cmd, check_call=True, log=True)
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800366 with file_utils.TempDirectory() as tmp_dir:
367 version_path = os.path.join(tmp_dir, VERSION_PATH)
368 os.makedirs(os.path.dirname(version_path))
369 file_utils.WriteFile(version_path, complete_version)
You-Cheng Syuc0682732017-05-10 12:17:05 +0800370 Spawn([cmd[0], '--lsm', version_path, '--append', tmp_dir, output_path],
371 check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800372 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800373 ' Factory toolkit generated at %s.\n'
374 '\n'
375 ' To install factory toolkit on a live device running a test image,\n'
376 ' copy this to the device and execute it as root.\n'
377 '\n'
378 ' Alternatively, the factory toolkit can be used to patch a test\n'
379 ' image. For more information, run:\n'
380 ' %s --help\n'
381 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800382
383
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800384def ExtractOverlord(src_root, output_dir):
385 output_dir = os.path.join(output_dir, 'overlord')
386 try:
387 os.makedirs(output_dir)
388 except OSError as e:
389 print str(e)
390 return
391
392 # Copy overlord binary and resource files
393 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
394 os.path.join(output_dir, 'overlordd'))
395 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
396 os.path.join(output_dir, 'app'))
397
398 # Give overlordd execution permission
399 os.chmod(os.path.join(output_dir, 'overlordd'), 0755)
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800400 print "Extracted overlord under '%s'" % output_dir
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800401
402
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800403def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800404 import logging
405 logging.basicConfig(level=logging.INFO)
406
407 # In order to determine which usage message to show, first determine
408 # whether we're in the self-extracting archive. Do this first
409 # because we need it to even parse the arguments.
410 if '--in-exe' in sys.argv:
411 sys.argv = [x for x in sys.argv if x != '--in-exe']
412 in_archive = True
413 else:
414 in_archive = False
415
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800416 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800417 description=HELP_HEADER + HELP_HEADER_ADVANCED,
418 usage=('install_factory_toolkit.run -- [options]' if in_archive
419 else None),
420 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800421 parser.add_argument(
422 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800423 help='A test image or the mount point of the stateful partition. '
424 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800425 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800426 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800427 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800428 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800429 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800430 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800431 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800432 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800433 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800434 help='Repack from previously unpacked toolkit')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800435 parser.add_argument('--version', metavar='VERSION',
436 help='String to write into TOOLKIT_VERSION when packing')
Vic Yang7039f422014-07-07 15:38:13 -0700437
Peter Ammon948b7172014-07-15 12:43:06 -0700438 parser.add_argument('--enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800439 action='store_true',
440 help='Run goofy in presenter mode on startup')
Peter Ammon948b7172014-07-15 12:43:06 -0700441 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800442 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800443 parser.set_defaults(enable_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700444
Vic Yang70fdae92015-02-17 19:21:08 -0800445 parser.add_argument('--non-cros', dest='non_cros',
446 action='store_true',
447 help='Install on non-ChromeOS host.')
448
Vic Yang7039f422014-07-07 15:38:13 -0700449 parser.add_argument('--enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800450 action='store_true',
451 help='Run goofy in device mode on startup')
Vic Yang7039f422014-07-07 15:38:13 -0700452 parser.add_argument('--no-enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800453 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800454 parser.set_defaults(enable_device=False)
Vic Yang423c2722014-09-24 16:05:06 +0800455
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800456 parser.add_argument('--device-id', dest='device_id', type=str, default=None,
457 help='Set device ID for this device')
458
Rong Chang6d65fad2014-08-22 16:00:12 +0800459 parser.add_argument('--exe-path', dest='exe_path',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800460 nargs='?', default=None,
461 help='Current self-extracting archive pathname')
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800462 parser.add_argument('--extract-overlord', dest='extract_overlord',
463 metavar='OUTPUT_DIR', type=str, default=None,
464 help='Extract overlord from the toolkit')
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800465 parser.add_argument('--install-dirs', nargs='+', default=None,
466 help=('Install only the specified directories under '
467 'factory folder. Can be used with --apps to '
468 'enable / disable some apps. Defaults to install '
469 'all folders.'))
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800470 parser.add_argument('--apps', type=lambda s: s.split(','), default=None,
471 help=('Enable or disable some apps under '
472 'factory/init/main.d/. Use prefix "-" to disable, '
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800473 'prefix "+" to enable, and use "," to separate. '
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800474 'For example: --apps="-goofy,+whale_servo"'))
Vic Yang7039f422014-07-07 15:38:13 -0700475
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800476 args = parser.parse_args()
477
Peter Shihad166772017-05-31 11:36:17 +0800478 src_root = paths.FACTORY_DIR
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800479 for _ in xrange(3):
480 src_root = os.path.dirname(src_root)
481
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800482 if args.extract_overlord is not None:
483 ExtractOverlord(src_root, args.extract_overlord)
484 return
485
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800486 # --pack-into may be called directly so this must be done before changing
487 # working directory to OLDPWD.
488 if args.pack_into and args.repack is None:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800489 PackFactoryToolkit(src_root, args.pack_into, args.version,
490 args.enable_device, args.enable_presenter)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800491 return
492
Jon Salz4f3ade52014-02-20 17:55:09 +0800493 if not in_archive:
494 # If you're not in the self-extracting archive, you're not allowed to
495 # do anything except the above --pack-into call.
496 parser.error('Not running from install_factory_toolkit.run; '
497 'only --pack-into (without --repack) is allowed')
498
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800499 # Change to original working directory in case the user specifies
500 # a relative path.
501 # TODO: Use USER_PWD instead when makeself is upgraded
502 os.chdir(os.environ['OLDPWD'])
503
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800504 if args.repack:
505 if args.pack_into is None:
506 parser.error('Must specify --pack-into when using --repack.')
Youcheng Syuac391772017-04-20 09:08:58 +0000507 Spawn([os.path.join(args.repack, INSTALLER_PATH),
508 '--pack-into', args.pack_into], check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800509 return
510
511 if args.build_info:
512 PrintBuildInfo(src_root)
513 return
514
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800515 if not os.path.exists(args.dest):
516 parser.error('Destination %s does not exist!' % args.dest)
517
518 patch_test_image = os.path.isfile(args.dest)
519
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800520 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800521 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800522
Hung-Te Lin56b18402015-01-16 14:52:30 +0800523 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800524 src=src_root, dest=dest, no_enable=args.no_enable,
525 enable_presenter=args.enable_presenter,
526 enable_device=args.enable_device, non_cros=args.non_cros,
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800527 device_id=args.device_id,
528 apps=args.apps)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800529
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800530 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800531
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800532 if not args.yes:
533 answer = raw_input('*** Continue? [y/N] ')
534 if not answer or answer[0] not in 'yY':
535 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800536
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800537 if args.install_dirs:
538 installer.InstallFactorySubDir(args.install_dirs)
539 else:
540 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800541
542if __name__ == '__main__':
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800543 # makself interprets "LICENSE" environment variable string as license text and
Hung-Te Lin5a2a1c42016-11-10 12:43:40 +0800544 # will prompt user to accept before installation. For factory toolkit, we
545 # don't want any user interaction in installation and the license is already
546 # covered by ebuild or download platform like CPFE.
547 os.putenv('LICENSE', '')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800548 main()