blob: 15f1ec704ad8f0c183de45390b7f7a3a2f4505d2 [file] [log] [blame]
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +08001#!/usr/bin/python -Bu
2#
Hung-Te Lin1990b742017-08-09 17:34:57 +08003# Copyright 2014 The Chromium OS Authors. All rights reserved.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +08004# 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-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 Syu53f4a0c2017-04-20 17:46:12 +080028from cros.factory.utils import file_utils
Youcheng Syuac391772017-04-20 09:08:58 +000029from cros.factory.utils.process_utils import Spawn
Peter Shihfe221582017-06-15 13:40:53 +080030from cros.factory.utils import sys_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'
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +080034VERSION_PATH = 'usr/local/factory/TOOLKIT_VERSION'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080035
Jon Salz4f3ade52014-02-20 17:55:09 +080036# Short and sweet help header for the executable generated by makeself.
37HELP_HEADER = """
38Installs the factory toolkit, transforming a test image into a factory test
39image. You can:
40
41- Install the factory toolkit on a CrOS device that is running a test
42 image. To do this, copy install_factory_toolkit.run to the device and
43 run it. The factory tests will then come up on the next boot.
44
45 rsync -a install_factory_toolkit.run crosdevice:/tmp
46 ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
47
48- Modify a test image, turning it into a factory test image. When you
49 use the image on a device, the factory tests will come up.
50
51 install_factory_toolkit.run chromiumos_test_image.bin
52"""
53
54HELP_HEADER_ADVANCED = """
55- (advanced) Modify a mounted stateful partition, turning it into a factory
56 test image. This is equivalent to the previous command:
57
58 mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
59 install_factory_toolkit.run /mnt/stateful
60 umount /mnt/stateful
61
62- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
63
64 # Unpack but don't actually install
65 install_factory_toolkit.run --target /tmp/toolkit --noexec
66 # Edit some files in /tmp/toolkit
67 emacs /tmp/toolkit/whatever
68 # Repack
69 install_factory_toolkit.run -- --repack /tmp/toolkit \\
70 --pack-into /path/to/new/install_factory_toolkit.run
71"""
72
73# The makeself-generated header comes next. This is a little confusing,
74# so explain.
75HELP_HEADER_MAKESELF = """
76For complete usage information and advanced operations, run
77"install_factory_toolkit.run -- --help" (note the extra "--").
78
79Following is the help message from makeself, which was used to create
80this self-extracting archive.
81
82-----
83"""
84
Vic Yangdb1e20e2014-10-05 12:10:33 +080085SERVER_FILE_MASK = [
86 # Exclude Umpire server but keep Umpire client
87 '--include', 'py/umpire/__init__.*',
88 '--include', 'py/umpire/common.*',
89 '--include', 'py/umpire/client',
90 '--include', 'py/umpire/client/**',
91 '--exclude', 'py/umpire/**',
Peter Shihac904782017-06-14 15:24:09 +080092 '--exclude', 'bin/umpire',
93 '--exclude', 'bin/umpired',
Vic Yangdb1e20e2014-10-05 12:10:33 +080094]
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.
Peter Shih2f1f8c42017-06-15 15:05:55 +0800107 apps: The list of apps to enable/disable under factory/init/main.d/.
108 active_test_list: The id of active test list for Goofy.
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,
Peter Shihfe221582017-06-15 13:40:53 +0800115 enable_device, non_cros=False, system_root='/',
Peter Shih2f1f8c42017-06-15 15:05:55 +0800116 apps=None, active_test_list=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-Han Chene6fe3872016-06-30 14:29:06 +0800162 self._apps = apps
Peter Shih2f1f8c42017-06-15 15:05:55 +0800163 self._active_test_list = active_test_list
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
Peter Shih2f1f8c42017-06-15 15:05:55 +0800207 def _SetActiveTestList(self):
208 """Set the active test list for Goofy."""
209 if self._active_test_list is not None:
210 file_utils.WriteFile(
211 os.path.join(self._usr_local_dest, 'factory', 'py', 'test',
212 'test_lists', 'ACTIVE'), self._active_test_list)
213
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800214 def _EnableApp(self, app, enabled):
215 """Enable / disable @app.
216
217 In factory/init/startup, a main app is considered disabled if and only:
218 1. file "factory/init/main.d/disable-@app" exists OR
219 2. file "factory/init/main.d/enable-@app" doesn't exist AND
220 file "factory/init/main.d/@app.sh" is not executable.
221
222 Therefore, we enable an app by removing file "disable-@app" and creating
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800223 file "enable-@app", and vice versa.
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800224 """
225 app_enable = os.path.join(self._usr_local_dest,
226 'factory', 'init', 'main.d', 'enable-' + app)
227 app_disable = os.path.join(self._usr_local_dest,
228 'factory', 'init', 'main.d', 'disable-' + app)
229 if enabled:
230 print '*** Enabling {app} ***'.format(app=app)
Youcheng Syuac391772017-04-20 09:08:58 +0000231 Spawn(['rm', '-f', app_disable], sudo=self._sudo, log=True,
232 check_call=True)
233 Spawn(['touch', app_enable], sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800234 else:
235 print '*** Disabling {app} ***'.format(app=app)
Youcheng Syuac391772017-04-20 09:08:58 +0000236 Spawn(['touch', app_disable], sudo=self._sudo, log=True, check_call=True)
237 Spawn(['rm', '-f', app_enable], sudo=self._sudo, log=True,
238 check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800239
240 def _EnableApps(self):
241 if not self._apps:
242 return
243
244 app_list = []
245 for app in self._apps:
246 if app[0] == '+':
247 app_list.append((app[1:], True))
248 elif app[0] == '-':
249 app_list.append((app[1:], False))
250 else:
251 raise ValueError(
252 'Use +{app} to enable and -{app} to disable'.format(app=app))
253
254 for app, enabled in app_list:
255 self._EnableApp(app, enabled)
256
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800257 def InstallFactorySubDir(self, sub_dirs):
258 """Install only the specified directories under factory folder."""
259
260 def _InstallOneSubDir(sub_dir_name):
261 sub_dir_dest = os.path.join(self._usr_local_dest, 'factory', sub_dir_name)
262 sub_dir_src = os.path.join(self._src, 'usr', 'local', 'factory',
263 sub_dir_name)
264 try:
Youcheng Syuac391772017-04-20 09:08:58 +0000265 Spawn(['mkdir', '-p', sub_dir_dest], sudo=True, log=True,
266 check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800267 except OSError as e:
268 print str(e)
269 return
270
Youcheng Syuac391772017-04-20 09:08:58 +0000271 Spawn(['rsync', '-a', '--force', '-v',
272 sub_dir_src + '/', sub_dir_dest],
273 sudo=self._sudo, log=True, check_call=True)
274 Spawn(['chown', '-R', 'root', sub_dir_dest],
275 sudo=self._sudo, log=True, check_call=True)
276 Spawn(['chmod', '-R', 'go+rX', sub_dir_dest],
277 sudo=self._sudo, log=True, check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800278
279 for sub_dir_name in sub_dirs:
280 _InstallOneSubDir(sub_dir_name)
281
282 self._SetTagFile('factory', self._tag_file, not self._no_enable)
283 self._EnableApps()
284
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800285 def Install(self):
286 print '*** Installing factory toolkit...'
Mao Huangf75aa3e2016-11-24 11:44:17 +0800287
288 # --no-owner and --no-group will set owner/group to the current user/group
289 # running the command. This is important if we're running with sudo, so
290 # the destination will be changed to root/root instead of the user/group
291 # before sudo (doesn't matter if sudo is not present). --force is also
292 # necessary to allow goofy directory from prior toolkit installations to
293 # be overwritten by the goofy symlink.
294 print '*** %s -> %s' % (self._usr_local_src, self._usr_local_dest)
Youcheng Syuac391772017-04-20 09:08:58 +0000295 Spawn(['rsync', '-a', '--no-owner', '--no-group', '--chmod=ugo+rX',
296 '--force'] + SERVER_FILE_MASK + [self._usr_local_src + '/',
297 self._usr_local_dest],
298 sudo=self._sudo, log=True, check_output=True, cwd=self._usr_local_src)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800299
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800300 print '*** Ensure SSH keys file permission...'
301 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
302 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
303 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
304 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
305 if ssh_private_keys:
Youcheng Syuac391772017-04-20 09:08:58 +0000306 Spawn(['chmod', '600'] + ssh_private_keys, log=True, check_call=True,
307 sudo=self._sudo)
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800308
Jon Salz25590302014-07-11 16:07:20 +0800309 print '*** Installing symlinks...'
310 install_symlinks.InstallSymlinks(
311 '../factory/bin',
312 os.path.join(self._usr_local_dest, 'bin'),
313 install_symlinks.MODE_FULL,
314 sudo=self._sudo)
315
Vic Yang7039f422014-07-07 15:38:13 -0700316 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700317 self._SetTagFile('presenter', self._presenter_tag_file,
318 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700319 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800320
Peter Shih2f1f8c42017-06-15 15:05:55 +0800321 self._SetActiveTestList()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800322 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800323
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800324 print '*** Installation completed.'
325
326
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800327@contextmanager
328def DummyContext(arg):
329 """A context manager that simply yields its argument."""
330 yield arg
331
332
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800333def PrintBuildInfo(src_root):
334 """Print build information."""
335 info_file = os.path.join(src_root, 'REPO_STATUS')
336 if not os.path.exists(info_file):
337 raise OSError('Build info file not found!')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800338 print file_utils.ReadFile(info_file)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800339
340
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800341def PackFactoryToolkit(src_root, output_path, initial_version,
342 enable_device, enable_presenter):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800343 """Packs the files containing this script into a factory toolkit."""
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800344 if initial_version is None:
345 complete_version = '%s repacked by %s@%s at %s\n' % (
346 file_utils.ReadFile(os.path.join(src_root, VERSION_PATH)),
347 getpass.getuser(), os.uname()[1], time.strftime('%Y-%m-%d %H:%M:%S'))
348 initial_version = complete_version.splitlines()[0]
349 else:
350 complete_version = initial_version + '\n'
351 modified_times = len(complete_version.splitlines()) - 1
352 if modified_times == 0:
353 modified_msg = ''
354 else:
355 modified_msg = ' (modified %d times)' % modified_times
Jon Salz4f3ade52014-02-20 17:55:09 +0800356 with tempfile.NamedTemporaryFile() as help_header:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800357 help_header.write(initial_version + '\n' +
358 HELP_HEADER + HELP_HEADER_MAKESELF)
Jon Salz4f3ade52014-02-20 17:55:09 +0800359 help_header.flush()
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800360 cmd = [os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
Jon Salz4f3ade52014-02-20 17:55:09 +0800361 '--help-header', help_header.name,
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800362 src_root, output_path, initial_version + modified_msg,
363 INSTALLER_PATH, '--in-exe']
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800364 if not enable_device:
365 cmd.append('--no-enable-device')
366 if not enable_presenter:
367 cmd.append('--no-enable-presenter')
Youcheng Syuac391772017-04-20 09:08:58 +0000368 Spawn(cmd, check_call=True, log=True)
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800369 with file_utils.TempDirectory() as tmp_dir:
370 version_path = os.path.join(tmp_dir, VERSION_PATH)
371 os.makedirs(os.path.dirname(version_path))
372 file_utils.WriteFile(version_path, complete_version)
You-Cheng Syuc0682732017-05-10 12:17:05 +0800373 Spawn([cmd[0], '--lsm', version_path, '--append', tmp_dir, output_path],
374 check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800375 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800376 ' Factory toolkit generated at %s.\n'
377 '\n'
378 ' To install factory toolkit on a live device running a test image,\n'
379 ' copy this to the device and execute it as root.\n'
380 '\n'
381 ' Alternatively, the factory toolkit can be used to patch a test\n'
382 ' image. For more information, run:\n'
383 ' %s --help\n'
384 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800385
386
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800387def ExtractOverlord(src_root, output_dir):
388 output_dir = os.path.join(output_dir, 'overlord')
389 try:
390 os.makedirs(output_dir)
391 except OSError as e:
392 print str(e)
393 return
394
395 # Copy overlord binary and resource files
396 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
397 os.path.join(output_dir, 'overlordd'))
398 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
399 os.path.join(output_dir, 'app'))
400
401 # Give overlordd execution permission
402 os.chmod(os.path.join(output_dir, 'overlordd'), 0755)
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800403 print "Extracted overlord under '%s'" % output_dir
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800404
405
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800406def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800407 import logging
408 logging.basicConfig(level=logging.INFO)
409
410 # In order to determine which usage message to show, first determine
411 # whether we're in the self-extracting archive. Do this first
412 # because we need it to even parse the arguments.
413 if '--in-exe' in sys.argv:
414 sys.argv = [x for x in sys.argv if x != '--in-exe']
415 in_archive = True
416 else:
417 in_archive = False
418
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800419 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800420 description=HELP_HEADER + HELP_HEADER_ADVANCED,
421 usage=('install_factory_toolkit.run -- [options]' if in_archive
422 else None),
423 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800424 parser.add_argument(
425 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800426 help='A test image or the mount point of the stateful partition. '
427 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800428 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800429 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800430 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800431 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800432 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800433 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800434 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800435 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800436 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800437 help='Repack from previously unpacked toolkit')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800438 parser.add_argument('--version', metavar='VERSION',
439 help='String to write into TOOLKIT_VERSION when packing')
Vic Yang7039f422014-07-07 15:38:13 -0700440
Peter Ammon948b7172014-07-15 12:43:06 -0700441 parser.add_argument('--enable-presenter', dest='enable_presenter',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800442 action='store_true',
443 help='Run goofy in presenter mode on startup')
Peter Ammon948b7172014-07-15 12:43:06 -0700444 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
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_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700447
Vic Yang70fdae92015-02-17 19:21:08 -0800448 parser.add_argument('--non-cros', dest='non_cros',
449 action='store_true',
450 help='Install on non-ChromeOS host.')
451
Vic Yang7039f422014-07-07 15:38:13 -0700452 parser.add_argument('--enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800453 action='store_true',
454 help='Run goofy in device mode on startup')
Vic Yang7039f422014-07-07 15:38:13 -0700455 parser.add_argument('--no-enable-device', dest='enable_device',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800456 action='store_false', help=argparse.SUPPRESS)
Hung-Te Lin0cba9d52016-03-22 11:45:35 +0800457 parser.set_defaults(enable_device=False)
Vic Yang423c2722014-09-24 16:05:06 +0800458
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"'))
Peter Shih2f1f8c42017-06-15 15:05:55 +0800475 parser.add_argument('--active-test-list', dest='active_test_list',
476 default=None,
477 help='Set the id of active test list for Goofy.')
Vic Yang7039f422014-07-07 15:38:13 -0700478
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800479 args = parser.parse_args()
480
Peter Shihad166772017-05-31 11:36:17 +0800481 src_root = paths.FACTORY_DIR
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800482 for _ in xrange(3):
483 src_root = os.path.dirname(src_root)
484
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800485 if args.extract_overlord is not None:
486 ExtractOverlord(src_root, args.extract_overlord)
487 return
488
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800489 # --pack-into may be called directly so this must be done before changing
490 # working directory to OLDPWD.
491 if args.pack_into and args.repack is None:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800492 PackFactoryToolkit(src_root, args.pack_into, args.version,
493 args.enable_device, args.enable_presenter)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800494 return
495
Jon Salz4f3ade52014-02-20 17:55:09 +0800496 if not in_archive:
497 # If you're not in the self-extracting archive, you're not allowed to
498 # do anything except the above --pack-into call.
499 parser.error('Not running from install_factory_toolkit.run; '
500 'only --pack-into (without --repack) is allowed')
501
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800502 # Change to original working directory in case the user specifies
503 # a relative path.
504 # TODO: Use USER_PWD instead when makeself is upgraded
505 os.chdir(os.environ['OLDPWD'])
506
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800507 if args.repack:
508 if args.pack_into is None:
509 parser.error('Must specify --pack-into when using --repack.')
Youcheng Syuac391772017-04-20 09:08:58 +0000510 Spawn([os.path.join(args.repack, INSTALLER_PATH),
511 '--pack-into', args.pack_into], check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800512 return
513
514 if args.build_info:
515 PrintBuildInfo(src_root)
516 return
517
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800518 if not os.path.exists(args.dest):
519 parser.error('Destination %s does not exist!' % args.dest)
520
521 patch_test_image = os.path.isfile(args.dest)
522
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800523 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800524 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800525
Hung-Te Lin56b18402015-01-16 14:52:30 +0800526 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800527 src=src_root, dest=dest, no_enable=args.no_enable,
528 enable_presenter=args.enable_presenter,
529 enable_device=args.enable_device, non_cros=args.non_cros,
Peter Shih2f1f8c42017-06-15 15:05:55 +0800530 apps=args.apps, active_test_list=args.active_test_list)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800531
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800532 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800533
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800534 if not args.yes:
535 answer = raw_input('*** Continue? [y/N] ')
536 if not answer or answer[0] not in 'yY':
537 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800538
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800539 if args.install_dirs:
540 installer.InstallFactorySubDir(args.install_dirs)
541 else:
542 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800543
544if __name__ == '__main__':
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800545 # makself interprets "LICENSE" environment variable string as license text and
Hung-Te Lin5a2a1c42016-11-10 12:43:40 +0800546 # will prompt user to accept before installation. For factory toolkit, we
547 # don't want any user interaction in installation and the license is already
548 # covered by ebuild or download platform like CPFE.
549 os.putenv('LICENSE', '')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800550 main()