Rework cros_sdk commands in full.

Via CL:33861, --download was changed to imply --no-enter basically;
regardless of if it was replacing or what.  This was a regression,
but serves as a good excuse to cleanup the commands.

Simplify it basically; all options imply --no-enter, unless
--enter is given, or a chroot command is given, or no cros_sdk
commands were given.

Finally, fix a bug from CL:33861 where if cros_sdk is invoked
from a standalone chromite, it fails w/ due to not being able
to find/read the sdk versions configuration.

BUG=None
TEST=cros_sdk invocations of --replace, --delete, --download, etc.

Change-Id: I17dbd5c2d7ac6682765fe0b0d0f8265351fd5449
Reviewed-on: https://gerrit.chromium.org/gerrit/35098
Commit-Ready: Brian Harring <ferringb@chromium.org>
Reviewed-by: Brian Harring <ferringb@chromium.org>
Tested-by: Brian Harring <ferringb@chromium.org>
diff --git a/scripts/cros_sdk.py b/scripts/cros_sdk.py
index 8976014..6d6d00e 100644
--- a/scripts/cros_sdk.py
+++ b/scripts/cros_sdk.py
@@ -6,6 +6,7 @@
 """This script fetches and prepares an SDK chroot.
 """
 
+import errno
 import os
 import urlparse
 
@@ -62,15 +63,19 @@
 def GetSdkConfig():
   """Extracts latest version from chromiumos-overlay."""
   d = {}
-  with open(SDK_VERSION_FILE) as f:
-    for raw_line in f:
-      line = raw_line.split('#')[0].strip()
-      if not line:
-        continue
-      chunks = line.split('=', 1)
-      if len(chunks) != 2:
-        raise Exception('Malformed version file; line %r' % raw_line)
-      d[chunks[0]] = chunks[1].strip().strip('"')
+  try:
+    with open(SDK_VERSION_FILE) as f:
+      for raw_line in f:
+        line = raw_line.split('#')[0].strip()
+        if not line:
+          continue
+        chunks = line.split('=', 1)
+        if len(chunks) != 2:
+          raise Exception('Malformed version file; line %r' % raw_line)
+        d[chunks[0]] = chunks[1].strip().strip('"')
+  except EnvironmentError, e:
+    if e.errnor != errno.ENOENT:
+      raise
   return d
 
 
@@ -214,61 +219,58 @@
 
 
 def main(argv):
