cros_setup_toolchains: regen cache when script changes

When we update MANUAL_PKGS, that doesn't make it out to the cache file
which leads us to updating an incomplete/old list of packages.  Force
a regen of the cache file whenever the cros_setup_toolchains.py script
itself changes.

BUG=chromium:751740
TEST=cache is updated when cros_setup_toolchains.py changes, but not when it's the same
TEST=updating host packages works again as the complete list of packages is found

Change-Id: I63551e0e498d71535d119670641ed0d6e390d792
Reviewed-on: https://chromium-review.googlesource.com/606721
Commit-Ready: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
diff --git a/scripts/cros_setup_toolchains.py b/scripts/cros_setup_toolchains.py
index 55be809..c905c3f 100644
--- a/scripts/cros_setup_toolchains.py
+++ b/scripts/cros_setup_toolchains.py
@@ -7,7 +7,9 @@
 from __future__ import print_function
 
 import copy
+import errno
 import glob
+import hashlib
 import json
 import os
 import re
@@ -110,14 +112,49 @@
 
   @classmethod
   def Load(cls, reconfig):
-    """Load crossdev cache from disk."""
+    """Load crossdev cache from disk.
+
+    We invalidate the cache when crossdev updates or this script changes.
+    """
     crossdev_version = GetStablePackageVersion('sys-devel/crossdev', True)
-    cls._CACHE = {'crossdev_version': crossdev_version}
-    if os.path.exists(cls._CACHE_FILE) and not reconfig:
-      with open(cls._CACHE_FILE) as f:
-        data = json.load(f)
-        if crossdev_version == data.get('crossdev_version'):
-          cls._CACHE = data
+    # If we run the compiled/cached .pyc file, we'll read/hash that when we
+    # really always want to track the source .py file.
+    script = os.path.abspath(__file__)
+    if script.endswith('.pyc'):
+      script = script[:-1]
+    setup_toolchains_hash = hashlib.md5(osutils.ReadFile(script)).hexdigest()
+
+    cls._CACHE = {
+        'crossdev_version': crossdev_version,
+        'setup_toolchains_hash': setup_toolchains_hash,
+    }
+
+    logging.debug('cache: checking file: %s', cls._CACHE_FILE)
+    if reconfig:
+      logging.debug('cache: forcing regen due to reconfig')
+      return
+
+    try:
+      file_data = osutils.ReadFile(cls._CACHE_FILE)
+    except IOError as e:
+      if e.errno != errno.ENOENT:
+        logging.warning('cache: reading failed: %s', e)
+        osutils.SafeUnlink(cls._CACHE_FILE)
+      return
+
+    try:
+      data = json.loads(file_data)
+    except ValueError as e:
+      logging.warning('cache: ignoring invalid content: %s', e)
+      return
+
+    if crossdev_version != data.get('crossdev_version'):
+      logging.debug('cache: rebuilding after crossdev upgrade')
+    elif setup_toolchains_hash != data.get('setup_toolchains_hash'):
+      logging.debug('cache: rebuilding after cros_setup_toolchains change')
+    else:
+      logging.debug('cache: content is up-to-date!')
+      cls._CACHE = data
 
   @classmethod
   def Save(cls):