Installer cleanup/documentation.
BUG=chrome-os-partner:25941
CQ-DEPEND=CL:187223
TEST=Unit tests
TEST=Manual on device, patching image, and patching stateful partition
TEST=Full buildbot build (link)
Change-Id: I221ee51ad79dbacabff57497b14d5f70b4fc22bc
Reviewed-on: https://chromium-review.googlesource.com/187224
Reviewed-by: Jon Salz <jsalz@chromium.org>
Commit-Queue: Jon Salz <jsalz@chromium.org>
Tested-by: Jon Salz <jsalz@chromium.org>
diff --git a/py/toolkit/installer.py b/py/toolkit/installer.py
index 8fbde66..17e7014 100755
--- a/py/toolkit/installer.py
+++ b/py/toolkit/installer.py
@@ -15,7 +15,9 @@
import argparse
from contextlib import contextmanager
import os
+import re
import sys
+import tempfile
import factory_common # pylint: disable=W0611
from cros.factory.test import factory
@@ -25,6 +27,56 @@
INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
+# Short and sweet help header for the executable generated by makeself.
+HELP_HEADER = """
+Installs the factory toolkit, transforming a test image into a factory test
+image. You can:
+
+- Install the factory toolkit on a CrOS device that is running a test
+ image. To do this, copy install_factory_toolkit.run to the device and
+ run it. The factory tests will then come up on the next boot.
+
+ rsync -a install_factory_toolkit.run crosdevice:/tmp
+ ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
+
+- Modify a test image, turning it into a factory test image. When you
+ use the image on a device, the factory tests will come up.
+
+ install_factory_toolkit.run chromiumos_test_image.bin
+"""
+
+HELP_HEADER_ADVANCED = """
+- (advanced) Modify a mounted stateful partition, turning it into a factory
+ test image. This is equivalent to the previous command:
+
+ mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
+ install_factory_toolkit.run /mnt/stateful
+ umount /mnt/stateful
+
+- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
+
+ # Unpack but don't actually install
+ install_factory_toolkit.run --target /tmp/toolkit --noexec
+ # Edit some files in /tmp/toolkit
+ emacs /tmp/toolkit/whatever
+ # Repack
+ install_factory_toolkit.run -- --repack /tmp/toolkit \\
+ --pack-into /path/to/new/install_factory_toolkit.run
+"""
+
+# The makeself-generated header comes next. This is a little confusing,
+# so explain.
+HELP_HEADER_MAKESELF = """
+For complete usage information and advanced operations, run
+"install_factory_toolkit.run -- --help" (note the extra "--").
+
+Following is the help message from makeself, which was used to create
+this self-extracting archive.
+
+-----
+"""
+
+
class FactoryToolkitInstaller():
"""Factory toolkit installer.
@@ -38,15 +90,32 @@
"""
def __init__(self, src, dest, no_enable, system_root='/'):
+ self._src = src
self._system_root = system_root
if dest == self._system_root:
self._usr_local_dest = os.path.join(dest, 'usr', 'local')
self._var_dest = os.path.join(dest, 'var')
+
+ # Make sure we're on a CrOS device.
+ lsb_release = self._ReadLSBRelease()
+ is_cros = (
+ lsb_release and
+ re.match('^CHROMEOS_RELEASE', lsb_release, re.MULTILINE) is not None)
+
+ if not is_cros:
+ sys.stderr.write(
+ "ERROR: You're not on a CrOS device (/etc/lsb-release does not\n"
+ "contain CHROMEOS_RELEASE), so you must specify a test image or a\n"
+ "mounted stateful partition on which to install the factory\n"
+ "toolkit. Please run\n"
+ "\n"
+ " install_factory_toolkit.run -- --help\n"
+ "\n"
+ "for help.\n")
+ sys.exit(1)
if os.getuid() != 0:
- raise Exception('Must be root to install on live machine!')
- if not os.path.exists('/etc/lsb-release'):
- raise Exception('/etc/lsb-release is missing. '
- 'Are you running this in chroot?')
+ raise Exception('You must be root to install the factory toolkit on a '
+ 'CrOS device.')
else:
self._usr_local_dest = os.path.join(dest, 'dev_image')
self._var_dest = os.path.join(dest, 'var_overlay')
@@ -66,26 +135,37 @@
raise Exception(
'This installer must be run from within the factory toolkit!')
+ @staticmethod
+ def _ReadLSBRelease():
+ """Returns the contents of /etc/lsb-release, or None if it does not
+ exist."""
+ if os.path.exists('/etc/lsb-release'):
+ with open('/etc/lsb-release') as f:
+ return f.read()
+ return None
+
def WarningMessage(self, target_test_image=None):
+ with open(os.path.join(self._src, 'VERSION')) as f:
+ ret = f.read()
if target_test_image:
- ret = (
+ ret += (
'\n'
'\n'
- '*** You are about to patch factory toolkit into:\n'
+ '*** You are about to patch the factory toolkit into:\n'
'*** %s\n'
'***' % target_test_image)
else:
- ret = (
+ ret += (
'\n'
'\n'
- '*** You are about to install factory toolkit to:\n'
+ '*** You are about to install the factory toolkit to:\n'
'*** %s\n'
'***' % self._dest)
if self._dest == self._system_root:
if self._no_enable:
ret += ('\n'
'*** Factory tests will be disabled after this process is done, but\n'
- '*** you can enable them by creating factory enabled tag:\n'
+ '*** you can enable them by creating the factory enabled tag:\n'
'*** %s\n'
'***' % self._tag_file)
else:
@@ -93,7 +173,8 @@
'*** After this process is done, your device will start factory\n'
'*** tests on the next reboot.\n'
'***\n'
- '*** Factory tests can be disabled by deleting factory enabled tag:\n'
+ '*** Factory tests can be disabled by deleting the factory enabled\n'
+ '*** tag:\n'
'*** %s\n'
'***' % self._tag_file)
return ret
@@ -140,9 +221,13 @@
"""Packs the files containing this script into a factory toolkit."""
with open(os.path.join(src_root, 'VERSION'), 'r') as f:
version = f.read().strip()
- Spawn([os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
- src_root, output_path, version, INSTALLER_PATH],
- check_call=True, log=True)
+ with tempfile.NamedTemporaryFile() as help_header:
+ help_header.write(version + "\n" + HELP_HEADER + HELP_HEADER_MAKESELF)
+ help_header.flush()
+ Spawn([os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
+ '--help-header', help_header.name,
+ src_root, output_path, version, INSTALLER_PATH, '--in-exe'],
+ check_call=True, log=True)
print ('\n'
' Factory toolkit generated at %s.\n'
'\n'
@@ -151,13 +236,28 @@
'\n'
' Alternatively, the factory toolkit can be used to patch a test\n'
' image. For more information, run:\n'
- ' %s -- --help\n'
+ ' %s --help\n'
'\n' % (output_path, output_path))
def main():
+ import logging
+ logging.basicConfig(level=logging.INFO)
+
+ # In order to determine which usage message to show, first determine
+ # whether we're in the self-extracting archive. Do this first
+ # because we need it to even parse the arguments.
+ if '--in-exe' in sys.argv:
+ sys.argv = [x for x in sys.argv if x != '--in-exe']
+ in_archive = True
+ else:
+ in_archive = False
+
parser = argparse.ArgumentParser(
- description='Factory toolkit installer.')
+ description=HELP_HEADER + HELP_HEADER_ADVANCED,
+ usage=('install_factory_toolkit.run -- [options]' if in_archive
+ else None),
+ formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('dest', nargs='?', default='/',
help='A test image or the mount point of the stateful partition. '
"If omitted, install to live system, i.e. '/'.")
@@ -183,6 +283,12 @@
PackFactoryToolkit(src_root, args.pack_into)
return
+ if not in_archive:
+ # If you're not in the self-extracting archive, you're not allowed to
+ # do anything except the above --pack-into call.
+ parser.error('Not running from install_factory_toolkit.run; '
+ 'only --pack-into (without --repack) is allowed')
+
# Change to original working directory in case the user specifies
# a relative path.
# TODO: Use USER_PWD instead when makeself is upgraded