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