blob: aea3ae9e8ffeb9995573c5c896600bff23ea4462 [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
15from __future__ import print_function
16
17import argparse
18import logging
19import os
20import pickle
21import sys
22import tempfile
Bertrand SIMONNET8e537282014-12-04 15:04:42 -080023import urlparse
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070024
25from chromite.lib import binpkg
26from chromite.lib import cache
27from chromite.lib import commandline
28from chromite.lib import cros_build_lib
29from chromite.lib import osutils
30from chromite.lib import parallel
31from chromite.lib import gs
Bertrand SIMONNET01394c32015-02-09 17:20:25 -080032
33if cros_build_lib.IsInsideChroot():
34 from portage import create_trees
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070035
36
37DEBUG_SYMS_EXT = '.debug.tbz2'
38
39
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080040# We cache the package indexes. When the format of what we store changes,
41# bump the cache version to avoid problems.
42CACHE_VERSION = '1'
43
44
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070045class DebugSymbolsInstaller(object):
46 """Container for enviromnent objects, needed to make multiprocessing work.
47
48 This also redirects stdout to null when stdout_to_null=True to avoid
49 polluting the output with portage QA warnings.
50 """
51 _old_stdout = None
52 _null = None
53
54 def __init__(self, vartree, gs_context, sysroot, stdout_to_null):
55 self._vartree = vartree
56 self._gs_context = gs_context
57 self._sysroot = sysroot
58 self._stdout_to_null = stdout_to_null
59
60 def __enter__(self):
61 if self._stdout_to_null:
62 self._old_stdout = sys.stdout
63 self._null = open(os.devnull, 'w')
64 sys.stdout = self._null
65 return self
66
67 def __exit__(self, _exc_type, _exc_val, _exc_tb):
68 if self._stdout_to_null:
69 sys.stdout = self._old_stdout
70 self._null.close()
71
72 def Install(self, cpv, url):
73 """Install the debug symbols for |cpv|.
74
75 This will install the debug symbols tarball in PKGDIR so that it can be
76 used later.
77
78 Args:
79 cpv: the cpv of the package to build. This assumes that the cpv is
80 installed in the sysroot.
81 url: url of the debug symbols archive. This could be a Google Storage url
82 or a local path.
83 """
84 archive = os.path.join(self._vartree.settings['PKGDIR'],
85 cpv + DEBUG_SYMS_EXT)
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -080086 # GsContext does not understand file:// scheme so we need to extract the
87 # path ourselves.
88 parsed_url = urlparse.urlsplit(url)
89 if not parsed_url.scheme or parsed_url.scheme == 'file':
90 url = parsed_url.path
91
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -070092 if not os.path.isfile(archive):
93 self._gs_context.Copy(url, archive, debug_level=logging.DEBUG)
94
95 with osutils.TempDir(sudo_rm=True) as tempdir:
96 cros_build_lib.SudoRunCommand(['tar', '-I', 'bzip2 -q', '-xf', archive,
97 '-C', tempdir], quiet=True)
98
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
107def ParseArgs(argv):
108 """Parse arguments and initialize field.
109
110 Args:
111 argv: arguments passed to the script.
112 """
113 parser = commandline.ArgumentParser(description=__doc__)
114 parser.add_argument('--board', help='Board name (required).', required=True)
115 parser.add_argument('--all', dest='all', action='store_true',
116 help='Install the debug symbols for all installed '
117 'packages', default=False)
118 parser.add_argument('packages', nargs=argparse.REMAINDER,
119 help='list of packages that need the debug symbols.')
120
121 advanced = parser.add_argument_group('Advanced options')
122 advanced.add_argument('--nocachebinhost', dest='cachebinhost', default=True,
123 action='store_false', help="Don't cache the list of"
124 " files contained in binhosts. (Default: cache)")
125 advanced.add_argument('--clearcache', dest='clearcache', action='store_true',
126 default=False, help='Clear the binhost cache.')
127 advanced.add_argument('--jobs', default=None, type=int,
128 help='Number of processes to run in parallel.')
129
130 options = parser.parse_args(argv)
131 options.Freeze()
132
133 if options.all and options.packages:
134 cros_build_lib.Die('Cannot use --all with a list of packages')
135 return options
136
137
138def ShouldGetSymbols(cpv, vardb, remote_symbols):
139 """Return True if the symbols for cpv are available and are not installed.
140
141 We try to check if the symbols are installed before checking availability as
142 a GS request is more expensive than checking locally.
143
144 Args:
145 cpv: cpv of the package
146 vardb: a vartree dbapi
147 remote_symbols: a mapping from cpv to debug symbols url
148
149 Returns:
150 True if |cpv|'s debug symbols are not installed and are available
151 """
152 features, contents = vardb.aux_get(cpv, ['FEATURES', 'CONTENTS'])
153
154 return ('separatedebug' in features and not '/usr/lib/debug/' in contents
155 and cpv in remote_symbols)
156
157
158def RemoteSymbols(vartree, binhost_cache=None):
159 """Get the cpv to debug symbols mapping.
160
161 If several binhost contain debug symbols for the same cpv, keep only the
162 highest priority one.
163
164 Args:
165 vartree: a vartree
166 binhost_cache: a cache containing the cpv to debug symbols url for all
167 known binhosts. None if we are not caching binhosts.
168
169 Returns:
170 a dictionary mapping the cpv to a remote debug symbols gsurl.
171 """
172 symbols_mapping = {}
173 for binhost in vartree.settings['PORTAGE_BINHOST'].split():
174 if binhost:
175 symbols_mapping.update(ListBinhost(binhost, binhost_cache))
176 return symbols_mapping
177
178
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800179def GetPackageIndex(binhost, binhost_cache=None):
180 """Get the packages index for |binhost|.
181
182 If a cache is provided, use it to a cache remote packages index.
183
184 Args:
185 binhost: a portage binhost, local, google storage or http.
186 binhost_cache: a cache for the remote packages index.
187
188 Returns:
189 A PackageIndex object.
190 """
191 key = binhost.split('://')[-1]
192 key = key.rstrip('/').split('/')
193
194 if binhost_cache and binhost_cache.Lookup(key).Exists():
195 with open(binhost_cache.Lookup(key).path) as f:
196 return pickle.load(f)
197
198 pkgindex = binpkg.GrabRemotePackageIndex(binhost)
199 if pkgindex and binhost_cache:
200 # Only cache remote binhosts as local binhosts can change.
201 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
202 pickle.dump(pkgindex, temp_file)
203 temp_file.file.close()
204 binhost_cache.Lookup(key).Assign(temp_file.name)
205 elif pkgindex is None:
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800206 urlparts = urlparse.urlsplit(binhost)
207 if urlparts.scheme not in ('file', ''):
208 # Don't fail the build on network errors. Print a warning message and
209 # continue.
210 cros_build_lib.Warning('Could not get package index %s' % binhost)
211 return None
212
213 binhost = urlparts.path
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800214 if not os.path.isdir(binhost):
215 raise ValueError('unrecognized binhost format for %s.')
216 pkgindex = binpkg.GrabLocalPackageIndex(binhost)
217
218 return pkgindex
219
220
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700221def ListBinhost(binhost, binhost_cache=None):
222 """Return the cpv to debug symbols mapping for a given binhost.
223
224 List the content of the binhost to extract the cpv to debug symbols
225 mapping. If --cachebinhost is set, we cache the result to avoid the
226 cost of gsutil every time.
227
228 Args:
229 binhost: a portage binhost, local or on google storage.
230 binhost_cache: a cache containing mappings cpv to debug symbols url for a
231 given binhost (None if we don't want to cache).
232
233 Returns:
234 A cpv to debug symbols url mapping.
235 """
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700236
237 symbols = {}
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800238 pkgindex = GetPackageIndex(binhost, binhost_cache)
Bertrand SIMONNETdd66ab52015-01-08 14:48:51 -0800239 if pkgindex is None:
240 return symbols
241
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700242 for p in pkgindex.packages:
243 if p.get('DEBUG_SYMBOLS') == 'yes':
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800244 path = p.get('PATH', p['CPV'] + '.tbz2')
245 base_url = pkgindex.header.get('URI', binhost)
246 symbols[p['CPV']] = os.path.join(base_url,
247 path.replace('.tbz2', DEBUG_SYMS_EXT))
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700248
249 return symbols
250
251
252def GetMatchingCPV(package, vardb):
253 """Return the cpv of the installed package matching |package|.
254
255 Args:
256 package: package name
257 vardb: a vartree dbapi
258
259 Returns:
260 The cpv of the installed package whose name matchex |package|.
261 """
262 matches = vardb.match(package)
263 if not matches:
264 cros_build_lib.Die('Could not find package %s' % package)
265 if len(matches) != 1:
266 cros_build_lib.Die('Ambiguous package name: %s.\n'
267 'Matching: %s' % (package, ' '.join(matches)))
268 return matches[0]
269
270
271def main(argv):
272 options = ParseArgs(argv)
273
Bertrand SIMONNET01394c32015-02-09 17:20:25 -0800274 if not cros_build_lib.IsInsideChroot():
275 raise commandline.ChrootRequiredError()
276
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700277 if os.geteuid() != 0:
Bertrand SIMONNET01394c32015-02-09 17:20:25 -0800278 cros_build_lib.SudoRunCommand(sys.argv)
279 return
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700280
281 # sysroot must have a trailing / as the tree dictionary produced by
282 # create_trees in indexed with a trailing /.
283 sysroot = cros_build_lib.GetSysroot(options.board) + '/'
284 trees = create_trees(target_root=sysroot, config_root=sysroot)
285
286 vartree = trees[sysroot]['vartree']
287
288 cache_dir = os.path.join(commandline.BaseParser.FindCacheDir(None, None),
Bertrand SIMONNET1e146e52014-12-11 14:11:56 -0800289 'cros_install_debug_syms-v' + CACHE_VERSION)
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700290
291 if options.clearcache:
292 osutils.RmDir(cache_dir, ignore_missing=True)
293
294 binhost_cache = None
295 if options.cachebinhost:
296 binhost_cache = cache.DiskCache(cache_dir)
297
Bertrand SIMONNETbef192e2014-12-17 14:09:16 -0800298 boto_file = vartree.settings['BOTO_CONFIG']
299 if boto_file:
300 os.environ['BOTO_CONFIG'] = boto_file
301
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700302 gs_context = gs.GSContext()
303 symbols_mapping = RemoteSymbols(vartree, binhost_cache)
304
305 if options.all:
306 to_install = vartree.dbapi.cpv_all()
307 else:
308 to_install = [GetMatchingCPV(p, vartree.dbapi) for p in options.packages]
309
310 to_install = [p for p in to_install
311 if ShouldGetSymbols(p, vartree.dbapi, symbols_mapping)]
312
313 if not to_install:
314 cros_build_lib.Info('nothing to do, exit')
315 return
316
317 with DebugSymbolsInstaller(vartree, gs_context, sysroot,
318 not options.debug) as installer:
319 args = [(p, symbols_mapping[p]) for p in to_install]
320 parallel.RunTasksInProcessPool(installer.Install, args,
321 processes=options.jobs)
322
323 cros_build_lib.Debug('installation done, updating packages index file')
324 packages_dir = os.path.join(sysroot, 'packages')
325 packages_file = os.path.join(packages_dir, 'Packages')
326 # binpkg will set DEBUG_SYMBOLS automatically if it detects the debug symbols
327 # in the packages dir.
328 pkgindex = binpkg.GrabLocalPackageIndex(packages_dir)
329 with open(packages_file, 'w') as p:
330 pkgindex.Write(p)