blob: 8200b7f1506d7022a51dd9b09c686c6dcefd9743 [file] [log] [blame]
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -07001# Copyright 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Install debug symbols for specified packages.
6
7Only reinstall the debug symbols if they are not already installed to save time.
8
9The debug symbols are packaged outside of the prebuilt package in a
10.debug.tbz2 archive when FEATURES=separatedebug is set (by default on
11builders). On local machines, separatedebug is not set and the debug symbols
12are part of the prebuilt package.
13"""
14
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070015import argparse
Alex Klein161495b2019-09-26 16:59:46 -060016import functools
Chris McDonald59650c32021-07-20 15:29:28 -060017import logging
Alex Klein161495b2019-09-26 16:59:46 -060018import multiprocessing
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070019import os
20import pickle
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070021import tempfile
Mike Frysingere852b072021-05-21 12:39:03 -040022import urllib.parse
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070023
24from chromite.lib import binpkg
Mike Frysinger06a51c82021-04-06 11:39:17 -040025from chromite.lib import build_target_lib
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070026from chromite.lib import cache
27from chromite.lib import commandline
Alex Klein161495b2019-09-26 16:59:46 -060028from chromite.lib import constants
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070029from chromite.lib import cros_build_lib
Chris McDonald59650c32021-07-20 15:29:28 -060030from chromite.lib import gs
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
Mike Frysinger151f5fb2019-10-22 20:36:25 -040033from chromite.utils import outcap
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080034
Chris McDonald59650c32021-07-20 15:29:28 -060035
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080036if cros_build_lib.IsInsideChroot():
Mike Frysinger27e21b72018-07-12 14:20:21 -040037 # pylint: disable=import-error
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080038 from portage import create_trees
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070039
Mike Frysinger6a2b0f22020-02-20 13:34:07 -050040
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070041DEBUG_SYMS_EXT = '.debug.tbz2'
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):
Mike Frysinger151f5fb2019-10-22 20:36:25 -040049 """Container for enviromnent objects, needed to make multiprocessing work."""
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070050
51 def __init__(self, vartree, gs_context, sysroot, stdout_to_null):
52 self._vartree = vartree
53 self._gs_context = gs_context
54 self._sysroot = sysroot
55 self._stdout_to_null = stdout_to_null
Mike Frysinger151f5fb2019-10-22 20:36:25 -040056 self._capturer = outcap.OutputCapturer()
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070057
58 def __enter__(self):
59 if self._stdout_to_null:
Mike Frysinger151f5fb2019-10-22 20:36:25 -040060 self._capturer.StartCapturing()
61
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070062 return self
63
64 def __exit__(self, _exc_type, _exc_val, _exc_tb):
65 if self._stdout_to_null:
Mike Frysinger151f5fb2019-10-22 20:36:25 -040066 self._capturer.StopCapturing()
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070067
68 def Install(self, cpv, url):
69 """Install the debug symbols for |cpv|.
70
71 This will install the debug symbols tarball in PKGDIR so that it can be
72 used later.
73
74 Args:
75 cpv: the cpv of the package to build. This assumes that the cpv is
76 installed in the sysroot.
77 url: url of the debug symbols archive. This could be a Google Storage url
78 or a local path.
79 """
80 archive = os.path.join(self._vartree.settings['PKGDIR'],
81 cpv + DEBUG_SYMS_EXT)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080082 # GsContext does not understand file:// scheme so we need to extract the
83 # path ourselves.
Mike Frysinger3dcacee2019-08-23 17:09:11 -040084 parsed_url = urllib.parse.urlsplit(url)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080085 if not parsed_url.scheme or parsed_url.scheme == 'file':
86 url = parsed_url.path
87
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070088 if not os.path.isfile(archive):
89 self._gs_context.Copy(url, archive, debug_level=logging.DEBUG)
90
91 with osutils.TempDir(sudo_rm=True) as tempdir:
Alex Klein161495b2019-09-26 16:59:46 -060092 cros_build_lib.sudo_run(
93 ['tar', '-I', 'bzip2 -q', '-xf', archive, '-C', tempdir], quiet=True)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070094
95 with open(self._vartree.getpath(cpv, filename='CONTENTS'),
96 'a') as content_file:
97 # Merge the content of the temporary dir into the sysroot.
98 # pylint: disable=protected-access
99 link = self._vartree.dbapi._dblink(cpv)
100 link.mergeme(tempdir, self._sysroot, content_file, None, '', {}, None)
101
102
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700103def ShouldGetSymbols(cpv, vardb, remote_symbols):
104 """Return True if the symbols for cpv are available and are not installed.
105
106 We try to check if the symbols are installed before checking availability as
107 a GS request is more expensive than checking locally.
108
109 Args:
110 cpv: cpv of the package
111 vardb: a vartree dbapi
112 remote_symbols: a mapping from cpv to debug symbols url
113
114 Returns:
115 True if |cpv|'s debug symbols are not installed and are available
116 """
117 features, contents = vardb.aux_get(cpv, ['FEATURES', 'CONTENTS'])
118
Alex Klein161495b2019-09-26 16:59:46 -0600119 return ('separatedebug' in features and not '/usr/lib/debug/' in contents and
120 cpv in remote_symbols)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700121
122
123def RemoteSymbols(vartree, binhost_cache=None):
124 """Get the cpv to debug symbols mapping.
125
126 If several binhost contain debug symbols for the same cpv, keep only the
127 highest priority one.
128
129 Args:
130 vartree: a vartree
131 binhost_cache: a cache containing the cpv to debug symbols url for all
132 known binhosts. None if we are not caching binhosts.
133
134 Returns:
135 a dictionary mapping the cpv to a remote debug symbols gsurl.
136 """
137 symbols_mapping = {}
138 for binhost in vartree.settings['PORTAGE_BINHOST'].split():
139 if binhost:
140 symbols_mapping.update(ListBinhost(binhost, binhost_cache))
141 return symbols_mapping
142
143
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800144def GetPackageIndex(binhost, binhost_cache=None):
145 """Get the packages index for |binhost|.
146
147 If a cache is provided, use it to a cache remote packages index.
148
149 Args:
150 binhost: a portage binhost, local, google storage or http.
151 binhost_cache: a cache for the remote packages index.
152
153 Returns:
154 A PackageIndex object.
155 """
156 key = binhost.split('://')[-1]
157 key = key.rstrip('/').split('/')
158
159 if binhost_cache and binhost_cache.Lookup(key).Exists():
Mike Frysinger8e99b372019-12-05 19:05:02 -0500160 with open(binhost_cache.Lookup(key).path, 'rb') as f:
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800161 return pickle.load(f)
162
Alex Klein161495b2019-09-26 16:59:46 -0600163 pkgindex = binpkg.GrabRemotePackageIndex(binhost, quiet=True)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800164 if pkgindex and binhost_cache:
165 # Only cache remote binhosts as local binhosts can change.
166 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
167 pickle.dump(pkgindex, temp_file)
168 temp_file.file.close()
169 binhost_cache.Lookup(key).Assign(temp_file.name)
170 elif pkgindex is None:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400171 urlparts = urllib.parse.urlsplit(binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800172 if urlparts.scheme not in ('file', ''):
173 # Don't fail the build on network errors. Print a warning message and
174 # continue.
Lann Martinffb95162018-08-28 12:02:54 -0600175 logging.warning('Could not get package index %s', binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800176 return None
177
178 binhost = urlparts.path
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800179 if not os.path.isdir(binhost):
180 raise ValueError('unrecognized binhost format for %s.')
181 pkgindex = binpkg.GrabLocalPackageIndex(binhost)
182
183 return pkgindex
184
185
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700186def ListBinhost(binhost, binhost_cache=None):
187 """Return the cpv to debug symbols mapping for a given binhost.
188
189 List the content of the binhost to extract the cpv to debug symbols
190 mapping. If --cachebinhost is set, we cache the result to avoid the
191 cost of gsutil every time.
192
193 Args:
194 binhost: a portage binhost, local or on google storage.
195 binhost_cache: a cache containing mappings cpv to debug symbols url for a
196 given binhost (None if we don't want to cache).
197
198 Returns:
199 A cpv to debug symbols url mapping.
200 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700201
202 symbols = {}
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800203 pkgindex = GetPackageIndex(binhost, binhost_cache)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800204 if pkgindex is None:
205 return symbols
206
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700207 for p in pkgindex.packages:
208 if p.get('DEBUG_SYMBOLS') == 'yes':
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800209 path = p.get('PATH', p['CPV'] + '.tbz2')
210 base_url = pkgindex.header.get('URI', binhost)
211 symbols[p['CPV']] = os.path.join(base_url,
212 path.replace('.tbz2', DEBUG_SYMS_EXT))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700213
214 return symbols
215
216
217def GetMatchingCPV(package, vardb):
218 """Return the cpv of the installed package matching |package|.
219
220 Args:
221 package: package name
222 vardb: a vartree dbapi
223
224 Returns:
225 The cpv of the installed package whose name matchex |package|.
226 """
227 matches = vardb.match(package)
228 if not matches:
229 cros_build_lib.Die('Could not find package %s' % package)
230 if len(matches) != 1:
231 cros_build_lib.Die('Ambiguous package name: %s.\n'
232 'Matching: %s' % (package, ' '.join(matches)))
233 return matches[0]
234
235
Alex Klein161495b2019-09-26 16:59:46 -0600236def GetVartree(sysroot):
237 """Get the portage vartree.
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700238
Alex Klein161495b2019-09-26 16:59:46 -0600239 This must be called in subprocesses only. The vartree is not serializable,
240 and is implemented as a singleton in portage, so it always breaks the
241 parallel call when initialized before it is called.
242 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700243 trees = create_trees(target_root=sysroot, config_root=sysroot)
Alex Klein161495b2019-09-26 16:59:46 -0600244 return trees[sysroot]['vartree']
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700245
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700246
Alex Klein161495b2019-09-26 16:59:46 -0600247def GetBinhostCache(options):
248 """Get and optionally clear the binhost cache."""
Gilad Arnold83233ed2015-05-08 12:12:13 -0700249 cache_dir = os.path.join(path_util.FindCacheDir(),
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800250 'cros_install_debug_syms-v' + CACHE_VERSION)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700251 if options.clearcache:
252 osutils.RmDir(cache_dir, ignore_missing=True)
253
254 binhost_cache = None
255 if options.cachebinhost:
256 binhost_cache = cache.DiskCache(cache_dir)
257
Alex Klein161495b2019-09-26 16:59:46 -0600258 return binhost_cache
Bertrand SIMONNETbef192e2014-12-17 14:09:16 -0800259
Alex Klein161495b2019-09-26 16:59:46 -0600260
261def GetInstallArgs(options, sysroot):
262 """Resolve the packages that are to have their debug symbols installed.
263
264 This function must be called in subprocesses only since it requires the
265 portage vartree.
266 See: GetVartree
267
268 Returns:
269 list[(pkg, binhost_url)]
270 """
271 vartree = GetVartree(sysroot)
272 symbols_mapping = RemoteSymbols(vartree, GetBinhostCache(options))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700273
274 if options.all:
275 to_install = vartree.dbapi.cpv_all()
276 else:
277 to_install = [GetMatchingCPV(p, vartree.dbapi) for p in options.packages]
278
Alex Klein161495b2019-09-26 16:59:46 -0600279 to_install = [
280 p for p in to_install
281 if ShouldGetSymbols(p, vartree.dbapi, symbols_mapping)
282 ]
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700283
Alex Klein161495b2019-09-26 16:59:46 -0600284 return [(p, symbols_mapping[p]) for p in to_install]
285
286
287def ListInstallArgs(options, sysroot):
288 """List the args for the calling process."""
289 lines = ['%s %s' % arg for arg in GetInstallArgs(options, sysroot)]
290 print('\n'.join(lines))
291
292
293def GetInstallArgsList(argv):
294 """Get the install args from the --list reexec of the command."""
Alex Klein209066a2020-05-11 16:36:54 -0600295 # Insert the --list as the first argument to prevent parsing --list as a
296 # package when a package is given.
297 cmd = [argv[0]] + ['--list'] + argv[1:]
Mike Frysinger8e99b372019-12-05 19:05:02 -0500298 result = cros_build_lib.run(cmd, capture_output=True, encoding='utf-8')
Alex Klein161495b2019-09-26 16:59:46 -0600299 lines = result.output.splitlines()
300 return [line.split() for line in lines if line]
301
302
303def _InstallOne(sysroot, debug, args):
304 """Parallelizable wrapper for the DebugSymbolsInstaller.Install method."""
305 vartree = GetVartree(sysroot)
306 gs_context = gs.GSContext(boto_file=vartree.settings['BOTO_CONFIG'])
307 with DebugSymbolsInstaller(vartree, gs_context, sysroot,
308 not debug) as installer:
309 installer.Install(*args)
310
311
312def GetParser():
313 """Build the argument parser."""
314 parser = commandline.ArgumentParser(description=__doc__)
315 parser.add_argument('--board', help='Board name (required).', required=True)
316 parser.add_argument(
317 '--all',
318 action='store_true',
319 default=False,
320 help='Install the debug symbols for all installed packages')
321 parser.add_argument(
322 'packages',
323 nargs=argparse.REMAINDER,
324 help='list of packages that need the debug symbols.')
325
326 advanced = parser.add_argument_group('Advanced options')
327 advanced.add_argument(
328 '--nocachebinhost',
329 dest='cachebinhost',
330 default=True,
331 action='store_false',
332 help="Don't cache the list of files contained in binhosts. "
333 '(Default: cache)')
334 advanced.add_argument(
335 '--clearcache',
336 action='store_true',
337 default=False,
338 help='Clear the binhost cache.')
339 advanced.add_argument(
340 '-j',
341 '--jobs',
342 default=multiprocessing.cpu_count(),
343 type=int,
344 help='Number of processes to run in parallel.')
345 advanced.add_argument(
346 '--list',
347 action='store_true',
348 default=False,
349 help='List the packages whose debug symbols would be installed and '
350 'their binhost path.')
351
352 return parser
353
354
355def ParseArgs(argv):
356 """Parse and validate arguments."""
357 parser = GetParser()
358
359 options = parser.parse_args(argv)
360 if options.all and options.packages:
361 parser.error('Cannot use --all with a list of packages')
362
363 options.Freeze()
364
365 return options
366
367
368def main(argv):
369 if not cros_build_lib.IsInsideChroot():
370 raise commandline.ChrootRequiredError(argv)
371
372 cmd = [os.path.join(constants.CHROMITE_BIN_DIR,
373 'cros_install_debug_syms')] + argv
374 if os.geteuid() != 0:
375 cros_build_lib.sudo_run(cmd)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700376 return
377
Alex Klein161495b2019-09-26 16:59:46 -0600378 options = ParseArgs(argv)
Allen Webb89e50af2018-12-21 10:28:34 -0800379
Alex Klein161495b2019-09-26 16:59:46 -0600380 # sysroot must have a trailing / as the tree dictionary produced by
381 # create_trees in indexed with a trailing /.
Mike Frysinger06a51c82021-04-06 11:39:17 -0400382 sysroot = build_target_lib.get_default_sysroot_path(options.board) + '/'
Alex Klein161495b2019-09-26 16:59:46 -0600383
384 if options.list:
385 ListInstallArgs(options, sysroot)
386 return
387
388 args = GetInstallArgsList(cmd)
389
390 if not args:
391 logging.info('No packages found needing debug symbols.')
392 return
393
394 # Partial to simplify the arguments to parallel since the first two are the
395 # same for every call.
396 partial_install = functools.partial(_InstallOne, sysroot, options.debug)
397 pool = multiprocessing.Pool(processes=options.jobs)
398 pool.map(partial_install, args)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700399
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700400 logging.debug('installation done, updating packages index file')
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700401 packages_dir = os.path.join(sysroot, 'packages')
402 packages_file = os.path.join(packages_dir, 'Packages')
403 # binpkg will set DEBUG_SYMBOLS automatically if it detects the debug symbols
404 # in the packages dir.
405 pkgindex = binpkg.GrabLocalPackageIndex(packages_dir)
406 with open(packages_file, 'w') as p:
407 pkgindex.Write(p)