Support repacking factory toolkit

With this, unpacked toolkit can be repacked. For example:

  # ./install_factory_toolkit.run -- --noexec --target unpacked

unpacks the content of the factory toolkit into a directory called
'unpacked'. After modifying the content, it can then be repacked by:

  # ./install_factory_toolkit.run -- --repack unpacked --pack-into \
        new_factory_toolkit.run

BUG=chrome-os-partner:25941
TEST=Unpack, modify a file, and then repack.
CQ-DEPEND=CL:186954

Change-Id: I03b6dd9a0310b5b8242987056fe9a8764ce6b41b
Signed-off-by: Vic (Chun-Ju) Yang <victoryang@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/186946
diff --git a/py/toolkit/installer.py b/py/toolkit/installer.py
index 580f67c..8fbde66 100755
--- a/py/toolkit/installer.py
+++ b/py/toolkit/installer.py
@@ -23,6 +23,8 @@
 from cros.factory.utils.process_utils import Spawn
 
 
+INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
+
 class FactoryToolkitInstaller():
   """Factory toolkit installer.
 
@@ -134,6 +136,25 @@
     print f.read()
 
 
+def PackFactoryToolkit(src_root, output_path):
+  """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)
+  print ('\n'
+      '  Factory toolkit generated at %s.\n'
+      '\n'
+      '  To install factory toolkit on a live device running a test image,\n'
+      '  copy this to the device and execute it as root.\n'
+      '\n'
+      '  Alternatively, the factory toolkit can be used to patch a test\n'
+      '  image. For more information, run:\n'
+      '    %s -- --help\n'
+      '\n' % (output_path, output_path))
+
+
 def main():
   parser = argparse.ArgumentParser(
       description='Factory toolkit installer.')
@@ -146,14 +167,20 @@
       help="Don't ask for confirmation")
   parser.add_argument('--build-info', action='store_true',
       help="Print build information and exit")
+  parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
+      help="Pack the files into a new factory toolkit")
+  parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
+      help="Repack from previously unpacked toolkit")
   args = parser.parse_args()
 
   src_root = factory.FACTORY_PATH
   for _ in xrange(3):
     src_root = os.path.dirname(src_root)
 
-  if args.build_info:
-    PrintBuildInfo(src_root)
+  # --pack-into may be called directly so this must be done before changing
+  # working directory to OLDPWD.
+  if args.pack_into and args.repack is None:
+    PackFactoryToolkit(src_root, args.pack_into)
     return
 
   # Change to original working directory in case the user specifies
@@ -161,6 +188,17 @@
   # TODO: Use USER_PWD instead when makeself is upgraded
   os.chdir(os.environ['OLDPWD'])
 
+  if args.repack:
+    if args.pack_into is None:
+      parser.error('Must specify --pack-into when using --repack.')
+    Spawn([os.path.join(args.repack, INSTALLER_PATH),
+           '--pack-into', args.pack_into], check_call=True, log=True)
+    return
+
+  if args.build_info:
+    PrintBuildInfo(src_root)
+    return
+
   if not os.path.exists(args.dest):
     parser.error('Destination %s does not exist!' % args.dest)