blob: 8b154e344da8bd3836687214ead8750c8e850700 [file] [log] [blame]
Jim Hebert91c052c2011-03-11 11:00:53 -08001# Copyright (c) 2010 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
Mike Frysinger012371e2019-01-03 03:42:32 -05005"""Command to extract the dependancy tree for a given package.
6
7This produces JSON output for other tools to process.
8"""
Don Garrett25f309a2014-03-19 14:02:12 -07009
Chris McDonaldd2fa6162019-07-30 15:30:58 -060010from __future__ import absolute_import
Mike Frysinger383367e2014-09-16 15:06:17 -040011
Chris McDonald59650c32021-07-20 15:29:28 -060012import logging
Mike Frysingera942aee2020-03-20 03:53:37 -040013import sys
Don Garrettf8bf7842014-03-20 17:03:42 -070014
Mike Frysinger06a51c82021-04-06 11:39:17 -040015from chromite.lib import build_target_lib
Jim Hebertcf870d72013-06-12 15:33:34 -070016from chromite.lib import commandline
17from chromite.lib import cros_build_lib
Mike Frysingerd0960812020-06-09 01:53:32 -040018from chromite.lib import pformat
Alex Klein5cdd57f2020-11-23 11:53:28 -070019from chromite.lib import sysroot_lib
Chris McDonald59650c32021-07-20 15:29:28 -060020from chromite.lib.depgraph import DepGraphGenerator
Alex Klein18a60af2020-06-11 12:08:47 -060021from chromite.lib.parser import package_info
Mike Frysingercc838832014-05-24 13:10:30 -040022
Chris McDonaldd8a7f112019-11-01 10:35:07 -060023
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070024def FlattenDepTree(deptree, pkgtable=None, parentcpv=None, get_cpe=False):
Don Garrett25f309a2014-03-19 14:02:12 -070025 """Simplify dependency json.
26
27Turn something like this (the parallel_emerge DepsTree format):
Jim Hebert91c052c2011-03-11 11:00:53 -080028{
29 "app-admin/eselect-1.2.9": {
30 "action": "merge",
31 "deps": {
32 "sys-apps/coreutils-7.5-r1": {
33 "action": "merge",
34 "deps": {},
35 "deptype": "runtime"
36 },
37 ...
38 }
39 }
40}
41 ...into something like this (the cros_extract_deps format):
42{
43 "app-admin/eselect-1.2.9": {
44 "deps": ["coreutils-7.5-r1"],
45 "rev_deps": [],
46 "name": "eselect",
47 "category": "app-admin",
48 "version": "1.2.9",
49 "full_name": "app-admin/eselect-1.2.9",
50 "action": "merge"
51 },
52 "sys-apps/coreutils-7.5-r1": {
53 "deps": [],
54 "rev_deps": ["app-admin/eselect-1.2.9"],
55 "name": "coreutils",
56 "category": "sys-apps",
57 "version": "7.5-r1",
58 "full_name": "sys-apps/coreutils-7.5-r1",
59 "action": "merge"
60 }
61}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070062
63 Args:
64 deptree: The dependency tree.
65 pkgtable: The package table to update. If None, create a new one.
66 parentcpv: The parent CPV.
67 get_cpe: If set True, include CPE in the flattened dependency tree.
68
69 Returns:
70 A flattened dependency tree.
Jim Hebert91c052c2011-03-11 11:00:53 -080071 """
David James1b363582012-12-17 11:53:11 -080072 if pkgtable is None:
73 pkgtable = {}
Mike Frysinger0bdbc102019-06-13 15:27:29 -040074 for cpv, record in deptree.items():
Jim Hebert91c052c2011-03-11 11:00:53 -080075 if cpv not in pkgtable:
Alex Klein18a60af2020-06-11 12:08:47 -060076 split = package_info.SplitCPV(cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070077 pkgtable[cpv] = {'deps': [],
78 'rev_deps': [],
Xuewei Zhang656f9932017-09-15 16:15:05 -070079 'name': split.package,
80 'category': split.category,
81 'version': '%s' % split.version,
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070082 'full_name': cpv,
83 'cpes': [],
84 'action': record['action']}
85 if get_cpe:
Xuewei Zhang656f9932017-09-15 16:15:05 -070086 pkgtable[cpv]['cpes'].extend(GetCPEFromCPV(
87 split.category, split.package, split.version_no_rev))
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070088
Jim Hebert91c052c2011-03-11 11:00:53 -080089 # If we have a parent, that is a rev_dep for the current package.
90 if parentcpv:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070091 pkgtable[cpv]['rev_deps'].append(parentcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080092 # If current package has any deps, record those.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070093 for childcpv in record['deps']:
94 pkgtable[cpv]['deps'].append(childcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080095 # Visit the subtree recursively as well.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070096 FlattenDepTree(record['deps'], pkgtable=pkgtable, parentcpv=cpv,
97 get_cpe=get_cpe)
Ned Nguyen3bbd2072019-01-28 19:33:41 -070098 # Sort 'deps' & 'rev_deps' alphabetically to make them more readable.
99 pkgtable[cpv]['deps'].sort()
100 pkgtable[cpv]['rev_deps'].sort()
Jim Hebert91c052c2011-03-11 11:00:53 -0800101 return pkgtable
102
103
Jim Hebertcf870d72013-06-12 15:33:34 -0700104def GetCPEFromCPV(category, package, version):
105 """Look up the CPE for a specified Portage package.
Jim Hebert91c052c2011-03-11 11:00:53 -0800106
Jim Hebertcf870d72013-06-12 15:33:34 -0700107 Args:
108 category: The Portage package's category, e.g. "net-misc"
109 package: The Portage package's name, e.g. "curl"
110 version: The Portage version, e.g. "7.30.0"
111
Mike Frysinger02e1e072013-11-10 22:11:34 -0500112 Returns:
113 A list of CPE Name strings, e.g.
114 ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
Jim Hebertcf870d72013-06-12 15:33:34 -0700115 """
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700116 equery_cmd = ['equery', 'm', '-U', '%s/%s' % (category, package)]
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -0500117 lines = cros_build_lib.run(equery_cmd, check=False, print_cmd=False,
Mike Frysinger44b83812019-12-10 00:09:30 -0500118 stdout=True, encoding='utf-8').stdout.splitlines()
Jim Hebertcf870d72013-06-12 15:33:34 -0700119 # Look for lines like "Remote-ID: cpe:/a:kernel:linux-pam ID: cpe"
120 # and extract the cpe URI.
121 cpes = []
122 for line in lines:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700123 if 'ID: cpe' not in line:
Jim Hebertcf870d72013-06-12 15:33:34 -0700124 continue
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700125 cpes.append('%s:%s' % (line.split()[1], version.replace('_', '')))
Jim Hebertcf870d72013-06-12 15:33:34 -0700126 # Note that we're assuming we can combine the root of the CPE, taken
127 # from metadata.xml, and tack on the version number as used by
128 # Portage, and come up with a legitimate CPE. This works so long as
129 # Portage and CPE agree on the precise formatting of the version
Jim Hebert96aff9c2013-07-16 15:43:17 -0700130 # number, which they almost always do. The major exception we've
131 # identified thus far is that our ebuilds have a pattern of inserting
132 # underscores prior to patchlevels, that neither upstream nor CPE
133 # use. For example, our code will decide we have
Jim Hebertcf870d72013-06-12 15:33:34 -0700134 # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
135 # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
136 # is "right" in this example, in that it matches www.sudo.ws.)
137 #
Jim Hebert96aff9c2013-07-16 15:43:17 -0700138 # Removing underscores seems to improve our chances of correctly
139 # arriving at the CPE used by NVD. However, at the end of the day,
140 # ebuild version numbers are rev'd by people who don't have "try to
141 # match NVD" as one of their goals, and there is always going to be
142 # some risk of minor formatting disagreements at the version number
143 # level, if not from stray underscores then from something else.
144 #
Jim Hebertcf870d72013-06-12 15:33:34 -0700145 # This is livable so long as you do some fuzzy version number
146 # comparison in your vulnerability monitoring, between what-we-have
147 # and what-the-advisory-says-is-affected.
148 return cpes
149
150
Chris McDonalde69db662018-11-15 12:50:18 -0700151def GenerateCPEList(deps_list, sysroot):
Xuewei Zhang656f9932017-09-15 16:15:05 -0700152 """Generate all CPEs for the packages included in deps_list and SDK packages
153
154 Args:
155 deps_list: A flattened dependency tree (cros_extract_deps format).
Chris McDonalde69db662018-11-15 12:50:18 -0700156 sysroot: The board directory to use when finding SDK packages.
Xuewei Zhang656f9932017-09-15 16:15:05 -0700157
158 Returns:
159 A list of CPE info for packages in deps_list and SDK packages, e.g.
160 [
161 {
162 "ComponentName": "app-admin/sudo",
163 "Repository": "cros",
164 "Targets": [
165 "cpe:/a:todd_miller:sudo:1.8.19p2"
166 ]
167 },
168 {
169 "ComponentName": "sys-libs/glibc",
170 "Repository": "cros",
171 "Targets": [
172 "cpe:/a:gnu:glibc:2.23"
173 ]
174 }
175 ]
176 """
Jim Hebertcf870d72013-06-12 15:33:34 -0700177 cpe_dump = []
Xuewei Zhang656f9932017-09-15 16:15:05 -0700178
Alex Klein5cdd57f2020-11-23 11:53:28 -0700179 # Generate CPEs for SDK packages.
180 for pkg_info in sorted(
181 sysroot_lib.get_sdk_provided_packages(sysroot), key=lambda x: x.cpvr):
Xuewei Zhang656f9932017-09-15 16:15:05 -0700182 # Only add CPE for SDK CPVs missing in deps_list.
Alex Klein5cdd57f2020-11-23 11:53:28 -0700183 if deps_list.get(pkg_info.cpvr) is not None:
Xuewei Zhang656f9932017-09-15 16:15:05 -0700184 continue
185
Alex Klein5cdd57f2020-11-23 11:53:28 -0700186 cpes = GetCPEFromCPV(pkg_info.category, pkg_info.package, pkg_info.version)
Xuewei Zhang656f9932017-09-15 16:15:05 -0700187 if cpes:
Alex Klein5cdd57f2020-11-23 11:53:28 -0700188 cpe_dump.append({'ComponentName': '%s' % pkg_info.atom,
Xuewei Zhang656f9932017-09-15 16:15:05 -0700189 'Repository': 'cros',
190 'Targets': sorted(cpes)})
191 else:
Alex Klein5cdd57f2020-11-23 11:53:28 -0700192 logging.warning('No CPE entry for %s', pkg_info.cpvr)
Xuewei Zhang656f9932017-09-15 16:15:05 -0700193
194 # Generage CPEs for packages in deps_list.
Mike Frysinger1457e482019-01-03 04:04:02 -0500195 for cpv, record in sorted(deps_list.items()):
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700196 if record['cpes']:
197 name = '%s/%s' % (record['category'], record['name'])
198 cpe_dump.append({'ComponentName': name,
199 'Repository': 'cros',
200 'Targets': sorted(record['cpes'])})
Jim Hebertcf870d72013-06-12 15:33:34 -0700201 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700202 logging.warning('No CPE entry for %s', cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700203 return sorted(cpe_dump, key=lambda k: k['ComponentName'])
Jim Hebert91c052c2011-03-11 11:00:53 -0800204
205
Chris McDonalde69db662018-11-15 12:50:18 -0700206def ParseArgs(argv):
207 """Parse command line arguments."""
Mike Frysinger012371e2019-01-03 03:42:32 -0500208 parser = commandline.ArgumentParser(description=__doc__)
Chris McDonalde69db662018-11-15 12:50:18 -0700209 target = parser.add_mutually_exclusive_group()
210 target.add_argument('--sysroot', type='path', help='Path to the sysroot.')
211 target.add_argument('--board', help='Board name.')
212
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700213 parser.add_argument('--format', default='deps',
214 choices=['deps', 'cpe'],
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700215 help='Output either traditional deps or CPE-only JSON.')
216 parser.add_argument('--output-path', default=None,
217 help='Write output to the given path.')
Mike Frysinger012371e2019-01-03 03:42:32 -0500218 parser.add_argument('pkgs', nargs='*')
219 opts = parser.parse_args(argv)
220 opts.Freeze()
221 return opts
Jim Hebert91c052c2011-03-11 11:00:53 -0800222
Chris McDonalde69db662018-11-15 12:50:18 -0700223
Ned Nguyene16dcfb2019-03-22 10:36:05 -0600224def FilterObsoleteDeps(package_deps):
225 """Remove all the packages that are to be uninstalled from |package_deps|.
226
227 Returns:
228 None since this method mutates |package_deps| directly.
229 """
230 obsolete_package_deps = []
Mike Frysinger0bdbc102019-06-13 15:27:29 -0400231 for k, v in package_deps.items():
Ned Nguyene16dcfb2019-03-22 10:36:05 -0600232 if v['action'] in ('merge', 'nomerge'):
233 continue
234 elif v['action'] == 'uninstall':
235 obsolete_package_deps.append(k)
236 else:
237 assert False, (
238 'Unrecognized action. Package dep data: %s' % v)
239 for p in obsolete_package_deps:
240 del package_deps[p]
241
242
Chris McDonaldd8a7f112019-11-01 10:35:07 -0600243def ExtractDeps(sysroot,
244 package_list,
245 formatting='deps',
Chris McDonald76608422020-06-15 11:42:20 -0600246 include_bdepend=True,
247 backtrack=True):
Ned Nguyendd3e09f2019-03-14 18:54:03 -0600248 """Returns the set of dependencies for the packages in package_list.
249
250 For calculating dependencies graph, this should only consider packages
Chris McDonaldebc7ae72019-10-03 14:58:46 -0600251 that are DEPENDS, RDEPENDS, or BDEPENDS. Essentially, this should answer the
252 question "which are all the packages which changing them may change the
253 execution of any binaries produced by packages in |package_list|."
Ned Nguyendd3e09f2019-03-14 18:54:03 -0600254
255 Args:
256 sysroot: the path (string) to the root directory into which the package is
257 pretend to be merged. This value is also used for setting
258 PORTAGE_CONFIGROOT.
259 package_list: the list of packages (CP string) to extract their dependencies
260 from.
261 formatting: can either be 'deps' or 'cpe'. For 'deps', see the return
262 format in docstring of FlattenDepTree, for 'cpe', see the return format in
263 docstring of GenerateCPEList.
Chris McDonaldd8a7f112019-11-01 10:35:07 -0600264 include_bdepend: Controls whether BDEPEND packages that would be installed
265 to BROOT (usually "/" instead of ROOT) are included in the output.
Chris McDonald76608422020-06-15 11:42:20 -0600266 backtrack: Setting to False disables backtracking in Portage's dependency
267 solver. If the highest available version of dependencies doesn't produce
268 a solvable graph Portage will give up and return an error instead of
269 trying other candidates.
Ned Nguyendd3e09f2019-03-14 18:54:03 -0600270
271 Returns:
272 A JSON-izable object that either follows 'deps' or 'cpe' format.
273 """
Chris McDonaldd8a7f112019-11-01 10:35:07 -0600274 lib_argv = ['--quiet', '--pretend', '--emptytree']
275 if include_bdepend:
276 lib_argv += ['--include-bdepend']
Chris McDonald76608422020-06-15 11:42:20 -0600277 if not backtrack:
278 lib_argv += ['--backtrack=0']
Chris McDonalde69db662018-11-15 12:50:18 -0700279 lib_argv += ['--sysroot=%s' % sysroot]
280 lib_argv.extend(package_list)
Jim Hebert91c052c2011-03-11 11:00:53 -0800281
282 deps = DepGraphGenerator()
Jim Hebertcf870d72013-06-12 15:33:34 -0700283 deps.Initialize(lib_argv)
Chris McDonaldd8a7f112019-11-01 10:35:07 -0600284
285 deps_tree, _deps_info, bdeps_tree = deps.GenDependencyTree()
286 trees = (deps_tree, bdeps_tree)
287
288 flattened_trees = tuple(
289 FlattenDepTree(tree, get_cpe=(formatting == 'cpe')) for tree in trees)
Ned Nguyene16dcfb2019-03-22 10:36:05 -0600290
291 # Workaround: since emerge doesn't honor the --emptytree flag, for now we need
292 # to manually filter out packages that are obsolete (meant to be
293 # uninstalled by emerge)
294 # TODO(crbug.com/938605): remove this work around once
295 # https://bugs.gentoo.org/681308 is addressed.
Chris McDonaldd8a7f112019-11-01 10:35:07 -0600296 for tree in flattened_trees:
297 FilterObsoleteDeps(tree)
Ned Nguyene16dcfb2019-03-22 10:36:05 -0600298
Chris McDonalde69db662018-11-15 12:50:18 -0700299 if formatting == 'cpe':
Chris McDonaldd8a7f112019-11-01 10:35:07 -0600300 flattened_trees = tuple(
301 GenerateCPEList(tree, sysroot) for tree in flattened_trees)
302 return flattened_trees
Chris McDonalde69db662018-11-15 12:50:18 -0700303
304
305def main(argv):
Mike Frysinger012371e2019-01-03 03:42:32 -0500306 opts = ParseArgs(argv)
Chris McDonalde69db662018-11-15 12:50:18 -0700307
Mike Frysinger06a51c82021-04-06 11:39:17 -0400308 sysroot = (opts.sysroot or
309 build_target_lib.get_default_sysroot_path(opts.board))
Chris McDonald486508d2019-11-04 09:20:07 -0700310 deps_list, _ = ExtractDeps(sysroot, opts.pkgs, opts.format)
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700311
Mike Frysingerd0960812020-06-09 01:53:32 -0400312 pformat.json(deps_list,
313 fp=opts.output_path if opts.output_path else sys.stdout)