Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
merge_and_upload_debug_symbols.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2#
3# Copyright 2013 The Flutter Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7""" Merges the debug symbols and uploads them to cipd.
8"""
9
10import argparse
11import collections
12import json
13import os
14import platform
15import re
16import shutil
17import subprocess
18import sys
19import tarfile
20import tempfile
21
22# Path to the engine root checkout. This is used to calculate absolute
23# paths if relative ones are passed to the script.
24BUILD_ROOT_DIR = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..'))
25
26
27def IsLinux():
28 return platform.system() == 'Linux'
29
30
31# out_dir here is of the format "/b/s/w/ir/k/recipe_cleanup/tmpIbWDdp"
32# we need to place the cipd definition in this directory.
33def GetPackagingDir(out_dir):
34 return os.path.abspath(out_dir)
35
36
37def CreateCIPDDefinition(target_arch, out_dir, symbol_dirs):
38 dir_name = os.path.basename(os.path.normpath(out_dir))
39 pkg_def = """
40package: flutter/fuchsia-debug-symbols-%s
41description: Flutter and Dart runner debug symbols for Fuchsia. Target architecture %s.
42install_mode: copy
43data:
44""" % (target_arch, target_arch)
45 for symbol_dir in symbol_dirs:
46 symbol_dir_name = os.path.basename(os.path.normpath(symbol_dir))
47 data = '\n - dir: %s' % (symbol_dir_name)
48 pkg_def = pkg_def + data
49 return pkg_def
50
51
52# CIPD CLI needs the definition and data directory to be relative to each other.
53def WriteCIPDDefinition(target_arch, out_dir, symbol_dirs):
54 _packaging_dir = GetPackagingDir(out_dir)
55 yaml_file = os.path.join(_packaging_dir, 'debug_symbols.cipd.yaml')
56 with open(yaml_file, 'w') as f:
57 cipd_def = CreateCIPDDefinition(target_arch, out_dir, symbol_dirs)
58 f.write(cipd_def)
59 return yaml_file
60
61
62def CheckCIPDPackageExists(package_name, tag):
63 '''Check to see if the current package/tag combo has been published'''
64 command = [
65 'cipd',
66 'search',
67 package_name,
68 '-tag',
69 tag,
70 ]
71 stdout = subprocess.check_output(command)
72 stdout = stdout if isinstance(stdout, str) else stdout.decode('UTF-8')
73 match = re.search(r'No matching instances\.', stdout)
74 if match:
75 return False
76 else:
77 return True
78
79
80def ProcessCIPDPackage(upload, cipd_yaml, engine_version, out_dir, target_arch):
81 _packaging_dir = GetPackagingDir(out_dir)
82 tag = 'git_revision:%s' % engine_version
83 package_name = 'flutter/fuchsia-debug-symbols-%s' % target_arch
84 already_exists = CheckCIPDPackageExists(package_name, tag)
85 if already_exists:
86 print('CIPD package %s tag %s already exists!' % (package_name, tag))
87
88 if upload and IsLinux() and not already_exists:
89 command = [
90 'cipd',
91 'create',
92 '-pkg-def',
93 cipd_yaml,
94 '-ref',
95 'latest',
96 '-tag',
97 tag,
98 '-verification-timeout',
99 '10m0s',
100 ]
101 else:
102 command = [
103 'cipd', 'pkg-build', '-pkg-def', cipd_yaml, '-out',
104 os.path.join(_packaging_dir, 'fuchsia-debug-symbols-%s.cipd' % target_arch)
105 ]
106
107 # Retry up to three times. We've seen CIPD fail on verification in some
108 # instances. Normally verification takes slightly more than 1 minute when
109 # it succeeds.
110 num_tries = 3
111 for tries in range(num_tries):
112 try:
113 subprocess.check_call(command, cwd=_packaging_dir)
114 break
115 except subprocess.CalledProcessError as error:
116 print('Failed %s times.\nError was: %s' % (tries + 1, error))
117 if tries == num_tries - 1:
118 raise
119
120
121# Recursively hardlinks contents from one directory to another,
122# skipping over collisions.
123def HardlinkContents(dirA, dirB):
124 internal_symbol_dirs = []
125 for src_dir, _, filenames in os.walk(dirA):
126 for filename in filenames:
127 # if a file contains 'dbg_success' in its name, it is a stamp file.
128 # An example of this would be
129 # '._dart_jit_runner_dbg_symbols_unstripped_dbg_success' these
130 # are generated by GN and have to be ignored.
131 if 'dbg_success' in filename:
132 continue
133 src = os.path.join(src_dir, filename)
134 dest_dir = os.path.join(dirB, os.path.relpath(src_dir, dirA))
135 try:
136 os.makedirs(dest_dir)
137 internal_symbol_dirs.append(dest_dir)
138 except:
139 pass
140 dest = os.path.join(dest_dir, filename)
141 if os.path.exists(dest):
142 # The last two path components provide a content address for a .build-id entry.
143 tokens = os.path.split(dest)
144 name = os.path.join(tokens[-2], tokens[-1])
145 print('%s already exists in destination; skipping linking' % name)
146 continue
147 os.link(src, dest)
148 return internal_symbol_dirs
149
150
152 results = []
153 for directory in dirs:
154 if os.path.isabs(directory):
155 results.append(directory)
156 else:
157 results.append(os.path.join(BUILD_ROOT_DIR, directory))
158 return results
159
160
161def main():
162 parser = argparse.ArgumentParser()
163
164 parser.add_argument(
165 '--symbol-dirs',
166 required=True,
167 nargs='+',
168 help='Space separated list of directories that contain the debug symbols.'
169 )
170 parser.add_argument(
171 '--out-dir',
172 action='store',
173 dest='out_dir',
174 default=tempfile.mkdtemp(),
175 help=(
176 'Output directory where the executables will be placed defaults to an '
177 'empty temp directory'
178 )
179 )
180 parser.add_argument('--target-arch', type=str, choices=['x64', 'arm64'], required=True)
181 parser.add_argument('--engine-version', required=True, help='Specifies the flutter engine SHA.')
182
183 parser.add_argument('--upload', default=False, action='store_true')
184
185 args = parser.parse_args()
186
187 symbol_dirs = CalculateAbsoluteDirs(args.symbol_dirs)
188 for symbol_dir in symbol_dirs:
189 assert os.path.exists(symbol_dir) and os.path.isdir(symbol_dir)
190
191 out_dir = args.out_dir
192
193 if os.path.exists(out_dir):
194 print('Directory: %s is not empty, deleting it.' % out_dir)
195 shutil.rmtree(out_dir)
196 os.makedirs(out_dir)
197
198 internal_symbol_dirs = []
199 for symbol_dir in symbol_dirs:
200 internal_symbol_dirs += HardlinkContents(symbol_dir, out_dir)
201
202 # make these unique
203 internal_symbol_dirs = list(set(internal_symbol_dirs))
204
205 arch = args.target_arch
206 cipd_def = WriteCIPDDefinition(arch, out_dir, internal_symbol_dirs)
207
208 # Set revision to HEAD if empty and remove upload. This is to support
209 # presubmit workflows. An empty engine_version means this script is running
210 # on presubmit.
211 should_upload = args.upload
212 engine_version = args.engine_version
213 if not engine_version:
214 engine_version = 'HEAD'
215 should_upload = False
216
217 ProcessCIPDPackage(should_upload, cipd_def, engine_version, out_dir, arch)
218 return 0
219
220
221if __name__ == '__main__':
222 sys.exit(main())
void print(void *str)
Definition bridge.cpp:126
Definition main.py:1
WriteCIPDDefinition(target_arch, out_dir, symbol_dirs)
ProcessCIPDPackage(upload, cipd_yaml, engine_version, out_dir, target_arch)
CreateCIPDDefinition(target_arch, out_dir, symbol_dirs)