Use hermetic clang with sanitizers

While so far we had no problems building the release/debug configs with
clang back to 3.4, turns out the situation is more difficult with
sanitizers. asan is broken with-3.4 and msan is broken with clang-3.9.
This makes harder for devs to reproduce an asan failure as seen on the
CI bots.
This CL introduces the ability to use a hermetic compiler, pinning the
same clang tarball that Chrome uses (% keeping it up to date) and by
default enables the hermetic version of clang when using any of the
sanitizers (overridable by setting is_hermetic_clang=true).
This still leaves the default debug/release configurations built with
the system clang, so we keep coverage for compilers available on
Ubuntu Trusty on the CI.

Change-Id: I824b729b6ae76b4b17dfacf0c44982414ba6ceda
diff --git a/build/install-build-deps b/build/install-build-deps
index e6bc7ea..a3d34dd 100755
--- a/build/install-build-deps
+++ b/build/install-build-deps
@@ -28,6 +28,18 @@
 # When adding a new git dependency here please also add a corresponding entry in
 # .travis.yml under the "cache:" section.
 
+# The format for the deps below is the following:
+# (target_folder, source_url, sha1, target_platform)
+# |source_url| can be either a git repo or a http url.
+# If a git repo, |sha1| is the committish that will be checked out.
+# If a http url, |sha1| is the shasum of the original file.
+# If the url is a .zip or .tgz file it will be automatically deflated under
+# |target_folder|, taking care of stripping the root folder if it's a single
+# root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have
+# instead just buildtools/protobuf).
+# |target_platform| is either 'darwin', 'linux2' or 'all' and applies the dep
+# only on the given platform (ask python why linux2 and not just linux).
+
 # Dependencies required to build code on the host or when targeting desktop OS.
 BUILD_DEPS_HOST = [
   # GN
@@ -86,8 +98,8 @@
    'all'
   ),
 
-  # libc++ and libc++abi, for clang msan that require rebuilding the C++ lib
-  # from sources. Keep the SHA1s in sync with Chrome's src/buildtools/DEPS.
+  # libc++, libc++abi and libunwind for Linux where we need to rebuild the C++
+  # lib from sources. Keep the SHA1s in sync with Chrome's src/buildtools/DEPS.
   ('buildtools/libcxx',
    'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git',
    '3a07dd740be63878167a0ea19fe81869954badd7',
@@ -104,6 +116,14 @@
    'all'
   ),
 
+  # Keep the revision in sync with Chrome's CLANG_REVISION in
+  # tools/clang/scripts/update.py.
+  ('buildtools/clang.tgz',
+   'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-315613-1.tgz',
+   '09f63ace1ce25a3e5d36e760026f4ad4619e42de',
+   'linux2'
+  ),
+
   # Benchmarking tool.
   ('buildtools/benchmark.zip',
    'https://github.com/google/benchmark/archive/v1.2.0.zip',
@@ -234,7 +254,7 @@
     if url.endswith('.git'):
       CheckoutGitRepo(local_path, url, expected_sha1)
       continue
-    is_zip = local_path.lower().endswith('.zip')
+    is_zip = local_path.endswith('.zip') or local_path.endswith('.tgz')
     zip_target_dir = local_path[:-4] if is_zip else None
     zip_dir_stamp = os.path.join(zip_target_dir, '.stamp') if is_zip else None
 
@@ -260,22 +280,30 @@
       if os.path.exists(zip_target_dir):
         logging.info('Deleting stale dir %s' % zip_target_dir)
         shutil.rmtree(zip_target_dir)
-      with zipfile.ZipFile(local_path, 'r') as zf:
-        for info in zf.infolist():
-          ExtractZipfilePreservePermissions(zf, info, zip_target_dir)
 
-        # If the zip contains one root folder, rebase one level up moving all
-        # its sub files and folders inside |target_dir|.
-        subdir = os.listdir(zip_target_dir)
-        if len(subdir) == 1:
-          subdir = os.path.join(zip_target_dir, subdir[0])
-          if os.path.isdir(subdir):
-            for subf in os.listdir(subdir):
-              shutil.move(os.path.join(subdir,subf), zip_target_dir)
-            os.rmdir(subdir)
-        with open(zip_dir_stamp, 'w') as stamp_file:
-          stamp_file.write(expected_sha1)
-        os.remove(local_path)
+      # Decompress the archive.
+      if local_path.endswith('.tgz'):
+        MkdirRecursive(zip_target_dir)
+        subprocess.check_call(['tar', '-zxf', local_path], cwd=zip_target_dir)
+      elif local_path.endswith('.zip'):
+        with zipfile.ZipFile(local_path, 'r') as zf:
+          for info in zf.infolist():
+            ExtractZipfilePreservePermissions(zf, info, zip_target_dir)
+
+      # If the zip contains one root folder, rebase one level up moving all
+      # its sub files and folders inside |target_dir|.
+      subdir = os.listdir(zip_target_dir)
+      if len(subdir) == 1:
+        subdir = os.path.join(zip_target_dir, subdir[0])
+        if os.path.isdir(subdir):
+          for subf in os.listdir(subdir):
+            shutil.move(os.path.join(subdir,subf), zip_target_dir)
+          os.rmdir(subdir)
+
+      # Create stamp and remove the archive.
+      with open(zip_dir_stamp, 'w') as stamp_file:
+        stamp_file.write(expected_sha1)
+      os.remove(local_path)
 
 
 if __name__ == '__main__':