-  # TODO(ferringb): make argv required once depot_tools is fixed.
-  usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- <args>]
+  usage = """usage: %prog [options] [VAR1=val1 .. VARn=valn -- args]
 
-This script manages a local CrOS SDK chroot. Depending on the flags,
-it can download, build or enter a chroot.
+This script is used for manipulating local chroot environments; creating,
+deleting, downloading, etc.  If given --enter (or no args), it defaults
+to an interactive bash shell within the chroot.
 
-Action taken is the following:
---enter  (default)  .. Installs and enters a chroot
---download          .. Just download a chroot (enter if combined with --enter)
---delete            .. Removes a chroot
-"""
+If given args those are passed to the chroot environment, and executed."""
   conf = GetSdkConfig()
   sdk_latest_version = conf.get('SDK_LATEST_VERSION', '<unknown>')
   bootstrap_latest_version = conf.get('BOOTSTRAP_LATEST_VERSION', '<unknown>')
 
-  parser = commandline.OptionParser(usage, caching=True)
-  # Actions:
-  parser.add_option('--bootstrap',
-                    action='store_true', dest='bootstrap', default=False,
-                    help=('Build everything from scratch, including the sdk.  '
-                          'Use this only if you need to validate a change '
-                          'that affects SDK creation itself (toolchain and '
-                          'build are typically the only folk who need this).  '
-                          'Note this will quite heavily slow down the build.  '
-                          'Finally, this option implies --enter.'))
-  parser.add_option('--delete',
-                    action='store_true', dest='delete', default=False,
-                    help=('Delete the current SDK chroot'))
-  parser.add_option('--download',
-                    action='store_true', dest='download', default=False,
-                    help=('Download and install a prebuilt SDK'))
-  parser.add_option('--enter',
-                    action='store_true', dest='enter', default=False,
-                    help=('Enter the SDK chroot, possibly (re)create first'))
+  parser = commandline.OptionParser(usage=usage, caching=True)
+
+  commands = parser.add_option_group("Commands")
+  commands.add_option(
+      '--enter', action='store_true', default=False,
+      help='Enter the SDK chroot.  Implies --create.')
+  commands.add_option(
+      '--create', action='store_true',default=False,
+      help='Create the chroot only if it does not already exist.  '
+      'Implies --download.')
+  commands.add_option(
+      '--bootstrap', action='store_true', default=False,
+      help='Build everything from scratch, including the sdk.  '
+      'Use this only if you need to validate a change '
+      'that affects SDK creation itself (toolchain and '
+      'build are typically the only folk who need this).  '
+      'Note this will quite heavily slow down the build.  '
+      'This option implies --create --nousepkg.')
+  commands.add_option(
+      '-r', '--replace', action='store_true', default=False,
+      help='Replace an existing SDK chroot.  Basically an alias '
+      'for --delete --create.')
+  commands.add_option(
+      '--delete', action='store_true', default=False,
+      help='Delete the current SDK chroot if it exists.')
+  commands.add_option(
+      '--download', action='store_true', default=False,
+      help='Download the sdk.')
 
   # Global options:
   default_chroot = os.path.join(SRC_ROOT, constants.DEFAULT_CHROOT_DIR)
-  parser.add_option('--chroot', dest='chroot', default=default_chroot,
-                    type='path',
-                    help=('SDK chroot dir name [%s]' %
-                          constants.DEFAULT_CHROOT_DIR))
+  parser.add_option(
+      '--chroot', dest='chroot', default=default_chroot, type='path',
+      help=('SDK chroot dir name [%s]' % constants.DEFAULT_CHROOT_DIR))
 
-  # Additional options:
-  parser.add_option('--chrome_root',
-                    dest='chrome_root', default=None, type='path',
-                    help=('Mount this chrome root into the SDK chroot'))
-  parser.add_option('--chrome_root_mount',
-                    dest='chrome_root_mount', default=None, type='path',
-                    help=('Mount chrome into this path inside SDK chroot'))
-  parser.add_option('-r', '--replace',
-                    action='store_true', dest='replace', default=False,
-                    help=('Replace an existing SDK chroot'))
-  parser.add_option('--nousepkg',
-                    action='store_true', dest='nousepkg', default=False,
-                    help=('Do not use binary packages when creating a chroot'))
+  parser.add_option('--chrome_root', default=None, type='path',
+                    help='Mount this chrome root into the SDK chroot')
+  parser.add_option('--chrome_root_mount', default=None, type='path',
+                    help='Mount chrome into this path inside SDK chroot')
+  parser.add_option('--nousepkg', action='store_true', default=False,
+                    help='Do not use binary packages when creating a chroot.')
   parser.add_option('-u', '--url',
                     dest='sdk_url', default=None,
                     help=('''Use sdk tarball located at this url.
@@ -277,7 +279,7 @@
                     help='Use this sdk version.  For prebuilt, current is %r'
                          ', for bootstrapping its %r.'
                           % (sdk_latest_version, bootstrap_latest_version))
-  (options, remaining_arguments) = parser.parse_args(argv)
+  options, chroot_command = parser.parse_args(argv)
 
   # Some sanity checks first, before we ask for sudo credentials.
   if cros_build_lib.IsInsideChroot():
@@ -299,22 +301,34 @@
         '  sudo apt-get install <packagename>'
         % (', '.join(missing))))
 
-  # Default action is --enter, if no other is selected.
-  if not (options.bootstrap or options.download or options.delete):
-    options.enter = True
+  # Expand out the aliases...
+  if options.replace:
+    options.delete = options.create = True
 
-  # Only --enter can process additional args as passthrough commands.
-  # Warn and exit for least surprise.
-  if len(remaining_arguments) > 0 and not options.enter:
-    parser.error("Additional arguments are not permitted, unless running "
-                 "with --enter")
+  if options.bootstrap:
+    options.create = True
 
-  # Some actions can be combined, as they merely modify how is the chroot
-  # going to be made. The only option that hates all others is --delete.
-  if options.delete and \
-    (options.enter or options.download or options.bootstrap):
-    parser.error("--delete cannot be combined with --enter, "
-                 "--download or --bootstrap")
+  # If a command is not given, default to enter.
+  options.enter |= not any(getattr(options, x.dest)
+                           for x in commands.option_list)
+  options.enter |= bool(chroot_command)
+
+  if options.enter and options.delete and not options.create:
+    parser.error("Trying to enter the chroot when --delete "
+                 "was specified makes no sense.")
+
+  # Finally, discern if we need to create the chroot.
+  chroot_exists = os.path.exists(options.chroot)
+  if options.create or options.enter:
+    # Only create if it's being wiped, or if it doesn't exist.
+    if not options.delete and chroot_exists:
+      options.create = False
+    else:
+      options.download = True
+
+  # Finally, flip create if necessary.
+  if options.enter:
+    options.create |= not chroot_exists
 
   if not options.sdk_version:
     sdk_version = (bootstrap_latest_version if options.bootstrap
@@ -330,10 +344,6 @@
   else:
     urls = GetArchStageTarballs(sdk_version)
 
-  if options.delete and not os.path.exists(options.chroot):
-    print "Not doing anything. The chroot you want to remove doesn't exist."
-    return 0
-
   lock_path = os.path.dirname(options.chroot)
   lock_path = os.path.join(lock_path,
                            '.%s_lock' % os.path.basename(options.chroot))
@@ -342,16 +352,9 @@
       _CreateLockFile(lock_path)
       with locking.FileLock(lock_path, 'chroot lock') as lock:
 
-        if os.path.exists(options.chroot):
-          if options.delete or options.replace:
-            lock.write_lock()
-            DeleteChroot(options.chroot)
-            if options.delete:
-              return 0
-          elif not options.enter and not options.download:
-            print "Chroot already exists. Run with --replace to re-create."
-        elif options.delete:
-          return 0
+        if options.delete and os.path.exists(options.chroot):
+          lock.write_lock()
+          DeleteChroot(options.chroot)
 
         sdk_cache = os.path.join(options.cache_dir, 'sdks')
         distfiles_cache = os.path.join(options.cache_dir, 'distfiles')
@@ -383,16 +386,16 @@
             # Wipe and continue.
             osutils.RmDir(src, sudo=True)
 
-        if not os.path.exists(options.chroot) or options.download:
+        if options.download:
           lock.write_lock()
           sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
-          if options.download:
-            # Nothing further to do.
-            return 0
+
+        if options.create:
+          lock.write_lock()
           CreateChroot(options.chroot, sdk_tarball, options.cache_dir,
                        nousepkg=(options.bootstrap or options.nousepkg))
 
         if options.enter:
           lock.read_lock()
           EnterChroot(options.chroot, options.cache_dir, options.chrome_root,
-                      options.chrome_root_mount, remaining_arguments)
+                      options.chrome_root_mount, chroot_command)