• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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