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