blob: 77c4c33ec47b61da166e784b89f27bb1d48a95d3 [file] [log] [blame]
You-Cheng Syud5692942018-01-04 14:40:59 +08001#!/usr/bin/env python
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +08002#
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
Cheng-Han Yang3f746e82019-04-10 14:33:29 +080027from cros.factory.test.test_lists import test_list_common
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
Yong Hong89938e62018-10-26 11:59:21 +080030from cros.factory.utils import json_utils
Youcheng Syuac391772017-04-20 09:08:58 +000031from cros.factory.utils.process_utils import Spawn
Peter Shihfe221582017-06-15 13:40:53 +080032from cros.factory.utils import sys_utils
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080033
34
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080035INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +080036VERSION_PATH = 'usr/local/factory/TOOLKIT_VERSION'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080037
Jon Salz4f3ade52014-02-20 17:55:09 +080038# Short and sweet help header for the executable generated by makeself.
39HELP_HEADER = """
40Installs the factory toolkit, transforming a test image into a factory test
41image. You can:
42
43- Install the factory toolkit on a CrOS device that is running a test
44 image. To do this, copy install_factory_toolkit.run to the device and
45 run it. The factory tests will then come up on the next boot.
46
47 rsync -a install_factory_toolkit.run crosdevice:/tmp
48 ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
49
50- Modify a test image, turning it into a factory test image. When you
51 use the image on a device, the factory tests will come up.
52
53 install_factory_toolkit.run chromiumos_test_image.bin
54"""
55
56HELP_HEADER_ADVANCED = """
57- (advanced) Modify a mounted stateful partition, turning it into a factory
58 test image. This is equivalent to the previous command:
59
60 mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
61 install_factory_toolkit.run /mnt/stateful
62 umount /mnt/stateful
63
64- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
65
66 # Unpack but don't actually install
67 install_factory_toolkit.run --target /tmp/toolkit --noexec
68 # Edit some files in /tmp/toolkit
69 emacs /tmp/toolkit/whatever
70 # Repack
71 install_factory_toolkit.run -- --repack /tmp/toolkit \\
72 --pack-into /path/to/new/install_factory_toolkit.run
73"""
74
75# The makeself-generated header comes next. This is a little confusing,
76# so explain.
77HELP_HEADER_MAKESELF = """
78For complete usage information and advanced operations, run
79"install_factory_toolkit.run -- --help" (note the extra "--").
80
81Following is the help message from makeself, which was used to create
82this self-extracting archive.
83
84-----
85"""
86
Vic Yangdb1e20e2014-10-05 12:10:33 +080087SERVER_FILE_MASK = [
88 # Exclude Umpire server but keep Umpire client
89 '--include', 'py/umpire/__init__.*',
90 '--include', 'py/umpire/common.*',
91 '--include', 'py/umpire/client',
92 '--include', 'py/umpire/client/**',
93 '--exclude', 'py/umpire/**',
Peter Shihac904782017-06-14 15:24:09 +080094 '--exclude', 'bin/umpire',
95 '--exclude', 'bin/umpired',
Vic Yangdb1e20e2014-10-05 12:10:33 +080096]
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.
Peter Shih2f1f8c42017-06-15 15:05:55 +0800109 apps: The list of apps to enable/disable under factory/init/main.d/.
110 active_test_list: The id of active test list for Goofy.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800111 """
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800112
Jon Salzb7e44262014-05-07 15:53:37 +0800113 # Whether to sudo when rsyncing; set to False for testing.
114 _sudo = True
115
Hung-Te Linfc162e62017-09-21 00:45:04 +0800116 def __init__(self, src, dest, no_enable, non_cros=False, system_root='/',
Peter Shih2f1f8c42017-06-15 15:05:55 +0800117 apps=None, active_test_list=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800118 self._src = src
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800119 self._system_root = system_root
120 if dest == self._system_root:
121 self._usr_local_dest = os.path.join(dest, 'usr', 'local')
Jon Salz4f3ade52014-02-20 17:55:09 +0800122
123 # Make sure we're on a CrOS device.
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800124 if not non_cros and not sys_utils.InCrOSDevice():
Jon Salz4f3ade52014-02-20 17:55:09 +0800125 sys.stderr.write(
Vic Yang196d5242014-08-05 13:51:35 +0800126 "ERROR: You're not on a CrOS device (for more details, please\n"
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800127 'check sys_utils.py:InCrOSDevice), so you must specify a test\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800128 'image or a mounted stateful partition on which to install the\n'
129 'factory toolkit. Please run\n'
130 '\n'
131 ' install_factory_toolkit.run -- --help\n'
132 '\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800133 'for help.\n'
134 '\n'
Hung-Te Linfc162e62017-09-21 00:45:04 +0800135 'If you want to install on a non-CrOS host,\n'
Vic Yang70fdae92015-02-17 19:21:08 -0800136 'please run\n'
137 '\n'
Hung-Te Linfc162e62017-09-21 00:45:04 +0800138 ' install_factory_toolkit.run -- --non-cros \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
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800155 self._apps = apps
Peter Shih2f1f8c42017-06-15 15:05:55 +0800156 self._active_test_list = active_test_list
Vic Yang7039f422014-07-07 15:38:13 -0700157
Hung-Te Lin4aeabe62016-10-21 15:45:20 +0800158 if not os.path.exists(self._usr_local_src):
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800159 raise Exception(
160 'This installer must be run from within the factory toolkit!')
161
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800162 def WarningMessage(self, target_test_image=None):
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800163 ret = file_utils.ReadFile(os.path.join(self._src, VERSION_PATH))
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800164 if target_test_image:
Jon Salz4f3ade52014-02-20 17:55:09 +0800165 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800166 '\n'
167 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800168 '*** You are about to patch the factory toolkit into:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800169 '*** %s\n'
170 '***' % target_test_image)
171 else:
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 install the factory toolkit to:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800176 '*** %s\n'
177 '***' % self._dest)
178 if self._dest == self._system_root:
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800179 if self._no_enable:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800180 ret += ('\n*** Factory tests will be disabled after this process is '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800181 'done, but\n*** you can enable them by creating the factory '
182 'enabled tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800183 else:
Hung-Te Lin7b596ff2015-01-16 20:19:15 +0800184 ret += ('\n*** After this process is done, your device will start '
Hung-Te Lin56b18402015-01-16 14:52:30 +0800185 'factory\n*** tests on the next reboot.\n***\n*** Factory '
186 'tests can be disabled by deleting the factory enabled\n*** '
187 'tag:\n*** %s\n***' % self._tag_file)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800188 return ret
189
Vic Yang7039f422014-07-07 15:38:13 -0700190 def _SetTagFile(self, name, path, enabled):
191 """Install or remove a tag file."""
192 if enabled:
193 print '*** Installing %s enabled tag...' % name
Youcheng Syuac391772017-04-20 09:08:58 +0000194 Spawn(['touch', path], sudo=True, log=True, check_call=True)
195 Spawn(['chmod', 'go+r', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700196 else:
197 print '*** Removing %s enabled tag...' % name
Youcheng Syuac391772017-04-20 09:08:58 +0000198 Spawn(['rm', '-f', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700199
Peter Shih2f1f8c42017-06-15 15:05:55 +0800200 def _SetActiveTestList(self):
201 """Set the active test list for Goofy."""
202 if self._active_test_list is not None:
Cheng-Han Yang5242a1f2019-01-16 20:00:05 +0800203 path = os.path.join(self._usr_local_dest, 'factory',
Cheng-Han Yang3f746e82019-04-10 14:33:29 +0800204 test_list_common.ACTIVE_TEST_LIST_CONFIG_RELPATH)
205 json_utils.DumpFile(
206 path,
207 test_list_common.GenerateActiveTestListConfig(self._active_test_list))
Peter Shih2f1f8c42017-06-15 15:05:55 +0800208
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800209 def _EnableApp(self, app, enabled):
210 """Enable / disable @app.
211
212 In factory/init/startup, a main app is considered disabled if and only:
213 1. file "factory/init/main.d/disable-@app" exists OR
214 2. file "factory/init/main.d/enable-@app" doesn't exist AND
215 file "factory/init/main.d/@app.sh" is not executable.
216
217 Therefore, we enable an app by removing file "disable-@app" and creating
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800218 file "enable-@app", and vice versa.
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800219 """
220 app_enable = os.path.join(self._usr_local_dest,
221 'factory', 'init', 'main.d', 'enable-' + app)
222 app_disable = os.path.join(self._usr_local_dest,
223 'factory', 'init', 'main.d', 'disable-' + app)
224 if enabled:
225 print '*** Enabling {app} ***'.format(app=app)
Youcheng Syuac391772017-04-20 09:08:58 +0000226 Spawn(['rm', '-f', app_disable], sudo=self._sudo, log=True,
227 check_call=True)
228 Spawn(['touch', app_enable], sudo=self._sudo, log=True, check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800229 else:
230 print '*** Disabling {app} ***'.format(app=app)
Youcheng Syuac391772017-04-20 09:08:58 +0000231 Spawn(['touch', app_disable], sudo=self._sudo, log=True, check_call=True)
232 Spawn(['rm', '-f', app_enable], sudo=self._sudo, log=True,
233 check_call=True)
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800234
235 def _EnableApps(self):
236 if not self._apps:
237 return
238
239 app_list = []
240 for app in self._apps:
241 if app[0] == '+':
242 app_list.append((app[1:], True))
243 elif app[0] == '-':
244 app_list.append((app[1:], False))
245 else:
246 raise ValueError(
247 'Use +{app} to enable and -{app} to disable'.format(app=app))
248
249 for app, enabled in app_list:
250 self._EnableApp(app, enabled)
251
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800252 def InstallFactorySubDir(self, sub_dirs):
253 """Install only the specified directories under factory folder."""
254
255 def _InstallOneSubDir(sub_dir_name):
256 sub_dir_dest = os.path.join(self._usr_local_dest, 'factory', sub_dir_name)
257 sub_dir_src = os.path.join(self._src, 'usr', 'local', 'factory',
258 sub_dir_name)
259 try:
Youcheng Syuac391772017-04-20 09:08:58 +0000260 Spawn(['mkdir', '-p', sub_dir_dest], sudo=True, log=True,
261 check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800262 except OSError as e:
263 print str(e)
264 return
265
Youcheng Syuac391772017-04-20 09:08:58 +0000266 Spawn(['rsync', '-a', '--force', '-v',
267 sub_dir_src + '/', sub_dir_dest],
268 sudo=self._sudo, log=True, check_call=True)
269 Spawn(['chown', '-R', 'root', sub_dir_dest],
270 sudo=self._sudo, log=True, check_call=True)
271 Spawn(['chmod', '-R', 'go+rX', sub_dir_dest],
272 sudo=self._sudo, log=True, check_call=True)
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800273
274 for sub_dir_name in sub_dirs:
275 _InstallOneSubDir(sub_dir_name)
276
277 self._SetTagFile('factory', self._tag_file, not self._no_enable)
278 self._EnableApps()
279
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800280 def Install(self):
281 print '*** Installing factory toolkit...'
Mao Huangf75aa3e2016-11-24 11:44:17 +0800282
283 # --no-owner and --no-group will set owner/group to the current user/group
284 # running the command. This is important if we're running with sudo, so
285 # the destination will be changed to root/root instead of the user/group
286 # before sudo (doesn't matter if sudo is not present). --force is also
287 # necessary to allow goofy directory from prior toolkit installations to
288 # be overwritten by the goofy symlink.
289 print '*** %s -> %s' % (self._usr_local_src, self._usr_local_dest)
Youcheng Syuac391772017-04-20 09:08:58 +0000290 Spawn(['rsync', '-a', '--no-owner', '--no-group', '--chmod=ugo+rX',
291 '--force'] + SERVER_FILE_MASK + [self._usr_local_src + '/',
292 self._usr_local_dest],
293 sudo=self._sudo, log=True, check_output=True, cwd=self._usr_local_src)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800294
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800295 print '*** Ensure SSH keys file permission...'
296 sshkeys_dir = os.path.join(self._usr_local_dest, 'factory/misc/sshkeys')
297 sshkeys = glob.glob(os.path.join(sshkeys_dir, '*'))
298 ssh_public_keys = glob.glob(os.path.join(sshkeys_dir, '*.pub'))
299 ssh_private_keys = list(set(sshkeys) - set(ssh_public_keys))
300 if ssh_private_keys:
Youcheng Syuac391772017-04-20 09:08:58 +0000301 Spawn(['chmod', '600'] + ssh_private_keys, log=True, check_call=True,
302 sudo=self._sudo)
Hung-Te Lineb7632b2016-07-29 15:38:34 +0800303
Jon Salz25590302014-07-11 16:07:20 +0800304 print '*** Installing symlinks...'
305 install_symlinks.InstallSymlinks(
306 '../factory/bin',
307 os.path.join(self._usr_local_dest, 'bin'),
308 install_symlinks.MODE_FULL,
309 sudo=self._sudo)
310
Vic Yang7039f422014-07-07 15:38:13 -0700311 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800312
Peter Shih2f1f8c42017-06-15 15:05:55 +0800313 self._SetActiveTestList()
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800314 self._EnableApps()
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800315
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800316 print '*** Installation completed.'
317
318
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800319@contextmanager
320def DummyContext(arg):
321 """A context manager that simply yields its argument."""
322 yield arg
323
324
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800325def PrintBuildInfo(src_root):
326 """Print build information."""
327 info_file = os.path.join(src_root, 'REPO_STATUS')
328 if not os.path.exists(info_file):
329 raise OSError('Build info file not found!')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800330 print file_utils.ReadFile(info_file)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800331
332
Hung-Te Linfc162e62017-09-21 00:45:04 +0800333def PackFactoryToolkit(src_root, output_path, initial_version):
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800334 """Packs the files containing this script into a factory toolkit."""
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800335 if initial_version is None:
336 complete_version = '%s repacked by %s@%s at %s\n' % (
337 file_utils.ReadFile(os.path.join(src_root, VERSION_PATH)),
338 getpass.getuser(), os.uname()[1], time.strftime('%Y-%m-%d %H:%M:%S'))
339 initial_version = complete_version.splitlines()[0]
340 else:
341 complete_version = initial_version + '\n'
342 modified_times = len(complete_version.splitlines()) - 1
343 if modified_times == 0:
344 modified_msg = ''
345 else:
346 modified_msg = ' (modified %d times)' % modified_times
Jon Salz4f3ade52014-02-20 17:55:09 +0800347 with tempfile.NamedTemporaryFile() as help_header:
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800348 help_header.write(initial_version + '\n' +
349 HELP_HEADER + HELP_HEADER_MAKESELF)
Jon Salz4f3ade52014-02-20 17:55:09 +0800350 help_header.flush()
Wei-Ning Huangb723d7f2014-11-17 17:26:32 +0800351 cmd = [os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
Jon Salz4f3ade52014-02-20 17:55:09 +0800352 '--help-header', help_header.name,
Meng-Huan Yuea9dd912019-08-07 17:45:17 +0800353 src_root, # archive_dir
354 output_path, # file_name
355 initial_version + modified_msg, # label
356 # startup script and args
357 # We have to explicitly execute python instead of directly execute
358 # INSTALLER_PATH because files under INSTALLER_PATH may not be
359 # executable.
360 'env', 'python', INSTALLER_PATH, '--in-exe']
Youcheng Syuac391772017-04-20 09:08:58 +0000361 Spawn(cmd, check_call=True, log=True)
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800362 with file_utils.TempDirectory() as tmp_dir:
363 version_path = os.path.join(tmp_dir, VERSION_PATH)
364 os.makedirs(os.path.dirname(version_path))
365 file_utils.WriteFile(version_path, complete_version)
You-Cheng Syuc0682732017-05-10 12:17:05 +0800366 Spawn([cmd[0], '--lsm', version_path, '--append', tmp_dir, output_path],
367 check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800368 print ('\n'
Hung-Te Lin56b18402015-01-16 14:52:30 +0800369 ' Factory toolkit generated at %s.\n'
370 '\n'
371 ' To install factory toolkit on a live device running a test image,\n'
372 ' copy this to the device and execute it as root.\n'
373 '\n'
374 ' Alternatively, the factory toolkit can be used to patch a test\n'
375 ' image. For more information, run:\n'
376 ' %s --help\n'
377 '\n' % (output_path, output_path))
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800378
379
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800380def ExtractOverlord(src_root, output_dir):
381 output_dir = os.path.join(output_dir, 'overlord')
382 try:
383 os.makedirs(output_dir)
384 except OSError as e:
385 print str(e)
386 return
387
388 # Copy overlord binary and resource files
389 shutil.copyfile(os.path.join(src_root, 'usr/bin/overlordd'),
390 os.path.join(output_dir, 'overlordd'))
391 shutil.copytree(os.path.join(src_root, 'usr/share/overlord/app'),
392 os.path.join(output_dir, 'app'))
393
394 # Give overlordd execution permission
Peter Shihe6afab32018-09-11 17:16:48 +0800395 os.chmod(os.path.join(output_dir, 'overlordd'), 0o755)
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800396 print "Extracted overlord under '%s'" % output_dir
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800397
398
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800399def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800400 import logging
401 logging.basicConfig(level=logging.INFO)
402
403 # In order to determine which usage message to show, first determine
404 # whether we're in the self-extracting archive. Do this first
405 # because we need it to even parse the arguments.
406 if '--in-exe' in sys.argv:
407 sys.argv = [x for x in sys.argv if x != '--in-exe']
408 in_archive = True
409 else:
410 in_archive = False
411
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800412 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800413 description=HELP_HEADER + HELP_HEADER_ADVANCED,
414 usage=('install_factory_toolkit.run -- [options]' if in_archive
415 else None),
416 formatter_class=argparse.RawDescriptionHelpFormatter)
Hung-Te Lin56b18402015-01-16 14:52:30 +0800417 parser.add_argument(
418 'dest', nargs='?', default='/',
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800419 help='A test image or the mount point of the stateful partition. '
420 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800421 parser.add_argument('--no-enable', '-n', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800422 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800423 parser.add_argument('--yes', '-y', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800424 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800425 parser.add_argument('--build-info', action='store_true',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800426 help='Print build information and exit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800427 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800428 help='Pack the files into a new factory toolkit')
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800429 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800430 help='Repack from previously unpacked toolkit')
You-Cheng Syu53f4a0c2017-04-20 17:46:12 +0800431 parser.add_argument('--version', metavar='VERSION',
432 help='String to write into TOOLKIT_VERSION when packing')
Vic Yang7039f422014-07-07 15:38:13 -0700433
Vic Yang70fdae92015-02-17 19:21:08 -0800434 parser.add_argument('--non-cros', dest='non_cros',
435 action='store_true',
436 help='Install on non-ChromeOS host.')
437
Vic Yang423c2722014-09-24 16:05:06 +0800438
Rong Chang6d65fad2014-08-22 16:00:12 +0800439 parser.add_argument('--exe-path', dest='exe_path',
Hung-Te Lin56b18402015-01-16 14:52:30 +0800440 nargs='?', default=None,
441 help='Current self-extracting archive pathname')
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800442 parser.add_argument('--extract-overlord', dest='extract_overlord',
443 metavar='OUTPUT_DIR', type=str, default=None,
444 help='Extract overlord from the toolkit')
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800445 parser.add_argument('--install-dirs', nargs='+', default=None,
446 help=('Install only the specified directories under '
447 'factory folder. Can be used with --apps to '
448 'enable / disable some apps. Defaults to install '
449 'all folders.'))
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800450 parser.add_argument('--apps', type=lambda s: s.split(','), default=None,
451 help=('Enable or disable some apps under '
452 'factory/init/main.d/. Use prefix "-" to disable, '
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800453 'prefix "+" to enable, and use "," to separate. '
Wei-Han Chene6fe3872016-06-30 14:29:06 +0800454 'For example: --apps="-goofy,+whale_servo"'))
Peter Shih2f1f8c42017-06-15 15:05:55 +0800455 parser.add_argument('--active-test-list', dest='active_test_list',
456 default=None,
457 help='Set the id of active test list for Goofy.')
Vic Yang7039f422014-07-07 15:38:13 -0700458
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800459 args = parser.parse_args()
460
Peter Shihad166772017-05-31 11:36:17 +0800461 src_root = paths.FACTORY_DIR
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800462 for _ in xrange(3):
463 src_root = os.path.dirname(src_root)
464
Wei-Ning Huang4855e792015-06-11 15:33:39 +0800465 if args.extract_overlord is not None:
466 ExtractOverlord(src_root, args.extract_overlord)
467 return
468
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800469 # --pack-into may be called directly so this must be done before changing
470 # working directory to OLDPWD.
471 if args.pack_into and args.repack is None:
Hung-Te Linfc162e62017-09-21 00:45:04 +0800472 PackFactoryToolkit(src_root, args.pack_into, args.version)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800473 return
474
Jon Salz4f3ade52014-02-20 17:55:09 +0800475 if not in_archive:
476 # If you're not in the self-extracting archive, you're not allowed to
477 # do anything except the above --pack-into call.
478 parser.error('Not running from install_factory_toolkit.run; '
479 'only --pack-into (without --repack) is allowed')
480
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800481 # Change to original working directory in case the user specifies
482 # a relative path.
483 # TODO: Use USER_PWD instead when makeself is upgraded
484 os.chdir(os.environ['OLDPWD'])
485
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800486 if args.repack:
487 if args.pack_into is None:
488 parser.error('Must specify --pack-into when using --repack.')
Youcheng Syuac391772017-04-20 09:08:58 +0000489 Spawn([os.path.join(args.repack, INSTALLER_PATH),
490 '--pack-into', args.pack_into], check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800491 return
492
493 if args.build_info:
494 PrintBuildInfo(src_root)
495 return
496
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800497 if not os.path.exists(args.dest):
498 parser.error('Destination %s does not exist!' % args.dest)
499
500 patch_test_image = os.path.isfile(args.dest)
501
Hung-Te Linf5f2d7f2016-01-08 17:12:46 +0800502 with (sys_utils.MountPartition(args.dest, 1, rw=True) if patch_test_image
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800503 else DummyContext(args.dest)) as dest:
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800504
Hung-Te Lin56b18402015-01-16 14:52:30 +0800505 installer = FactoryToolkitInstaller(
Wei-Ning Huang5135b7e2015-07-03 17:31:15 +0800506 src=src_root, dest=dest, no_enable=args.no_enable,
Hung-Te Linfc162e62017-09-21 00:45:04 +0800507 non_cros=args.non_cros, apps=args.apps,
508 active_test_list=args.active_test_list)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800509
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800510 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800511
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800512 if not args.yes:
513 answer = raw_input('*** Continue? [y/N] ')
514 if not answer or answer[0] not in 'yY':
515 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800516
Wei-Han Chen7137dcf2016-08-03 15:43:22 +0800517 if args.install_dirs:
518 installer.InstallFactorySubDir(args.install_dirs)
519 else:
520 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800521
522if __name__ == '__main__':
You-Cheng Syu70e99ad2017-03-09 17:15:00 +0800523 # makself interprets "LICENSE" environment variable string as license text and
Hung-Te Lin5a2a1c42016-11-10 12:43:40 +0800524 # will prompt user to accept before installation. For factory toolkit, we
525 # don't want any user interaction in installation and the license is already
526 # covered by ebuild or download platform like CPFE.
527 os.putenv('LICENSE', '')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800528 main()