blob: d4f0bc1d0af78ce159bd4688264a88b498222533 [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
Alex Klein161495b2019-09-26 16:59:46 -060019import functools
20import multiprocessing
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070021import os
22import pickle
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070023import tempfile
Mike Frysinger3dcacee2019-08-23 17:09:11 -040024
25from six.moves import urllib
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070026
27from chromite.lib import binpkg
28from chromite.lib import cache
29from chromite.lib import commandline
Alex Klein161495b2019-09-26 16:59:46 -060030from chromite.lib import constants
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070031from chromite.lib import cros_build_lib
Ralph Nathan91874ca2015-03-19 13:29:41 -070032from chromite.lib import cros_logging as logging
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070033from chromite.lib import osutils
Gilad Arnold83233ed2015-05-08 12:12:13 -070034from chromite.lib import path_util
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070035from chromite.lib import gs
Mike Frysinger151f5fb2019-10-22 20:36:25 -040036from chromite.utils import outcap
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080037
38if cros_build_lib.IsInsideChroot():
Mike Frysinger27e21b72018-07-12 14:20:21 -040039 # pylint: disable=import-error
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080040 from portage import create_trees
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070041
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070042DEBUG_SYMS_EXT = '.debug.tbz2'
43
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080044# We cache the package indexes. When the format of what we store changes,
45# bump the cache version to avoid problems.
46CACHE_VERSION = '1'
47
48
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070049class DebugSymbolsInstaller(object):
Mike Frysinger151f5fb2019-10-22 20:36:25 -040050 """Container for enviromnent objects, needed to make multiprocessing work."""
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070051
52 def __init__(self, vartree, gs_context, sysroot, stdout_to_null):
53 self._vartree = vartree
54 self._gs_context = gs_context
55 self._sysroot = sysroot
56 self._stdout_to_null = stdout_to_null
Mike Frysinger151f5fb2019-10-22 20:36:25 -040057 self._capturer = outcap.OutputCapturer()
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070058
59 def __enter__(self):
60 if self._stdout_to_null:
Mike Frysinger151f5fb2019-10-22 20:36:25 -040061 self._capturer.StartCapturing()
62
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070063 return self
64
65 def __exit__(self, _exc_type, _exc_val, _exc_tb):
66 if self._stdout_to_null:
Mike Frysinger151f5fb2019-10-22 20:36:25 -040067 self._capturer.StopCapturing()
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070068
69 def Install(self, cpv, url):
70 """Install the debug symbols for |cpv|.
71
72 This will install the debug symbols tarball in PKGDIR so that it can be
73 used later.
74
75 Args:
76 cpv: the cpv of the package to build. This assumes that the cpv is
77 installed in the sysroot.
78 url: url of the debug symbols archive. This could be a Google Storage url
79 or a local path.
80 """
81 archive = os.path.join(self._vartree.settings['PKGDIR'],
82 cpv + DEBUG_SYMS_EXT)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080083 # GsContext does not understand file:// scheme so we need to extract the
84 # path ourselves.
Mike Frysinger3dcacee2019-08-23 17:09:11 -040085 parsed_url = urllib.parse.urlsplit(url)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080086 if not parsed_url.scheme or parsed_url.scheme == 'file':
87 url = parsed_url.path
88
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070089 if not os.path.isfile(archive):
90 self._gs_context.Copy(url, archive, debug_level=logging.DEBUG)
91
92 with osutils.TempDir(sudo_rm=True) as tempdir:
Alex Klein161495b2019-09-26 16:59:46 -060093 cros_build_lib.sudo_run(
94 ['tar', '-I', 'bzip2 -q', '-xf', archive, '-C', tempdir], quiet=True)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070095
96 with open(self._vartree.getpath(cpv, filename='CONTENTS'),
97 'a') as content_file:
98 # Merge the content of the temporary dir into the sysroot.
99 # pylint: disable=protected-access
100 link = self._vartree.dbapi._dblink(cpv)
101 link.mergeme(tempdir, self._sysroot, content_file, None, '', {}, None)
102
103
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700104def ShouldGetSymbols(cpv, vardb, remote_symbols):
105 """Return True if the symbols for cpv are available and are not installed.
106
107 We try to check if the symbols are installed before checking availability as
108 a GS request is more expensive than checking locally.
109
110 Args:
111 cpv: cpv of the package
112 vardb: a vartree dbapi
113 remote_symbols: a mapping from cpv to debug symbols url
114
115 Returns:
116 True if |cpv|'s debug symbols are not installed and are available
117 """
118 features, contents = vardb.aux_get(cpv, ['FEATURES', 'CONTENTS'])
119
Alex Klein161495b2019-09-26 16:59:46 -0600120 return ('separatedebug' in features and not '/usr/lib/debug/' in contents and
121 cpv in remote_symbols)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700122
123
124def RemoteSymbols(vartree, binhost_cache=None):
125 """Get the cpv to debug symbols mapping.
126
127 If several binhost contain debug symbols for the same cpv, keep only the
128 highest priority one.
129
130 Args:
131 vartree: a vartree
132 binhost_cache: a cache containing the cpv to debug symbols url for all
133 known binhosts. None if we are not caching binhosts.
134
135 Returns:
136 a dictionary mapping the cpv to a remote debug symbols gsurl.
137 """
138 symbols_mapping = {}
139 for binhost in vartree.settings['PORTAGE_BINHOST'].split():
140 if binhost:
141 symbols_mapping.update(ListBinhost(binhost, binhost_cache))
142 return symbols_mapping
143
144
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800145def GetPackageIndex(binhost, binhost_cache=None):
146 """Get the packages index for |binhost|.
147
148 If a cache is provided, use it to a cache remote packages index.
149
150 Args:
151 binhost: a portage binhost, local, google storage or http.
152 binhost_cache: a cache for the remote packages index.
153
154 Returns:
155 A PackageIndex object.
156 """
157 key = binhost.split('://')[-1]
158 key = key.rstrip('/').split('/')
159
160 if binhost_cache and binhost_cache.Lookup(key).Exists():
Mike Frysinger8e99b372019-12-05 19:05:02 -0500161 with open(binhost_cache.Lookup(key).path, 'rb') as f:
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800162 return pickle.load(f)
163
Alex Klein161495b2019-09-26 16:59:46 -0600164 pkgindex = binpkg.GrabRemotePackageIndex(binhost, quiet=True)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800165 if pkgindex and binhost_cache:
166 # Only cache remote binhosts as local binhosts can change.
167 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
168 pickle.dump(pkgindex, temp_file)
169 temp_file.file.close()
170 binhost_cache.Lookup(key).Assign(temp_file.name)
171 elif pkgindex is None:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400172 urlparts = urllib.parse.urlsplit(binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800173 if urlparts.scheme not in ('file', ''):
174 # Don't fail the build on network errors. Print a warning message and
175 # continue.
Lann Martinffb95162018-08-28 12:02:54 -0600176 logging.warning('Could not get package index %s', binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800177 return None
178
179 binhost = urlparts.path
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800180 if not os.path.isdir(binhost):
181 raise ValueError('unrecognized binhost format for %s.')
182 pkgindex = binpkg.GrabLocalPackageIndex(binhost)
183
184 return pkgindex
185
186
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700187def ListBinhost(binhost, binhost_cache=None):
188 """Return the cpv to debug symbols mapping for a given binhost.
189
190 List the content of the binhost to extract the cpv to debug symbols
191 mapping. If --cachebinhost is set, we cache the result to avoid the
192 cost of gsutil every time.
193
194 Args:
195 binhost: a portage binhost, local or on google storage.
196 binhost_cache: a cache containing mappings cpv to debug symbols url for a
197 given binhost (None if we don't want to cache).
198
199 Returns:
200 A cpv to debug symbols url mapping.
201 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700202
203 symbols = {}
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800204 pkgindex = GetPackageIndex(binhost, binhost_cache)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800205 if pkgindex is None:
206 return symbols
207
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700208 for p in pkgindex.packages:
209 if p.get('DEBUG_SYMBOLS') == 'yes':
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800210 path = p.get('PATH', p['CPV'] + '.tbz2')
211 base_url = pkgindex.header.get('URI', binhost)
212 symbols[p['CPV']] = os.path.join(base_url,
213 path.replace('.tbz2', DEBUG_SYMS_EXT))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700214
215 return symbols
216
217
218def GetMatchingCPV(package, vardb):
219 """Return the cpv of the installed package matching |package|.
220
221 Args:
222 package: package name
223 vardb: a vartree dbapi
224
225 Returns:
226 The cpv of the installed package whose name matchex |package|.
227 """
228 matches = vardb.match(package)
229 if not matches:
230 cros_build_lib.Die('Could not find package %s' % package)
231 if len(matches) != 1:
232 cros_build_lib.Die('Ambiguous package name: %s.\n'
233 'Matching: %s' % (package, ' '.join(matches)))
234 return matches[0]
235
236
Alex Klein161495b2019-09-26 16:59:46 -0600237def GetVartree(sysroot):
238 """Get the portage vartree.
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700239
Alex Klein161495b2019-09-26 16:59:46 -0600240 This must be called in subprocesses only. The vartree is not serializable,
241 and is implemented as a singleton in portage, so it always breaks the
242 parallel call when initialized before it is called.
243 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700244 trees = create_trees(target_root=sysroot, config_root=sysroot)
Alex Klein161495b2019-09-26 16:59:46 -0600245 return trees[sysroot]['vartree']
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700246
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700247
Alex Klein161495b2019-09-26 16:59:46 -0600248def GetBinhostCache(options):
249 """Get and optionally clear the binhost cache."""
Gilad Arnold83233ed2015-05-08 12:12:13 -0700250 cache_dir = os.path.join(path_util.FindCacheDir(),
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800251 'cros_install_debug_syms-v' + CACHE_VERSION)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700252 if options.clearcache:
253 osutils.RmDir(cache_dir, ignore_missing=True)
254
255 binhost_cache = None
256 if options.cachebinhost:
257 binhost_cache = cache.DiskCache(cache_dir)
258
Alex Klein161495b2019-09-26 16:59:46 -0600259 return binhost_cache
Bertrand SIMONNETbef192e2014-12-17 14:09:16 -0800260
Alex Klein161495b2019-09-26 16:59:46 -0600261
262def GetInstallArgs(options, sysroot):
263 """Resolve the packages that are to have their debug symbols installed.
264
265 This function must be called in subprocesses only since it requires the
266 portage vartree.
267 See: GetVartree
268
269 Returns:
270 list[(pkg, binhost_url)]
271 """
272 vartree = GetVartree(sysroot)
273 symbols_mapping = RemoteSymbols(vartree, GetBinhostCache(options))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700274
275 if options.all:
276 to_install = vartree.dbapi.cpv_all()
277 else:
278 to_install = [GetMatchingCPV(p, vartree.dbapi) for p in options.packages]
279
Alex Klein161495b2019-09-26 16:59:46 -0600280 to_install = [
281 p for p in to_install
282 if ShouldGetSymbols(p, vartree.dbapi, symbols_mapping)
283 ]
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700284
Alex Klein161495b2019-09-26 16:59:46 -0600285 return [(p, symbols_mapping[p]) for p in to_install]
286
287
288def ListInstallArgs(options, sysroot):
289 """List the args for the calling process."""
290 lines = ['%s %s' % arg for arg in GetInstallArgs(options, sysroot)]
291 print('\n'.join(lines))
292
293
294def GetInstallArgsList(argv):
295 """Get the install args from the --list reexec of the command."""
296 cmd = argv + ['--list']
Mike Frysinger8e99b372019-12-05 19:05:02 -0500297 result = cros_build_lib.run(cmd, capture_output=True, encoding='utf-8')
Alex Klein161495b2019-09-26 16:59:46 -0600298 lines = result.output.splitlines()
299 return [line.split() for line in lines if line]
300
301
302def _InstallOne(sysroot, debug, args):
303 """Parallelizable wrapper for the DebugSymbolsInstaller.Install method."""
304 vartree = GetVartree(sysroot)
305 gs_context = gs.GSContext(boto_file=vartree.settings['BOTO_CONFIG'])
306 with DebugSymbolsInstaller(vartree, gs_context, sysroot,
307 not debug) as installer:
308 installer.Install(*args)
309
310
311def GetParser():
312 """Build the argument parser."""
313 parser = commandline.ArgumentParser(description=__doc__)
314 parser.add_argument('--board', help='Board name (required).', required=True)
315 parser.add_argument(
316 '--all',
317 action='store_true',
318 default=False,
319 help='Install the debug symbols for all installed packages')
320 parser.add_argument(
321 'packages',
322 nargs=argparse.REMAINDER,
323 help='list of packages that need the debug symbols.')
324
325 advanced = parser.add_argument_group('Advanced options')
326 advanced.add_argument(
327 '--nocachebinhost',
328 dest='cachebinhost',
329 default=True,
330 action='store_false',
331 help="Don't cache the list of files contained in binhosts. "
332 '(Default: cache)')
333 advanced.add_argument(
334 '--clearcache',
335 action='store_true',
336 default=False,
337 help='Clear the binhost cache.')
338 advanced.add_argument(
339 '-j',
340 '--jobs',
341 default=multiprocessing.cpu_count(),
342 type=int,
343 help='Number of processes to run in parallel.')
344 advanced.add_argument(
345 '--list',
346 action='store_true',
347 default=False,
348 help='List the packages whose debug symbols would be installed and '
349 'their binhost path.')
350
351 return parser
352
353
354def ParseArgs(argv):
355 """Parse and validate arguments."""
356 parser = GetParser()
357
358 options = parser.parse_args(argv)
359 if options.all and options.packages:
360 parser.error('Cannot use --all with a list of packages')
361
362 options.Freeze()
363
364 return options
365
366
367def main(argv):
368 if not cros_build_lib.IsInsideChroot():
369 raise commandline.ChrootRequiredError(argv)
370
371 cmd = [os.path.join(constants.CHROMITE_BIN_DIR,
372 'cros_install_debug_syms')] + argv
373 if os.geteuid() != 0:
374 cros_build_lib.sudo_run(cmd)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700375 return
376
Alex Klein161495b2019-09-26 16:59:46 -0600377 options = ParseArgs(argv)
Allen Webb89e50af2018-12-21 10:28:34 -0800378
Alex Klein161495b2019-09-26 16:59:46 -0600379 # sysroot must have a trailing / as the tree dictionary produced by
380 # create_trees in indexed with a trailing /.
381 sysroot = cros_build_lib.GetSysroot(options.board) + '/'
382
383 if options.list:
384 ListInstallArgs(options, sysroot)
385 return
386
387 args = GetInstallArgsList(cmd)
388
389 if not args:
390 logging.info('No packages found needing debug symbols.')
391 return
392
393 # Partial to simplify the arguments to parallel since the first two are the
394 # same for every call.
395 partial_install = functools.partial(_InstallOne, sysroot, options.debug)
396 pool = multiprocessing.Pool(processes=options.jobs)
397 pool.map(partial_install, args)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700398
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700399 logging.debug('installation done, updating packages index file')
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700400 packages_dir = os.path.join(sysroot, 'packages')
401 packages_file = os.path.join(packages_dir, 'Packages')
402 # binpkg will set DEBUG_SYMBOLS automatically if it detects the debug symbols
403 # in the packages dir.
404 pkgindex = binpkg.GrabLocalPackageIndex(packages_dir)
405 with open(packages_file, 'w') as p:
406 pkgindex.Write(p)