blob: 5890d92aa55adff8d780946ac7ca433737eab902 [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
Mike Frysinger2e169a92022-04-27 02:15:09 -040091 compression = cros_build_lib.CompressionDetectType(archive)
92 compressor = cros_build_lib.FindCompressor(compression)
93
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070094 with osutils.TempDir(sudo_rm=True) as tempdir:
Alex Klein161495b2019-09-26 16:59:46 -060095 cros_build_lib.sudo_run(
Mike Frysingerb89977d2022-08-18 21:53:26 -040096 ['tar', '-I', compressor, '-xf', archive, '-C', tempdir],
97 debug_level=logging.DEBUG, capture_output=True)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070098
99 with open(self._vartree.getpath(cpv, filename='CONTENTS'),
100 'a') as content_file:
101 # Merge the content of the temporary dir into the sysroot.
102 # pylint: disable=protected-access
103 link = self._vartree.dbapi._dblink(cpv)
104 link.mergeme(tempdir, self._sysroot, content_file, None, '', {}, None)
105
106
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700107def ShouldGetSymbols(cpv, vardb, remote_symbols):
108 """Return True if the symbols for cpv are available and are not installed.
109
110 We try to check if the symbols are installed before checking availability as
111 a GS request is more expensive than checking locally.
112
113 Args:
114 cpv: cpv of the package
115 vardb: a vartree dbapi
116 remote_symbols: a mapping from cpv to debug symbols url
117
118 Returns:
119 True if |cpv|'s debug symbols are not installed and are available
120 """
121 features, contents = vardb.aux_get(cpv, ['FEATURES', 'CONTENTS'])
122
Alex Klein161495b2019-09-26 16:59:46 -0600123 return ('separatedebug' in features and not '/usr/lib/debug/' in contents and
124 cpv in remote_symbols)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700125
126
127def RemoteSymbols(vartree, binhost_cache=None):
128 """Get the cpv to debug symbols mapping.
129
130 If several binhost contain debug symbols for the same cpv, keep only the
131 highest priority one.
132
133 Args:
134 vartree: a vartree
135 binhost_cache: a cache containing the cpv to debug symbols url for all
136 known binhosts. None if we are not caching binhosts.
137
138 Returns:
139 a dictionary mapping the cpv to a remote debug symbols gsurl.
140 """
141 symbols_mapping = {}
142 for binhost in vartree.settings['PORTAGE_BINHOST'].split():
143 if binhost:
144 symbols_mapping.update(ListBinhost(binhost, binhost_cache))
145 return symbols_mapping
146
147
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800148def GetPackageIndex(binhost, binhost_cache=None):
149 """Get the packages index for |binhost|.
150
151 If a cache is provided, use it to a cache remote packages index.
152
153 Args:
154 binhost: a portage binhost, local, google storage or http.
155 binhost_cache: a cache for the remote packages index.
156
157 Returns:
158 A PackageIndex object.
159 """
160 key = binhost.split('://')[-1]
161 key = key.rstrip('/').split('/')
162
163 if binhost_cache and binhost_cache.Lookup(key).Exists():
Mike Frysinger8e99b372019-12-05 19:05:02 -0500164 with open(binhost_cache.Lookup(key).path, 'rb') as f:
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800165 return pickle.load(f)
166
Alex Klein82963df2022-08-24 08:45:58 -0600167 pkgindex = binpkg.GrabRemotePackageIndex(binhost)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800168 if pkgindex and binhost_cache:
169 # Only cache remote binhosts as local binhosts can change.
170 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
171 pickle.dump(pkgindex, temp_file)
172 temp_file.file.close()
173 binhost_cache.Lookup(key).Assign(temp_file.name)
174 elif pkgindex is None:
Mike Frysinger3dcacee2019-08-23 17:09:11 -0400175 urlparts = urllib.parse.urlsplit(binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800176 if urlparts.scheme not in ('file', ''):
177 # Don't fail the build on network errors. Print a warning message and
178 # continue.
Lann Martinffb95162018-08-28 12:02:54 -0600179 logging.warning('Could not get package index %s', binhost)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800180 return None
181
182 binhost = urlparts.path
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800183 if not os.path.isdir(binhost):
184 raise ValueError('unrecognized binhost format for %s.')
185 pkgindex = binpkg.GrabLocalPackageIndex(binhost)
186
187 return pkgindex
188
189
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700190def ListBinhost(binhost, binhost_cache=None):
191 """Return the cpv to debug symbols mapping for a given binhost.
192
193 List the content of the binhost to extract the cpv to debug symbols
194 mapping. If --cachebinhost is set, we cache the result to avoid the
195 cost of gsutil every time.
196
197 Args:
198 binhost: a portage binhost, local or on google storage.
199 binhost_cache: a cache containing mappings cpv to debug symbols url for a
200 given binhost (None if we don't want to cache).
201
202 Returns:
203 A cpv to debug symbols url mapping.
204 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700205
206 symbols = {}
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800207 pkgindex = GetPackageIndex(binhost, binhost_cache)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800208 if pkgindex is None:
209 return symbols
210
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700211 for p in pkgindex.packages:
212 if p.get('DEBUG_SYMBOLS') == 'yes':
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800213 path = p.get('PATH', p['CPV'] + '.tbz2')
214 base_url = pkgindex.header.get('URI', binhost)
215 symbols[p['CPV']] = os.path.join(base_url,
216 path.replace('.tbz2', DEBUG_SYMS_EXT))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700217
218 return symbols
219
220
221def GetMatchingCPV(package, vardb):
222 """Return the cpv of the installed package matching |package|.
223
224 Args:
225 package: package name
226 vardb: a vartree dbapi
227
228 Returns:
229 The cpv of the installed package whose name matchex |package|.
230 """
231 matches = vardb.match(package)
232 if not matches:
233 cros_build_lib.Die('Could not find package %s' % package)
234 if len(matches) != 1:
235 cros_build_lib.Die('Ambiguous package name: %s.\n'
236 'Matching: %s' % (package, ' '.join(matches)))
237 return matches[0]
238
239
Alex Klein161495b2019-09-26 16:59:46 -0600240def GetVartree(sysroot):
241 """Get the portage vartree.
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700242
Alex Klein161495b2019-09-26 16:59:46 -0600243 This must be called in subprocesses only. The vartree is not serializable,
244 and is implemented as a singleton in portage, so it always breaks the
245 parallel call when initialized before it is called.
246 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700247 trees = create_trees(target_root=sysroot, config_root=sysroot)
Alex Klein161495b2019-09-26 16:59:46 -0600248 return trees[sysroot]['vartree']
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700249
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700250
Alex Klein161495b2019-09-26 16:59:46 -0600251def GetBinhostCache(options):
252 """Get and optionally clear the binhost cache."""
Gilad Arnold83233ed2015-05-08 12:12:13 -0700253 cache_dir = os.path.join(path_util.FindCacheDir(),
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800254 'cros_install_debug_syms-v' + CACHE_VERSION)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700255 if options.clearcache:
256 osutils.RmDir(cache_dir, ignore_missing=True)
257
258 binhost_cache = None
259 if options.cachebinhost:
260 binhost_cache = cache.DiskCache(cache_dir)
261
Alex Klein161495b2019-09-26 16:59:46 -0600262 return binhost_cache
Bertrand SIMONNETbef192e2014-12-17 14:09:16 -0800263
Alex Klein161495b2019-09-26 16:59:46 -0600264
265def GetInstallArgs(options, sysroot):
266 """Resolve the packages that are to have their debug symbols installed.
267
268 This function must be called in subprocesses only since it requires the
269 portage vartree.
270 See: GetVartree
271
272 Returns:
273 list[(pkg, binhost_url)]
274 """
275 vartree = GetVartree(sysroot)
276 symbols_mapping = RemoteSymbols(vartree, GetBinhostCache(options))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700277
278 if options.all:
279 to_install = vartree.dbapi.cpv_all()
280 else:
281 to_install = [GetMatchingCPV(p, vartree.dbapi) for p in options.packages]
282
Alex Klein161495b2019-09-26 16:59:46 -0600283 to_install = [
284 p for p in to_install
285 if ShouldGetSymbols(p, vartree.dbapi, symbols_mapping)
286 ]
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700287
Alex Klein161495b2019-09-26 16:59:46 -0600288 return [(p, symbols_mapping[p]) for p in to_install]
289
290
291def ListInstallArgs(options, sysroot):
292 """List the args for the calling process."""
293 lines = ['%s %s' % arg for arg in GetInstallArgs(options, sysroot)]
294 print('\n'.join(lines))
295
296
297def GetInstallArgsList(argv):
298 """Get the install args from the --list reexec of the command."""
Alex Klein209066a2020-05-11 16:36:54 -0600299 # Insert the --list as the first argument to prevent parsing --list as a
300 # package when a package is given.
301 cmd = [argv[0]] + ['--list'] + argv[1:]
Mike Frysinger8e99b372019-12-05 19:05:02 -0500302 result = cros_build_lib.run(cmd, capture_output=True, encoding='utf-8')
Mike Frysinger876a8e52022-06-23 18:07:30 -0400303 lines = result.stdout.splitlines()
Alex Klein161495b2019-09-26 16:59:46 -0600304 return [line.split() for line in lines if line]
305
306
307def _InstallOne(sysroot, debug, args):
308 """Parallelizable wrapper for the DebugSymbolsInstaller.Install method."""
309 vartree = GetVartree(sysroot)
310 gs_context = gs.GSContext(boto_file=vartree.settings['BOTO_CONFIG'])
311 with DebugSymbolsInstaller(vartree, gs_context, sysroot,
312 not debug) as installer:
313 installer.Install(*args)
314
315
316def GetParser():
317 """Build the argument parser."""
318 parser = commandline.ArgumentParser(description=__doc__)
319 parser.add_argument('--board', help='Board name (required).', required=True)
320 parser.add_argument(
321 '--all',
322 action='store_true',
323 default=False,
324 help='Install the debug symbols for all installed packages')
325 parser.add_argument(
326 'packages',
327 nargs=argparse.REMAINDER,
328 help='list of packages that need the debug symbols.')
329
330 advanced = parser.add_argument_group('Advanced options')
331 advanced.add_argument(
332 '--nocachebinhost',
333 dest='cachebinhost',
334 default=True,
335 action='store_false',
336 help="Don't cache the list of files contained in binhosts. "
337 '(Default: cache)')
338 advanced.add_argument(
339 '--clearcache',
340 action='store_true',
341 default=False,
342 help='Clear the binhost cache.')
343 advanced.add_argument(
344 '-j',
345 '--jobs',
346 default=multiprocessing.cpu_count(),
347 type=int,
348 help='Number of processes to run in parallel.')
349 advanced.add_argument(
350 '--list',
351 action='store_true',
352 default=False,
353 help='List the packages whose debug symbols would be installed and '
354 'their binhost path.')
355
356 return parser
357
358
359def ParseArgs(argv):
360 """Parse and validate arguments."""
361 parser = GetParser()
362
363 options = parser.parse_args(argv)
364 if options.all and options.packages:
365 parser.error('Cannot use --all with a list of packages')
366
367 options.Freeze()
368
369 return options
370
371
372def main(argv):
373 if not cros_build_lib.IsInsideChroot():
374 raise commandline.ChrootRequiredError(argv)
375
Mike Frysinger2b1fed52022-04-27 02:15:33 -0400376 options = ParseArgs(argv)
377
Alex Klein161495b2019-09-26 16:59:46 -0600378 cmd = [os.path.join(constants.CHROMITE_BIN_DIR,
379 'cros_install_debug_syms')] + argv
Ram Chandrasekar69751282022-02-25 21:07:36 +0000380 if osutils.IsNonRootUser():
Alex Klein161495b2019-09-26 16:59:46 -0600381 cros_build_lib.sudo_run(cmd)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700382 return
383
Alex Klein161495b2019-09-26 16:59:46 -0600384 # sysroot must have a trailing / as the tree dictionary produced by
385 # create_trees in indexed with a trailing /.
Mike Frysinger06a51c82021-04-06 11:39:17 -0400386 sysroot = build_target_lib.get_default_sysroot_path(options.board) + '/'
Alex Klein161495b2019-09-26 16:59:46 -0600387
388 if options.list:
389 ListInstallArgs(options, sysroot)
390 return
391
392 args = GetInstallArgsList(cmd)
393
394 if not args:
395 logging.info('No packages found needing debug symbols.')
396 return
397
398 # Partial to simplify the arguments to parallel since the first two are the
399 # same for every call.
400 partial_install = functools.partial(_InstallOne, sysroot, options.debug)
Sergey Frolovde30ffe2022-06-06 17:18:03 -0600401 with multiprocessing.Pool(processes=options.jobs) as pool:
402 pool.map(partial_install, args)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700403
Ralph Nathan5a582ff2015-03-20 18:18:30 -0700404 logging.debug('installation done, updating packages index file')
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700405 packages_dir = os.path.join(sysroot, 'packages')
406 packages_file = os.path.join(packages_dir, 'Packages')
407 # binpkg will set DEBUG_SYMBOLS automatically if it detects the debug symbols
408 # in the packages dir.
409 pkgindex = binpkg.GrabLocalPackageIndex(packages_dir)
410 with open(packages_file, 'w') as p:
411 pkgindex.Write(p)