blob: d8d171d4c9ff63f695776c4b6848048dea1a797e [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
32from portage import create_trees
33
34
35DEBUG_SYMS_EXT = '.debug.tbz2'
36
37
38class DebugSymbolsInstaller(object):
39 """Container for enviromnent objects, needed to make multiprocessing work.
40
41 This also redirects stdout to null when stdout_to_null=True to avoid
42 polluting the output with portage QA warnings.
43 """
44 _old_stdout = None
45 _null = None
46
47 def __init__(self, vartree, gs_context, sysroot, stdout_to_null):
48 self._vartree = vartree
49 self._gs_context = gs_context
50 self._sysroot = sysroot
51 self._stdout_to_null = stdout_to_null
52
53 def __enter__(self):
54 if self._stdout_to_null:
55 self._old_stdout = sys.stdout
56 self._null = open(os.devnull, 'w')
57 sys.stdout = self._null
58 return self
59
60 def __exit__(self, _exc_type, _exc_val, _exc_tb):
61 if self._stdout_to_null:
62 sys.stdout = self._old_stdout
63 self._null.close()
64
65 def Install(self, cpv, url):
66 """Install the debug symbols for |cpv|.
67
68 This will install the debug symbols tarball in PKGDIR so that it can be
69 used later.
70
71 Args:
72 cpv: the cpv of the package to build. This assumes that the cpv is
73 installed in the sysroot.
74 url: url of the debug symbols archive. This could be a Google Storage url
75 or a local path.
76 """
77 archive = os.path.join(self._vartree.settings['PKGDIR'],
78 cpv + DEBUG_SYMS_EXT)
79 if not os.path.isfile(archive):
80 self._gs_context.Copy(url, archive, debug_level=logging.DEBUG)
81
82 with osutils.TempDir(sudo_rm=True) as tempdir:
83 cros_build_lib.SudoRunCommand(['tar', '-I', 'bzip2 -q', '-xf', archive,
84 '-C', tempdir], quiet=True)
85
86 with open(self._vartree.getpath(cpv, filename='CONTENTS'),
87 'a') as content_file:
88 # Merge the content of the temporary dir into the sysroot.
89 # pylint: disable=protected-access
90 link = self._vartree.dbapi._dblink(cpv)
91 link.mergeme(tempdir, self._sysroot, content_file, None, '', {}, None)
92
93
94def ParseArgs(argv):
95 """Parse arguments and initialize field.
96
97 Args:
98 argv: arguments passed to the script.
99 """
100 parser = commandline.ArgumentParser(description=__doc__)
101 parser.add_argument('--board', help='Board name (required).', required=True)
102 parser.add_argument('--all', dest='all', action='store_true',
103 help='Install the debug symbols for all installed '
104 'packages', default=False)
105 parser.add_argument('packages', nargs=argparse.REMAINDER,
106 help='list of packages that need the debug symbols.')
107
108 advanced = parser.add_argument_group('Advanced options')
109 advanced.add_argument('--nocachebinhost', dest='cachebinhost', default=True,
110 action='store_false', help="Don't cache the list of"
111 " files contained in binhosts. (Default: cache)")
112 advanced.add_argument('--clearcache', dest='clearcache', action='store_true',
113 default=False, help='Clear the binhost cache.')
114 advanced.add_argument('--jobs', default=None, type=int,
115 help='Number of processes to run in parallel.')
116
117 options = parser.parse_args(argv)
118 options.Freeze()
119
120 if options.all and options.packages:
121 cros_build_lib.Die('Cannot use --all with a list of packages')
122 return options
123
124
125def ShouldGetSymbols(cpv, vardb, remote_symbols):
126 """Return True if the symbols for cpv are available and are not installed.
127
128 We try to check if the symbols are installed before checking availability as
129 a GS request is more expensive than checking locally.
130
131 Args:
132 cpv: cpv of the package
133 vardb: a vartree dbapi
134 remote_symbols: a mapping from cpv to debug symbols url
135
136 Returns:
137 True if |cpv|'s debug symbols are not installed and are available
138 """
139 features, contents = vardb.aux_get(cpv, ['FEATURES', 'CONTENTS'])
140
141 return ('separatedebug' in features and not '/usr/lib/debug/' in contents
142 and cpv in remote_symbols)
143
144
145def RemoteSymbols(vartree, binhost_cache=None):
146 """Get the cpv to debug symbols mapping.
147
148 If several binhost contain debug symbols for the same cpv, keep only the
149 highest priority one.
150
151 Args:
152 vartree: a vartree
153 binhost_cache: a cache containing the cpv to debug symbols url for all
154 known binhosts. None if we are not caching binhosts.
155
156 Returns:
157 a dictionary mapping the cpv to a remote debug symbols gsurl.
158 """
159 symbols_mapping = {}
160 for binhost in vartree.settings['PORTAGE_BINHOST'].split():
161 if binhost:
162 symbols_mapping.update(ListBinhost(binhost, binhost_cache))
163 return symbols_mapping
164
165
166def ListBinhost(binhost, binhost_cache=None):
167 """Return the cpv to debug symbols mapping for a given binhost.
168
169 List the content of the binhost to extract the cpv to debug symbols
170 mapping. If --cachebinhost is set, we cache the result to avoid the
171 cost of gsutil every time.
172
173 Args:
174 binhost: a portage binhost, local or on google storage.
175 binhost_cache: a cache containing mappings cpv to debug symbols url for a
176 given binhost (None if we don't want to cache).
177
178 Returns:
179 A cpv to debug symbols url mapping.
180 """
181 key = binhost.split('://')[-1]
182 key = key.rstrip('/').split('/')
183
Bertrand SIMONNET8e537282014-12-04 15:04:42 -0800184 should_cache = True
185
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700186 if binhost_cache and binhost_cache.Lookup(key).Exists():
187 with open(binhost_cache.Lookup(key).path) as f:
188 return pickle.load(f)
189
190 pkgindex = binpkg.GrabRemotePackageIndex(binhost)
191 if pkgindex is None:
Bertrand SIMONNET8e537282014-12-04 15:04:42 -0800192 binhost = urlparse.urlsplit(binhost).path
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700193 if not os.path.isdir(binhost):
194 raise ValueError('unrecognized binhost format for %s. Supported formats: '
195 'http, gs or local path.' % binhost)
196 pkgindex = binpkg.GrabLocalPackageIndex(binhost)
Bertrand SIMONNET8e537282014-12-04 15:04:42 -0800197 # If the package index is local, do not cache as the package index may
198 # change later (for example when reusing packages from other board).
199 should_cache = False
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700200
201 symbols = {}
202 for p in pkgindex.packages:
203 if p.get('DEBUG_SYMBOLS') == 'yes':
204 path = p.get('PATH', os.path.join(binhost, p['CPV'] + '.tbz2'))
205 symbols[p['CPV']] = path.replace('.tbz2', '.debug.tbz2')
206
Bertrand SIMONNET8e537282014-12-04 15:04:42 -0800207 if binhost_cache and should_cache:
Bertrand SIMONNET6b6a1312014-10-29 18:37:51 -0700208 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
209 pickle.dump(symbols, temp_file)
210 temp_file.file.close()
211 binhost_cache.Lookup(key).Assign(temp_file.name)
212
213 return symbols
214
215
216def GetMatchingCPV(package, vardb):
217 """Return the cpv of the installed package matching |package|.
218
219 Args:
220 package: package name
221 vardb: a vartree dbapi
222
223 Returns:
224 The cpv of the installed package whose name matchex |package|.
225 """
226 matches = vardb.match(package)
227 if not matches:
228 cros_build_lib.Die('Could not find package %s' % package)
229 if len(matches) != 1:
230 cros_build_lib.Die('Ambiguous package name: %s.\n'
231 'Matching: %s' % (package, ' '.join(matches)))
232 return matches[0]
233
234
235def main(argv):
236 options = ParseArgs(argv)
237
238 cros_build_lib.AssertInsideChroot()
239 if os.geteuid() != 0:
240 cros_build_lib.Die('This script must be ran as root.')
241
242 # sysroot must have a trailing / as the tree dictionary produced by
243 # create_trees in indexed with a trailing /.
244 sysroot = cros_build_lib.GetSysroot(options.board) + '/'
245 trees = create_trees(target_root=sysroot, config_root=sysroot)
246
247 vartree = trees[sysroot]['vartree']
248
249 cache_dir = os.path.join(commandline.BaseParser.FindCacheDir(None, None),
250 'cros_install_debug_syms')
251
252 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
259 gs_context = gs.GSContext()
260 symbols_mapping = RemoteSymbols(vartree, binhost_cache)
261
262 if options.all:
263 to_install = vartree.dbapi.cpv_all()
264 else:
265 to_install = [GetMatchingCPV(p, vartree.dbapi) for p in options.packages]
266
267 to_install = [p for p in to_install
268 if ShouldGetSymbols(p, vartree.dbapi, symbols_mapping)]
269
270 if not to_install:
271 cros_build_lib.Info('nothing to do, exit')
272 return
273
274 with DebugSymbolsInstaller(vartree, gs_context, sysroot,
275 not options.debug) as installer:
276 args = [(p, symbols_mapping[p]) for p in to_install]
277 parallel.RunTasksInProcessPool(installer.Install, args,
278 processes=options.jobs)
279
280 cros_build_lib.Debug('installation done, updating packages index file')
281 packages_dir = os.path.join(sysroot, 'packages')
282 packages_file = os.path.join(packages_dir, 'Packages')
283 # binpkg will set DEBUG_SYMBOLS automatically if it detects the debug symbols
284 # in the packages dir.
285 pkgindex = binpkg.GrabLocalPackageIndex(packages_dir)
286 with open(packages_file, 'w') as p:
287 pkgindex.Write(p)