Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 # Copyright (C) 2017 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 
     16 import argparse
     17 import hashlib
     18 import logging
     19 import os
     20 import shutil
     21 import subprocess
     22 import sys
     23 import urllib
     24 import zipfile
     25 
     26 from collections import namedtuple
     27 
     28 # When adding a new git dependency here please also add a corresponding entry in
     29 # .travis.yml under the "cache:" section.
     30 
     31 # The format for the deps below is the following:
     32 # (target_folder, source_url, sha1, target_platform)
     33 # |source_url| can be either a git repo or a http url.
     34 # If a git repo, |sha1| is the committish that will be checked out.
     35 # If a http url, |sha1| is the shasum of the original file.
     36 # If the url is a .zip or .tgz file it will be automatically deflated under
     37 # |target_folder|, taking care of stripping the root folder if it's a single
     38 # root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have
     39 # instead just buildtools/protobuf).
     40 # |target_platform| is either 'darwin', 'linux2' or 'all' and applies the dep
     41 # only on the given platform (ask python why linux2 and not just linux).
     42 
     43 # Dependencies required to build code on the host or when targeting desktop OS.
     44 BUILD_DEPS_HOST = [
     45   # GN
     46   ('buildtools/mac/gn',
     47    'https://storage.googleapis.com/chromium-gn/c2c934d4dda1f470a6511b1015dda9a9fb1ce50b',
     48    'c2c934d4dda1f470a6511b1015dda9a9fb1ce50b',
     49    'darwin'
     50   ),
     51   ('buildtools/linux64/gn',
     52    'https://storage.googleapis.com/chromium-gn/b53fa13e950948c6f9a062189b76b34a9610281f',
     53    'b53fa13e950948c6f9a062189b76b34a9610281f',
     54    'linux2'
     55   ),
     56 
     57   # clang-format
     58   ('buildtools/mac/clang-format',
     59    'https://storage.googleapis.com/chromium-clang-format/0679b295e2ce2fce7919d1e8d003e497475f24a3',
     60    '0679b295e2ce2fce7919d1e8d003e497475f24a3',
     61    'darwin'
     62   ),
     63   ('buildtools/linux64/clang-format',
     64    'https://storage.googleapis.com/chromium-clang-format/5349d1954e17f6ccafb6e6663b0f13cdb2bb33c8',
     65    '5349d1954e17f6ccafb6e6663b0f13cdb2bb33c8',
     66    'linux2'
     67   ),
     68   # Keep the SHA1 in sync with |clang_format_rev| in chromium //buildtools/DEPS.
     69   ('buildtools/clang_format/script',
     70    'https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git',
     71    '0653eee0c81ea04715c635dd0885e8096ff6ba6d',
     72    'all'
     73   ),
     74 
     75   # Ninja
     76   ('buildtools/mac/ninja',
     77    'https://storage.googleapis.com/fuchsia-build/fuchsia/ninja/mac/a1db595e824c50cf565fbf0af2437fd91b7babf4',
     78    'a1db595e824c50cf565fbf0af2437fd91b7babf4',
     79    'darwin'
     80   ),
     81   ('buildtools/linux64/ninja',
     82    'https://storage.googleapis.com/fuchsia-build/fuchsia/ninja/linux64/d35b36c84a09f7e38b25947cafada10e8bf835bc',
     83    'd35b36c84a09f7e38b25947cafada10e8bf835bc',
     84    'linux2'
     85   ),
     86 
     87   # Keep in sync with Android's //external/googletest/README.version.
     88   ('buildtools/googletest.zip',
     89    'https://github.com/google/googletest/archive/ff07a5de0e81580547f1685e101194ed1a4fcd56.zip',
     90    'c7edec7d7e6db1fc37a20710de9c4d89e3a3893b',
     91    'all'
     92   ),
     93 
     94   # Keep in sync with Android's //external/protobuf/README.version.
     95   ('buildtools/protobuf.zip',
     96    'https://github.com/google/protobuf/releases/download/v3.0.0-beta-3/protobuf-cpp-3.0.0-beta-3.zip',
     97    '3caec60aa9d8eefc8c3c3201b6b8ca19935edb89',
     98    'all'
     99   ),
    100 
    101   # libc++, libc++abi and libunwind for Linux where we need to rebuild the C++
    102   # lib from sources. Keep the SHA1s in sync with Chrome's src/buildtools/DEPS.
    103   ('buildtools/libcxx',
    104    'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git',
    105    '3a07dd740be63878167a0ea19fe81869954badd7',
    106    'all'
    107   ),
    108   ('buildtools/libcxxabi',
    109    'https://chromium.googlesource.com/chromium/llvm-project/libcxxabi.git',
    110    '4072e8fd76febee37f60aeda76d6d9f5e3791daa',
    111    'all'
    112   ),
    113   ('buildtools/libunwind',
    114    'https://chromium.googlesource.com/external/llvm.org/libunwind.git',
    115    '41f982e5887185b904a456e20dfcd58e6be6cc19',
    116    'all'
    117   ),
    118 
    119   # Keep the revision in sync with Chrome's CLANG_REVISION in
    120   # tools/clang/scripts/update.py.
    121   ('buildtools/clang.tgz',
    122    'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-321529-1.tgz',
    123    '7ca04034ac7e4a956b0084a8bc604bacab94af26',
    124    'linux2'
    125   ),
    126 
    127   # Benchmarking tool.
    128   ('buildtools/benchmark.zip',
    129    'https://github.com/google/benchmark/archive/v1.3.0.zip',
    130    'f387e0df37d54bfd5be239e8d0d3ea2e2c3e34f4',
    131    'all'
    132   ),
    133 
    134   # Libbacktrace, for stacktraces in Linux/Android debug builds.
    135   ('buildtools/libbacktrace.zip',
    136    'https://github.com/ianlancetaylor/libbacktrace/archive/177940370e4a6b2509e92a0aaa9749184e64af43.zip',
    137    'b723fe9d671d1ab54df1297f6afbf2893a41c3ea',
    138    'all'
    139   ),
    140 ]
    141 
    142 # Dependencies required to build Android code.
    143 # URLs and SHA1s taken from:
    144 # - https://dl.google.com/android/repository/repository-11.xml
    145 # - https://dl.google.com/android/repository/sys-img/android/sys-img.xml
    146 BUILD_DEPS_ANDROID = [
    147   # Android NDK
    148   ('buildtools/ndk.zip',
    149    'https://dl.google.com/android/repository/android-ndk-r15c-darwin-x86_64.zip',
    150    'ea4b5d76475db84745aa8828000d009625fc1f98',
    151    'darwin'
    152   ),
    153   ('buildtools/ndk.zip',
    154    'https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip',
    155    '0bf02d4e8b85fd770fd7b9b2cdec57f9441f27a2',
    156    'linux2'
    157   ),
    158 ]
    159 
    160 # Dependencies required to run Android tests.
    161 TEST_DEPS_ANDROID = [
    162   # Android emulator images.
    163   ('buildtools/aosp-arm.zip',
    164    'https://storage.googleapis.com/perfetto/aosp-02022018-arm.zip',
    165    'a480d5e7d3ca888b0a58fe15ce76b1791537429a',
    166    'all'
    167   ),
    168 
    169   # platform-tools.zip contains adb binaries.
    170   ('buildtools/android_sdk/platform-tools.zip',
    171    'https://dl.google.com/android/repository/platform-tools_r26.0.0-darwin.zip',
    172    'e75b6137dc444f777eb02f44a6d9819b3aabff82',
    173    'darwin'
    174   ),
    175   ('buildtools/android_sdk/platform-tools.zip',
    176    'https://dl.google.com/android/repository/platform-tools_r26.0.0-linux.zip',
    177    '00de8a6631405b617c10f68cd11ff2e1cd528e23',
    178    'linux2'
    179   ),
    180 
    181   # Android emulator binaries.
    182   ('buildtools/emulator',
    183    'https://android.googlesource.com/platform/prebuilts/android-emulator.git',
    184    '4b260028dc27bc92c39bee9129cb2ba839970956',
    185    'all'
    186   ),
    187 ]
    188 
    189 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    190 
    191 
    192 def ReadFile(path):
    193   if not os.path.exists(path):
    194     return None
    195   with open(path) as f:
    196       return f.read().strip()
    197 
    198 
    199 def MkdirRecursive(path):
    200   # Works with both relative and absolute paths
    201   cwd = '/' if path.startswith('/') else ROOT_DIR
    202   for part in path.split('/'):
    203     cwd = os.path.join(cwd, part)
    204     if not os.path.exists(cwd):
    205       os.makedirs(cwd)
    206     else:
    207       assert(os.path.isdir(cwd))
    208 
    209 
    210 def HashLocalFile(path):
    211   if not os.path.exists(path):
    212     return None
    213   with open(path, 'rb') as f:
    214     return hashlib.sha1(f.read()).hexdigest()
    215 
    216 
    217 def ExtractZipfilePreservePermissions(zf, info, path):
    218   zf.extract(info.filename, path=path)
    219   target_path = os.path.join(path, info.filename)
    220   min_acls = 0o755 if info.filename.endswith('/') else 0o644
    221   os.chmod(target_path, (info.external_attr >> 16L) | min_acls)
    222 
    223 
    224 def IsGitRepoCheckoutOutAtRevision(path, revision):
    225   return ReadFile(os.path.join(path, '.git', 'HEAD')) == revision
    226 
    227 
    228 def CheckoutGitRepo(path, git_url, revision):
    229   if IsGitRepoCheckoutOutAtRevision(path, revision):
    230     return
    231   if os.path.exists(path):
    232     shutil.rmtree(path)
    233   MkdirRecursive(path)
    234   logging.info('Fetching %s @ %s into %s', git_url, revision, path)
    235   subprocess.check_call(['git', 'init', path], cwd=path)
    236   subprocess.check_call(
    237     ['git', 'fetch', '--depth', '1', git_url, revision], cwd=path)
    238   subprocess.check_call(['git', 'checkout', revision, '--quiet'], cwd=path)
    239   assert(IsGitRepoCheckoutOutAtRevision(path, revision))
    240 
    241 
    242 def Main():
    243   parser = argparse.ArgumentParser()
    244   parser.add_argument('--no-android', action='store_true')
    245   args = parser.parse_args()
    246   deps = BUILD_DEPS_HOST
    247   if not args.no_android:
    248     deps += BUILD_DEPS_ANDROID + TEST_DEPS_ANDROID
    249   for rel_path, url, expected_sha1, platform in deps:
    250     if (platform != 'all' and platform != sys.platform):
    251       continue
    252     local_path = os.path.join(ROOT_DIR, rel_path)
    253     if url.endswith('.git'):
    254       CheckoutGitRepo(local_path, url, expected_sha1)
    255       continue
    256     is_zip = local_path.endswith('.zip') or local_path.endswith('.tgz')
    257     zip_target_dir = local_path[:-4] if is_zip else None
    258     zip_dir_stamp = os.path.join(zip_target_dir, '.stamp') if is_zip else None
    259 
    260     if ((not is_zip and HashLocalFile(local_path) == expected_sha1) or
    261         (is_zip and ReadFile(zip_dir_stamp) == expected_sha1)):
    262       continue
    263     MkdirRecursive(os.path.dirname(rel_path))
    264     if HashLocalFile(local_path) != expected_sha1:
    265       download_path = local_path + '.tmp'
    266       logging.info('Downloading %s from %s', local_path, url)
    267       urllib.urlretrieve(url, download_path)
    268       os.chmod(download_path, 0o755)
    269       if (HashLocalFile(download_path) != expected_sha1):
    270         os.remove(download_path)
    271         logging.fatal('SHA1 mismatch for %s', download_path)
    272         return 1
    273       os.rename(download_path, local_path)
    274     assert(HashLocalFile(local_path) == expected_sha1)
    275 
    276     if is_zip:
    277       logging.info('Extracting %s into %s' % (local_path, zip_target_dir))
    278       assert(os.path.commonprefix((ROOT_DIR, zip_target_dir)) == ROOT_DIR)
    279       if os.path.exists(zip_target_dir):
    280         logging.info('Deleting stale dir %s' % zip_target_dir)
    281         shutil.rmtree(zip_target_dir)
    282 
    283       # Decompress the archive.
    284       if local_path.endswith('.tgz'):
    285         MkdirRecursive(zip_target_dir)
    286         subprocess.check_call(['tar', '-zxf', local_path], cwd=zip_target_dir)
    287       elif local_path.endswith('.zip'):
    288         with zipfile.ZipFile(local_path, 'r') as zf:
    289           for info in zf.infolist():
    290             ExtractZipfilePreservePermissions(zf, info, zip_target_dir)
    291 
    292       # If the zip contains one root folder, rebase one level up moving all
    293       # its sub files and folders inside |target_dir|.
    294       subdir = os.listdir(zip_target_dir)
    295       if len(subdir) == 1:
    296         subdir = os.path.join(zip_target_dir, subdir[0])
    297         if os.path.isdir(subdir):
    298           for subf in os.listdir(subdir):
    299             shutil.move(os.path.join(subdir,subf), zip_target_dir)
    300           os.rmdir(subdir)
    301 
    302       # Create stamp and remove the archive.
    303       with open(zip_dir_stamp, 'w') as stamp_file:
    304         stamp_file.write(expected_sha1)
    305       os.remove(local_path)
    306 
    307 
    308 if __name__ == '__main__':
    309   logging.basicConfig(level=logging.INFO)
    310   sys.exit(Main())
    311