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 16import argparse 17import hashlib 18import logging 19import os 20import shutil 21import subprocess 22import sys 23import tempfile 24import zipfile 25 26from collections import namedtuple 27from platform import system 28 29 30# The format for the deps below is the following: 31# (target_folder, source_url, sha1, target_platform) 32# |source_url| can be either a git repo or a http url. 33# If a git repo, |sha1| is the committish that will be checked out. 34# If a http url, |sha1| is the shasum of the original file. 35# If the url is a .zip or .tgz file it will be automatically deflated under 36# |target_folder|, taking care of stripping the root folder if it's a single 37# root (to avoid ending up with buildtools/protobuf/protobuf-1.2.3/... and have 38# instead just buildtools/protobuf). 39# |target_platform| is either 'darwin', 'linux' or 'all' and applies the dep 40# only on the given platform 41 42# Dependencies required to build code on the host or when targeting desktop OS. 43BUILD_DEPS_HOST = [ 44 # GN 45 ('buildtools/mac/gn', 46 'https://storage.googleapis.com/perfetto/gn-mac-1695-83dad00a', 47 '4c0d45772aea4146699772165e8112fa76ceb295', 'darwin'), 48 ('buildtools/linux64/gn', 49 'https://storage.googleapis.com/perfetto/gn-linux64-1695-83dad00a', 50 'fcabfc379bccaa65b4e2fc791594ba124dafc7d0', 'linux'), 51 52 # clang-format 53 ('buildtools/mac/clang-format', 54 'https://storage.googleapis.com/chromium-clang-format/025ca7c75f37ef4a40f3a67d81ddd11d7d0cdb9b', 55 '025ca7c75f37ef4a40f3a67d81ddd11d7d0cdb9b', 'darwin'), 56 ('buildtools/linux64/clang-format', 57 'https://storage.googleapis.com/chromium-clang-format/942fc8b1789144b8071d3fc03ff0fcbe1cf81ac8', 58 '942fc8b1789144b8071d3fc03ff0fcbe1cf81ac8', 'linux'), 59 # Keep the SHA1 in sync with |clang_format_rev| in chromium //buildtools/DEPS. 60 ('buildtools/clang_format/script', 61 'https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git', 62 '96636aa0e9f047f17447f2d45a094d0b59ed7917', 'all'), 63 64 # Ninja 65 ('buildtools/mac/ninja', 66 'https://storage.googleapis.com/perfetto/ninja-mac-c15b0698da038b2bd2e8970c14c75fadc06b1add', 67 'c15b0698da038b2bd2e8970c14c75fadc06b1add', 'darwin'), 68 ('buildtools/linux64/ninja', 69 'https://storage.googleapis.com/perfetto/ninja-linux64-c866952bda50c29a669222477309287119bbb7e8', 70 'c866952bda50c29a669222477309287119bbb7e8', 'linux'), 71 72 # Keep in sync with Android's //external/googletest/README.version. 73 ('buildtools/googletest.zip', 74 'https://github.com/google/googletest/archive/3f05f651ae3621db58468153e32016bc1397800b.zip', 75 '86384688f7c533ad325a505efc917e0cdf39a0ce', 'all'), 76 77 # Keep in sync with Chromium's //third_party/protobuf. 78 ('buildtools/protobuf.zip', 79 'https://github.com/protocolbuffers/protobuf/releases/download/v3.9.0/protobuf-cpp-3.9.0.zip', 80 'c975536dffe9d9a3d362928aef4fb9f199012b98', 'all'), 81 82 # libc++, libc++abi and libunwind for Linux where we need to rebuild the C++ 83 # lib from sources. Keep the SHA1s in sync with Chrome's src/buildtools/DEPS. 84 ('buildtools/libcxx', 85 'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git', 86 '78d6a7767ed57b50122a161b91f59f19c9bd0d19', 'all'), 87 ('buildtools/libcxxabi', 88 'https://chromium.googlesource.com/chromium/llvm-project/libcxxabi.git', 89 '0d529660e32d77d9111912d73f2c74fc5fa2a858', 'all'), 90 ('buildtools/libunwind', 91 'https://chromium.googlesource.com/external/llvm.org/libunwind.git', 92 '69d9b84cca8354117b9fe9705a4430d789ee599b', 'all'), 93 94 # Keep the revision in sync with Chrome's PACKAGE_VERSION in 95 # tools/clang/scripts/update.py. 96 ('buildtools/clang.tgz', 97 'https://commondatastorage.googleapis.com/chromium-browser-clang/Linux_x64/clang-n332890-c2443155-2.tgz', 98 'd6501ffdb5dbb0ffe8a4b873cc092a9929e661ec', 'linux'), 99 100 # Keep in sync with chromium DEPS. 101 ('buildtools/libfuzzer', 102 'https://chromium.googlesource.com/chromium/llvm-project/compiler-rt/lib/fuzzer.git', 103 'debe7d2d1982e540fbd6bd78604bf001753f9e74', 'linux'), 104 105 # Benchmarking tool. 106 ('buildtools/benchmark.zip', 107 'https://github.com/google/benchmark/archive/v1.5.0.zip', 108 'a9c9bd8a28db82f5ba02998197cfcc4db5a67507', 'all'), 109 110 # Libbacktrace, for stacktraces in Linux/Android debug builds. 111 ('buildtools/libbacktrace.zip', 112 'https://github.com/ianlancetaylor/libbacktrace/archive/177940370e4a6b2509e92a0aaa9749184e64af43.zip', 113 'b723fe9d671d1ab54df1297f6afbf2893a41c3ea', 'all'), 114 115 # Sqlite for the trace processing library. 116 # This is the amalgamated source whose compiled output is meant to be faster. 117 # We still pull the full source for the extensions (not amalgamated). 118 ('buildtools/sqlite.zip', 119 'https://storage.googleapis.com/perfetto/sqlite-amalgamation-3250300.zip', 120 'b78c2cb0d2c9182686c582312479f96a82bf5380', 'all'), 121 ('buildtools/sqlite_src.zip', 122 'https://storage.googleapis.com/perfetto/sqlite-src-3250300.zip', 123 'd1af2883bb800852946f9bf8ab6055e7698e18ee', 'all'), 124 125 # JsonCpp for legacy json import. Used only by the trace processor in 126 # standalone builds. 127 ('buildtools/jsoncpp.zip', 128 'https://github.com/open-source-parsers/jsoncpp/archive/1.0.0.zip', 129 '3219e26f2e249bb46b7d688478208c7ec138fea4', 'all'), 130 131 # These dependencies are for libunwindstack, which is used by src/profiling. 132 ('buildtools/android-core', 133 'https://android.googlesource.com/platform/system/core.git', 134 '8bf4e29e44098e3232ff646331675fb113064162', 'all'), 135 ('buildtools/lzma', 136 'https://android.googlesource.com/platform/external/lzma.git', 137 '7851dce6f4ca17f5caa1c93a4e0a45686b1d56c3', 'all'), 138 ('buildtools/zlib', 139 'https://android.googlesource.com/platform/external/zlib.git', 140 'dfa0646a03b4e1707469e04dc931b09774968fe6', 'all'), 141 ('buildtools/bionic', 142 'https://android.googlesource.com/platform/bionic.git', 143 'a60488109cda997dfd83832731c8527feaa2825e', 'all'), 144 145 # Example traces for regression tests. 146 ( 147 'buildtools/test_data.zip', 148 'https://storage.googleapis.com/perfetto/test-data-20200604-140632.zip', 149 '2c0a3307f25730d0060d7554d0e59c078c80a366', 150 'all', 151 ), 152 153 # Linenoise, used only by trace_processor in standalone builds. 154 ('buildtools/linenoise', 155 'https://fuchsia.googlesource.com/third_party/linenoise.git', 156 'c894b9e59f02203dbe4e2be657572cf88c4230c3', 'all'), 157] 158 159# Dependencies required to build Android code. 160# URLs and SHA1s taken from: 161# - https://dl.google.com/android/repository/repository-11.xml 162# - https://dl.google.com/android/repository/sys-img/android/sys-img.xml 163BUILD_DEPS_ANDROID = [ 164 # Android NDK 165 ('buildtools/ndk.zip', 166 'https://dl.google.com/android/repository/android-ndk-r17b-darwin-x86_64.zip', 167 'f990aafaffec0b583d2c5420bfa622e52ac14248', 'darwin'), 168 ('buildtools/ndk.zip', 169 'https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip', 170 'dd5762ee7ef4995ad04fe0c45a608c344d99ca9f', 'linux'), 171] 172 173# Dependencies required to run Android tests. 174TEST_DEPS_ANDROID = [ 175 # Android emulator images. 176 ('buildtools/aosp-arm.zip', 177 'https://storage.googleapis.com/perfetto/aosp-02022018-arm.zip', 178 'a480d5e7d3ca888b0a58fe15ce76b1791537429a', 'all'), 179 180 # platform-tools.zip contains adb binaries. 181 ('buildtools/android_sdk/platform-tools.zip', 182 'https://dl.google.com/android/repository/platform-tools_r26.0.0-darwin.zip', 183 'e75b6137dc444f777eb02f44a6d9819b3aabff82', 'darwin'), 184 ('buildtools/android_sdk/platform-tools.zip', 185 'https://dl.google.com/android/repository/platform-tools_r26.0.0-linux.zip', 186 '00de8a6631405b617c10f68cd11ff2e1cd528e23', 'linux'), 187 188 # Android emulator binaries. 189 ('buildtools/emulator', 190 'https://android.googlesource.com/platform/prebuilts/android-emulator.git', 191 '4b260028dc27bc92c39bee9129cb2ba839970956', 'all'), 192] 193 194# This variable is updated by tools/roll-catapult-trace-viewer. 195CATAPULT_SHA1 = '5f77256e1b24851a05e8580438b532e2480dd7fd' 196 197TYPEFACES_SHA1 = '4fb455de506f8a2859dc5264b8448c2559b08ab8' 198 199UI_DEPS = [ 200 ('buildtools/nodejs.tgz', 201 'https://storage.googleapis.com/perfetto/node-v10.3.0-darwin-x64.tar.gz', 202 '6d9a122785f38c256add3b25f74adf125497861a', 'darwin'), 203 ('buildtools/nodejs.tgz', 204 'https://storage.googleapis.com/perfetto/node-v10.3.0-linux-x64.tar.xz', 205 '118f6ea19f75089b3f12ac2ddfce357bff872b5e', 'linux'), 206 ('buildtools/emsdk/emscripten.tgz', 207 'https://storage.googleapis.com/perfetto/emscripten-1.37.40.tar.gz', 208 '588c28221321ebbdfc8e3a6f47ea6106f589669b', 'all'), 209 ('buildtools/emsdk/llvm.tgz', 210 'https://storage.googleapis.com/perfetto/emscripten-llvm-e1.37.40-darwin.tar.gz', 211 '7a894ef0a52821c62f6abaac552dc4ce5d424607', 'darwin'), 212 ('buildtools/emsdk/llvm.tgz', 213 'https://storage.googleapis.com/perfetto/emscripten-llvm-e1.37.40-static-linux.tar.gz', 214 '478501b9b7a14884e546c84efe209a90052cbb07', 'linux'), 215 ('buildtools/d8.tgz', 216 'https://storage.googleapis.com/perfetto/d8-linux-5.7.492.65.tar.gz', 217 '95e82ad7faf0a6f74d950c2aa65e3858b7bdb6c6', 'linux'), 218 ('buildtools/d8.tgz', 219 'https://storage.googleapis.com/perfetto/d8-darwin-6.6.346.32.tar.gz', 220 '1abd630619bb1977ab62095570a113d782a1545d', 'darwin'), 221 ('buildtools/catapult_trace_viewer.tgz', 222 'https://storage.googleapis.com/perfetto/catapult_trace_viewer-%s.tar.gz' % 223 CATAPULT_SHA1, CATAPULT_SHA1, 'all'), 224 ('buildtools/typefaces.tgz', 225 'https://storage.googleapis.com/perfetto/typefaces-%s.tar.gz' % 226 TYPEFACES_SHA1, TYPEFACES_SHA1, 'all') 227] 228 229ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 230UI_DIR = os.path.join(ROOT_DIR, 'ui') 231NODE_MODULES_STATUS_FILE = os.path.join(UI_DIR, 'node_modules', '.last_install') 232 233 234def DownloadURL(url, out_file): 235 subprocess.check_call(['curl', '-L', '-#', '-o', out_file, url]) 236 237 238def ReadFile(path): 239 if not os.path.exists(path): 240 return None 241 with open(path) as f: 242 return f.read().strip() 243 244 245def MkdirRecursive(path): 246 # Works with both relative and absolute paths 247 cwd = '/' if path.startswith('/') else ROOT_DIR 248 for part in path.split('/'): 249 cwd = os.path.join(cwd, part) 250 if not os.path.exists(cwd): 251 os.makedirs(cwd) 252 else: 253 assert (os.path.isdir(cwd)) 254 255 256def HashLocalFile(path): 257 if not os.path.exists(path): 258 return None 259 with open(path, 'rb') as f: 260 return hashlib.sha1(f.read()).hexdigest() 261 262 263def ExtractZipfilePreservePermissions(zf, info, path): 264 zf.extract(info.filename, path=path) 265 target_path = os.path.join(path, info.filename) 266 min_acls = 0o755 if info.filename.endswith('/') else 0o644 267 os.chmod(target_path, (info.external_attr >> 16) | min_acls) 268 269 270def IsGitRepoCheckoutOutAtRevision(path, revision): 271 return ReadFile(os.path.join(path, '.git', 'HEAD')) == revision 272 273 274def CheckoutGitRepo(path, git_url, revision, check_only): 275 if IsGitRepoCheckoutOutAtRevision(path, revision): 276 return False 277 if check_only: 278 return True 279 if os.path.exists(path): 280 shutil.rmtree(path) 281 MkdirRecursive(path) 282 logging.info('Fetching %s @ %s into %s', git_url, revision, path) 283 subprocess.check_call(['git', 'init', path], cwd=path) 284 subprocess.check_call( 285 ['git', 'fetch', '--quiet', '--depth', '1', git_url, revision], cwd=path) 286 subprocess.check_call(['git', 'checkout', revision, '--quiet'], cwd=path) 287 assert (IsGitRepoCheckoutOutAtRevision(path, revision)) 288 return True 289 290 291def InstallNodeModules(): 292 logging.info("Running npm install in {0}".format(UI_DIR)) 293 subprocess.check_call([os.path.join(UI_DIR, 'npm'), 'install', '--no-save'], 294 cwd=UI_DIR) 295 with open(NODE_MODULES_STATUS_FILE, 'w') as f: 296 f.write(HashLocalFile(os.path.join(UI_DIR, 'package-lock.json'))) 297 298 299def CheckNodeModules(): 300 """Returns True if the modules are up-to-date. 301 302 There doesn't seem to be an easy way to check node modules versions. Instead 303 just check if package-lock.json changed since the last `npm install` call. 304 """ 305 if not os.path.exists(NODE_MODULES_STATUS_FILE): 306 return False 307 with open(NODE_MODULES_STATUS_FILE, 'r') as f: 308 actual = f.read() 309 expected = HashLocalFile(os.path.join(UI_DIR, 'package-lock.json')) 310 return expected == actual 311 312 313def CheckHashes(): 314 for deps in [BUILD_DEPS_HOST, BUILD_DEPS_ANDROID, TEST_DEPS_ANDROID, UI_DEPS]: 315 for rel_path, url, expected_sha1, platform in deps: 316 if url.endswith('.git'): 317 continue 318 logging.info('Downloading %s from %s', rel_path, url) 319 with tempfile.NamedTemporaryFile(delete=False) as f: 320 f.close() 321 DownloadURL(url, f.name) 322 actual_sha1 = HashLocalFile(f.name) 323 os.unlink(f.name) 324 if (actual_sha1 != expected_sha1): 325 logging.fatal('SHA1 mismatch for {} expected {} was {}'.format( 326 url, expected_sha1, actual_sha1)) 327 328 329def Main(): 330 parser = argparse.ArgumentParser() 331 parser.add_argument('--android', action='store_true') 332 parser.add_argument('--ui', action='store_true') 333 parser.add_argument('--check-only') 334 parser.add_argument('--verify', help='Check all URLs', action='store_true') 335 args = parser.parse_args() 336 if args.verify: 337 CheckHashes() 338 return 0 339 deps = BUILD_DEPS_HOST 340 if args.android: 341 deps += BUILD_DEPS_ANDROID + TEST_DEPS_ANDROID 342 if args.ui: 343 deps += UI_DEPS 344 deps_updated = False 345 for rel_path, url, expected_sha1, platform in deps: 346 if (platform != 'all' and platform != system().lower()): 347 continue 348 local_path = os.path.join(ROOT_DIR, rel_path) 349 if url.endswith('.git'): 350 deps_updated |= CheckoutGitRepo(local_path, url, expected_sha1, 351 args.check_only) 352 continue 353 is_zip = local_path.endswith('.zip') or local_path.endswith('.tgz') 354 zip_target_dir = local_path[:-4] if is_zip else None 355 zip_dir_stamp = os.path.join(zip_target_dir, '.stamp') if is_zip else None 356 357 if ((not is_zip and HashLocalFile(local_path) == expected_sha1) or 358 (is_zip and ReadFile(zip_dir_stamp) == expected_sha1)): 359 continue 360 deps_updated = True 361 if args.check_only: 362 continue 363 MkdirRecursive(os.path.dirname(rel_path)) 364 if HashLocalFile(local_path) != expected_sha1: 365 download_path = local_path + '.tmp' 366 logging.info('Downloading %s from %s', local_path, url) 367 DownloadURL(url, download_path) 368 os.chmod(download_path, 0o755) 369 actual_sha1 = HashLocalFile(download_path) 370 if (actual_sha1 != expected_sha1): 371 os.remove(download_path) 372 logging.fatal('SHA1 mismatch for {} expected {} was {}'.format( 373 download_path, expected_sha1, actual_sha1)) 374 return 1 375 os.rename(download_path, local_path) 376 assert (HashLocalFile(local_path) == expected_sha1) 377 378 if is_zip: 379 logging.info('Extracting %s into %s' % (local_path, zip_target_dir)) 380 assert (os.path.commonprefix((ROOT_DIR, zip_target_dir)) == ROOT_DIR) 381 if os.path.exists(zip_target_dir): 382 logging.info('Deleting stale dir %s' % zip_target_dir) 383 shutil.rmtree(zip_target_dir) 384 385 # Decompress the archive. 386 if local_path.endswith('.tgz'): 387 MkdirRecursive(zip_target_dir) 388 subprocess.check_call(['tar', '-xf', local_path], cwd=zip_target_dir) 389 elif local_path.endswith('.zip'): 390 with zipfile.ZipFile(local_path, 'r') as zf: 391 for info in zf.infolist(): 392 ExtractZipfilePreservePermissions(zf, info, zip_target_dir) 393 394 # If the zip contains one root folder, rebase one level up moving all 395 # its sub files and folders inside |target_dir|. 396 subdir = os.listdir(zip_target_dir) 397 if len(subdir) == 1: 398 subdir = os.path.join(zip_target_dir, subdir[0]) 399 if os.path.isdir(subdir): 400 for subf in os.listdir(subdir): 401 shutil.move(os.path.join(subdir, subf), zip_target_dir) 402 os.rmdir(subdir) 403 404 # Create stamp and remove the archive. 405 with open(zip_dir_stamp, 'w') as stamp_file: 406 stamp_file.write(expected_sha1) 407 os.remove(local_path) 408 409 if args.ui: 410 # Needs to happen after nodejs is installed above. 411 if args.check_only: 412 deps_updated = not CheckNodeModules() 413 else: 414 InstallNodeModules() 415 416 if args.check_only: 417 if not deps_updated: 418 with open(args.check_only, 'w') as f: 419 f.write('OK') # The content is irrelevant, just keep GN happy. 420 return 0 421 argz = ' '.join([x for x in sys.argv[1:] if not '--check-only' in x]) 422 sys.stderr.write('\033[91mBuild deps are stale. ' + 423 'Please run tools/install-build-deps %s\033[0m' % argz) 424 return 1 425 426 if deps_updated: 427 # Stale binary files may be compiled against old sysroot headers that aren't 428 # tracked by gn. 429 logging.warning('Remember to run "gn clean <output_directory>" ' + 430 'to avoid stale binary files.') 431 432 433if __name__ == '__main__': 434 logging.basicConfig(level=logging.INFO) 435 sys.exit(Main()) 436