blob: 48e78b96afbe010e637507b5c0e63d7d4200f162 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -07002# Copyright 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Install debug symbols for specified packages.
7
8Only reinstall the debug symbols if they are not already installed to save time.
9
10The debug symbols are packaged outside of the prebuilt package in a
11.debug.tbz2 archive when FEATURES=separatedebug is set (by default on
12builders). On local machines, separatedebug is not set and the debug symbols
13are part of the prebuilt package.
14"""
15
16from __future__ import print_function
17
18import argparse
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070019import os
20import pickle
21import sys
22import tempfile
Mike Frysinger3dcacee2019-08-23 17:09:11 -040023
24from six.moves import urllib
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070025
26from chromite.lib import binpkg
27from chromite.lib import cache
28from chromite.lib import commandline
29from chromite.lib import cros_build_lib
Ralph Nathan91874ca2015-03-19 13:29:41 -070030from chromite.lib import cros_logging as logging
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070031from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070032from chromite.lib import path_util
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070033from chromite.lib import gs
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080034
35if cros_build_lib.IsInsideChroot():
Mike Frysinger27e21b72018-07-12 14:20:21 -040036 # pylint: disable=import-error
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080037 from portage import create_trees
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070038
39
40DEBUG_SYMS_EXT = '.debug.tbz2'
41
42
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080043# We cache the package indexes. When the format of what we store changes,
44# bump the cache version to avoid problems.
45CACHE_VERSION = '1'
46
47
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070048class DebugSymbolsInstaller(object):
49 """Container for enviromnent objects, needed to make multiprocessing work.
50
51 This also redirects stdout to null when stdout_to_null=True to avoid
52 polluting the output with portage QA warnings.
53 """
54 _old_stdout = None
55 _null = None
56
57 def __init__(self, vartree, gs_context, sysroot, stdout_to_null):
58 self._vartree = vartree
59 self._gs_context = gs_context
60 self._sysroot = sysroot
61 self._stdout_to_null = stdout_to_null
62
63 def __enter__(self):
64 if self._stdout_to_null:
65 self._old_stdout = sys.stdout
66 self._null = open(os.devnull, 'w')
67 sys.stdout = self._null
68 return self
69
70 def __exit__(self, _exc_type, _exc_val, _exc_tb):
71 if self._stdout_to_null:
72 sys.stdout = self._old_stdout
73 self._null.close()
74
75 def Install(self, cpv, url):
76 """Install the debug symbols for |cpv|.
77
78 This will install the debug symbols tarball in PKGDIR so that it can be
79 used later.
80
81 Args:
82 cpv: the cpv of the package to build. This assumes that the cpv is
83 installed in the sysroot.
84 url: url of the debug symbols archive. This could be a Google Storage url
85 or a local path.
86 """
87 archive = os.path.join(self._vartree.settings['PKGDIR'],
88 cpv + DEBUG_SYMS_EXT)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080089 # GsContext does not understand file:// scheme so we need to extract the
90 # path ourselves.
Mike Frysinger3dcacee2019-08-23 17:09:11 -040091 parsed_url = urllib.parse.urlsplit(url)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080092 if not parsed_url.scheme or parsed_url.scheme == 'file':
93 url = parsed_url.path
94
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070095 if not os.path.isfile(archive):
96 self._gs_context.Copy(url, archive, debug_level=logging.DEBUG)
97
98 with osutils.TempDir(sudo_rm=True) as tempdir:
Mike Frysinger45602c72019-09-22 02:15:11 -040099 cros_build_lib.sudo_run(['tar', '-I', 'bzip2 -q', '-xf', archive,
100 '-C', tempdir], quiet=True)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700101
102 with open(self._vartree.getpath(cpv, filename='CONTENTS'),
103 'a') as content_file:
104 # Merge the content of the temporary dir into the sysroot.
105 # pylint: disable=protected-access
106 link = self._vartree.dbapi._dblink(cpv)
107 link.mergeme(tempdir, self._sysroot, content_file, None, '', {}, None)
108
109
110def ParseArgs(argv):
111 """Parse arguments and initialize field.
112
113 Args:
114 argv: arguments passed to the script.
115 """
116 parser = commandline.ArgumentParser(description=__doc__)
117 parser.add_argument('--board', help='Board name (required).', required=True)
118 parser.add_argument('--all', dest='all', action='store_true',
119 help='Install the debug symbols for all installed '
120 'packages', default=False)
121 parser.add_argument('packages', nargs=argparse.REMAINDER,
122 help='list of packages that need the debug symbols.')
123
124 advanced = parser.add_argument_group('Advanced options')
125 advanced.add_argument('--nocachebinhost', dest='cachebinhost', default=True,
126 action='store_false', help="Don't cache the list of"
Mike Frysinger80de5012019-08-01 14:10:53 -0400127 ' files contained in binhosts. (Default: cache)')
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700128 advanced.add_argument('--clearcache', dest='clearcache', action='store_true',
129 default=False, help='Clear the binhost cache.')
130 advanced.add_argument('--jobs', default=None, type=int,
131 help='Number of processes to run in parallel.')
132
133 options = parser.parse_args(argv)
134 options.Freeze()
135
136 if options.all and options.packages:
137 cros_build_lib.Die('Cannot use --all with a list of packages')
138 return options
139
140
141def ShouldGetSymbols(cpv, vardb, remote_symbols):
142 """Return True if the symbols for cpv are available and are not installed.
143
144 We try to check if the symbols are installed before checking availability as
145 a GS request is more expensive than checking locally.
146
147 Args:
148 cpv: cpv of the package
149 vardb: a vartree dbapi
150 remote_symbols: a mapping from cpv to debug symbols url
151
152 Returns:
153 True if |cpv|'s debug symbols are not installed and are available
154 """
155 features, contents = vardb.aux_get(cpv, ['FEATURES', 'CONTENTS'])
156
157 return ('separatedebug' in features and not '/usr/lib/debug/' in contents
158 and cpv in remote_symbols)
159
160
161def RemoteSymbols(vartree, binhost_cache=None):
162 """Get the cpv to debug symbols mapping.
163
164 If several binhost contain debug symbols for the same cpv, keep only the
165 highest priority one.
166
167 Args:
168 vartree: a vartree
169 binhost_cache: a cache containing the cpv to debug symbols url for all
170 known binhosts. None if we are not caching binhosts.
171
172 Returns:
173 a dictionary mapping the cpv to a remote debug symbols gsurl.
174 """
175 symbols_mapping = {}
176 for binhost in vartree.settings['PORTAGE_BINHOST'].split():
177 if binhost:
178 symbols_mapping.update(ListBinhost(binhost, binhost_cache))
179 return symbols_mapping
180
181
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800182def GetPackageIndex(binhost, binhost_cache=None):
183 """Get the packages index for |binhost|.
184
185 If a cache is provided, use it to a cache remote packages index.
186
187 Args:
188 binhost: a portage binhost, local, google storage or http.
189 binhost_cache: a cache for the remote packages index.
190
191 Returns:
192 A PackageIndex object.
193 """
194 key = binhost.split('://')[-1]
195 key = key.rstrip('/').split('/')
196
197 if binhost_cache and binhost_cache.Lookup(key).Exists():
198 with open(binhost_cache.Lookup(key).path) as f:
199 return pickle.load(f)
200
201 pkgindex = binpkg.GrabRemotePackageIndex(binhost)
202 if pkgindex and binhost_cache:
203 # Only cache remote binhosts as local binhosts can change.
204 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
205 pickle.dump(pkgindex, temp_file)
206 temp_file.file.close()
207 binhost_cache.Lookup(key).Assign(temp_file.name)
208 elif pkgindex is None:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400209 urlparts = urllib.parse.urlsplit(binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800210 if urlparts.scheme not in ('file', ''):
211 # Don't fail the build on network errors. Print a warning message and
212 # continue.
Lann Martinffb95162018-08-28 12:02:54 -0600213 logging.warning('Could not get package index %s', binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800214 return None
215
216 binhost = urlparts.path
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800217 if not os.path.isdir(binhost):
218 raise ValueError('unrecognized binhost format for %s.')
219 pkgindex = binpkg.GrabLocalPackageIndex(binhost)
220
221 return pkgindex
222
223
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700224def ListBinhost(binhost, binhost_cache=None):
225 """Return the cpv to debug symbols mapping for a given binhost.
226
227 List the content of the binhost to extract the cpv to debug symbols
228 mapping. If --cachebinhost is set, we cache the result to avoid the
229 cost of gsutil every time.
230
231 Args:
232 binhost: a portage binhost, local or on google storage.
233 binhost_cache: a cache containing mappings cpv to debug symbols url for a
234 given binhost (None if we don't want to cache).
235
236 Returns:
237 A cpv to debug symbols url mapping.
238 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700239
240 symbols = {}
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800241 pkgindex = GetPackageIndex(binhost, binhost_cache)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800242 if pkgindex is None:
243 return symbols
244
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700245 for p in pkgindex.packages:
246 if p.get('DEBUG_SYMBOLS') == 'yes':
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800247 path = p.get('PATH', p['CPV'] + '.tbz2')
248 base_url = pkgindex.header.get('URI', binhost)
249 symbols[p['CPV']] = os.path.join(base_url,
250 path.replace('.tbz2', DEBUG_SYMS_EXT))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700251
252 return symbols
253
254
255def GetMatchingCPV(package, vardb):
256 """Return the cpv of the installed package matching |package|.
257
258 Args:
259 package: package name
260 vardb: a vartree dbapi
261
262 Returns:
263 The cpv of the installed package whose name matchex |package|.
264 """
265 matches = vardb.match(package)
266 if not matches:
267 cros_build_lib.Die('Could not find package %s' % package)
268 if len(matches) != 1:
269 cros_build_lib.Die('Ambiguous package name: %s.\n'
270 'Matching: %s' % (package, ' '.join(matches)))
271 return matches[0]
272
273
274def main(argv):
275 options = ParseArgs(argv)
276
Bertrand SIMONNET01394c32015-02-09 17:20:25 -0800277 if not cros_build_lib.IsInsideChroot():
Mike Frysingera620b8e2018-07-14 00:59:58 -0400278 raise commandline.ChrootRequiredError(argv)
Bertrand SIMONNET01394c32015-02-09 17:20:25 -0800279
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700280 if os.geteuid() != 0:
Mike Frysinger45602c72019-09-22 02:15:11 -0400281 cros_build_lib.sudo_run(sys.argv)
Bertrand SIMONNET01394c32015-02-09 17:20:25 -0800282 return
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700283
284 # sysroot must have a trailing / as the tree dictionary produced by
285 # create_trees in indexed with a trailing /.
286 sysroot = cros_build_lib.GetSysroot(options.board) + '/'
287 trees = create_trees(target_root=sysroot, config_root=sysroot)
288
289 vartree = trees[sysroot]['vartree']
290
Gilad Arnold83233ed2015-05-08 12:12:13 -0700291 cache_dir = os.path.join(path_util.FindCacheDir(),
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800292 'cros_install_debug_syms-v' + CACHE_VERSION)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700293
294 if options.clearcache:
295 osutils.RmDir(cache_dir, ignore_missing=True)
296
297 binhost_cache = None
298 if options.cachebinhost:
299 binhost_cache = cache.DiskCache(cache_dir)
300
Bertrand SIMONNETbef192e2014-12-17 14:09:16 -0800301 boto_file = vartree.settings['BOTO_CONFIG']
302 if boto_file:
303 os.environ['BOTO_CONFIG'] = boto_file
304
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700305 gs_context = gs.GSContext()
306 symbols_mapping = RemoteSymbols(vartree, binhost_cache)
307
308 if options.all:
309 to_install = vartree.dbapi.cpv_all()
310 else:
311 to_install = [GetMatchingCPV(p, vartree.dbapi) for p in options.packages]
312
313 to_install = [p for p in to_install
314 if ShouldGetSymbols(p, vartree.dbapi, symbols_mapping)]
315
316 if not to_install:
Ralph Nathan03047282015-03-23 11:09:32 -0700317 logging.info('nothing to do, exit')
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700318 return
319
320 with DebugSymbolsInstaller(vartree, gs_context, sysroot,
321 not options.debug) as installer:
322 args = [(p, symbols_mapping[p]) for p in to_install]
Allen Webb89e50af2018-12-21 10:28:34 -0800323 # TODO(crbug.com/917405) revert the hack and restore parallel functionality
324 # parallel.RunTasksInProcessPool(installer.Install, args,
325 # processes=options.jobs)
326 for arg in args:
327 installer.Install(*arg)
328
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700329
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700330 logging.debug('installation done, updating packages index file')
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700331 packages_dir = os.path.join(sysroot, 'packages')
332 packages_file = os.path.join(packages_dir, 'Packages')
333 # binpkg will set DEBUG_SYMBOLS automatically if it detects the debug symbols
334 # in the packages dir.
335 pkgindex = binpkg.GrabLocalPackageIndex(packages_dir)
336 with open(packages_file, 'w') as p:
337 pkgindex.Write(p)