Primiano Tucci | 1fe41c8 | 2021-07-28 20:52:40 +0100 | [diff] [blame] | 1 | # Copyright (C) 2021 The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | """ |
| 15 | This source defines a self-contained function to fetch a perfetto prebuilt. |
| 16 | |
| 17 | This function is copy/pasted by //tools/roll-prebuilts in different places: |
| 18 | - Into the //tools/{trace_processor, traceconv} scripts, which are just plain |
| 19 | wrappers around executables. |
| 20 | - Into the //tools/{heap_profiler, record_android_trace} scripts, which contain |
| 21 | some other hand-written python code. |
| 22 | In both cases toll-prebuilts copies this source (together with a manifest) into |
| 23 | a section annotated with "BEGIN_SECTION_GENERATED_BY(roll-prebuilts)" / END... . |
| 24 | The automated-copy-paste is to keep those script hermetic, so people can just |
| 25 | download and run them without checking out the repo. |
| 26 | |
| 27 | The manifest argument looks as follows in the generated files: |
| 28 | PERFETTO_PREBUILT_MANIFEST = [{ |
| 29 | 'tool': 'trace_to_text', |
| 30 | 'arch': 'mac-amd64', |
| 31 | 'file_name': 'trace_to_text', |
| 32 | 'file_size': 7087080, |
| 33 | 'url': https://commondatastorage.googleapis.com/.../trace_to_text', |
| 34 | 'sha256': 7d957c005b0dc130f5bd855d6cec27e060d38841b320d04840afc569f9087490', |
| 35 | 'platform': 'darwin', |
| 36 | 'machine': 'x86_64' |
| 37 | }, |
| 38 | ... |
| 39 | ] |
| 40 | |
| 41 | The intended usage is: |
| 42 | |
| 43 | bin_path = get_perfetto_prebuilt('trace_processor_shell') |
| 44 | subprocess.call(bin_path, ...) |
| 45 | """ |
| 46 | |
| 47 | from logging import exception |
| 48 | |
| 49 | PERFETTO_PREBUILT_MANIFEST = [] |
| 50 | |
| 51 | # COPIED_SECTION_START_MARKER |
| 52 | |
| 53 | |
| 54 | # DO NOT EDIT. If you wish to make edits to this code, you need to change only |
| 55 | # //tools/get_perfetto_prebuilt.py and run /tools/roll-prebuilts to regenerate |
| 56 | # all the others scripts this is embedded into. |
| 57 | def get_perfetto_prebuilt(tool_name, soft_fail=False, arch=None): |
| 58 | """ Downloads the prebuilt, if necessary, and returns its path on disk. """ |
| 59 | |
| 60 | # The first time this is invoked, it downloads the |url| and caches it into |
| 61 | # ~/.perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the |
| 62 | # cached version. |
| 63 | def download_or_get_cached(file_name, url, sha256): |
| 64 | import os, hashlib, subprocess |
| 65 | dir = os.path.join( |
| 66 | os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts') |
| 67 | os.makedirs(dir, exist_ok=True) |
| 68 | bin_path = os.path.join(dir, file_name) |
| 69 | sha256_path = os.path.join(dir, file_name + '.sha256') |
| 70 | needs_download = True |
| 71 | |
| 72 | # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last |
| 73 | # download is cached into file_name.sha256, just check if that matches. |
| 74 | if os.path.exists(bin_path) and os.path.exists(sha256_path): |
| 75 | with open(sha256_path, 'rb') as f: |
| 76 | digest = f.read().decode() |
| 77 | if digest == sha256: |
| 78 | needs_download = False |
| 79 | |
| 80 | if needs_download: |
| 81 | # Either the filed doesn't exist or the SHA256 doesn't match. |
| 82 | tmp_path = bin_path + '.tmp' |
| 83 | print('Downloading ' + url) |
| 84 | subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url]) |
| 85 | with open(tmp_path, 'rb') as fd: |
| 86 | actual_sha256 = hashlib.sha256(fd.read()).hexdigest() |
| 87 | if actual_sha256 != sha256: |
| 88 | raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' % |
| 89 | (url, actual_sha256, sha256)) |
| 90 | os.chmod(tmp_path, 0o755) |
| 91 | os.rename(tmp_path, bin_path) |
| 92 | with open(sha256_path, 'w') as f: |
| 93 | f.write(sha256) |
| 94 | return bin_path |
| 95 | # --- end of download_or_get_cached() --- |
| 96 | |
| 97 | # --- get_perfetto_prebuilt() function starts here. --- |
| 98 | import os, platform, sys |
| 99 | plat = sys.platform.lower() |
| 100 | machine = platform.machine().lower() |
| 101 | manifest_entry = None |
| 102 | for entry in PERFETTO_PREBUILT_MANIFEST: |
| 103 | # If the caller overrides the arch, just match that (for Android prebuilts). |
| 104 | if arch and entry.get('arch') == arch: |
| 105 | manifest_entry = entry |
| 106 | break |
| 107 | # Otherwise guess the local machine arch. |
| 108 | if entry.get('tool') == tool_name and entry.get( |
Primiano Tucci | d3e40bd | 2021-08-03 10:52:39 +0100 | [diff] [blame] | 109 | 'platform') == plat and machine in entry.get('machine', []): |
Primiano Tucci | 1fe41c8 | 2021-07-28 20:52:40 +0100 | [diff] [blame] | 110 | manifest_entry = entry |
| 111 | break |
| 112 | if manifest_entry is None: |
| 113 | if soft_fail: |
| 114 | return None |
| 115 | raise Exception( |
| 116 | ('No prebuilts available for %s-%s\n' % (plat, machine)) + |
| 117 | 'See https://perfetto.dev/docs/contributing/build-instructions') |
| 118 | |
| 119 | return download_or_get_cached( |
| 120 | file_name=manifest_entry['file_name'], |
| 121 | url=manifest_entry['url'], |
| 122 | sha256=manifest_entry['sha256']) |