Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_extract_deps.py
1 # 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
5 """Command to extract the dependancy tree for a given package."""
6
7 from __future__ import print_function
8
9 import json
10 import portage  # pylint: disable=F0401
11
12 from parallel_emerge import DepGraphGenerator
13
14 from chromite.lib import commandline
15 from chromite.lib import cros_build_lib
16
17
18 def FlattenDepTree(deptree, pkgtable=None, parentcpv=None):
19   """Simplify dependency json.
20
21 Turn something like this (the parallel_emerge DepsTree format):
22 {
23   "app-admin/eselect-1.2.9": {
24     "action": "merge",
25     "deps": {
26       "sys-apps/coreutils-7.5-r1": {
27         "action": "merge",
28         "deps": {},
29         "deptype": "runtime"
30       },
31       ...
32     }
33   }
34 }
35   ...into something like this (the cros_extract_deps format):
36 {
37   "app-admin/eselect-1.2.9": {
38     "deps": ["coreutils-7.5-r1"],
39     "rev_deps": [],
40     "name": "eselect",
41     "category": "app-admin",
42     "version": "1.2.9",
43     "full_name": "app-admin/eselect-1.2.9",
44     "action": "merge"
45   },
46   "sys-apps/coreutils-7.5-r1": {
47     "deps": [],
48     "rev_deps": ["app-admin/eselect-1.2.9"],
49     "name": "coreutils",
50     "category": "sys-apps",
51     "version": "7.5-r1",
52     "full_name": "sys-apps/coreutils-7.5-r1",
53     "action": "merge"
54   }
55 }
56   """
57   if pkgtable is None:
58     pkgtable = {}
59   for cpv, record in deptree.items():
60     if cpv not in pkgtable:
61       cat, nam, ver, rev = portage.versions.catpkgsplit(cpv)
62       pkgtable[cpv] = {"deps": [],
63                        "rev_deps": [],
64                        "name": nam,
65                        "category": cat,
66                        "version": "%s-%s" % (ver, rev),
67                        "full_name": cpv,
68                        "cpes": GetCPEFromCPV(cat, nam, ver),
69                        "action": record["action"]}
70     # If we have a parent, that is a rev_dep for the current package.
71     if parentcpv:
72       pkgtable[cpv]["rev_deps"].append(parentcpv)
73     # If current package has any deps, record those.
74     for childcpv in record["deps"]:
75       pkgtable[cpv]["deps"].append(childcpv)
76     # Visit the subtree recursively as well.
77     FlattenDepTree(record["deps"], pkgtable=pkgtable, parentcpv=cpv)
78   return pkgtable
79
80
81 def GetCPEFromCPV(category, package, version):
82   """Look up the CPE for a specified Portage package.
83
84   Args:
85     category: The Portage package's category, e.g. "net-misc"
86     package: The Portage package's name, e.g. "curl"
87     version: The Portage version, e.g. "7.30.0"
88
89   Returns:
90     A list of CPE Name strings, e.g.
91     ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
92   """
93   equery_cmd = ["equery", "m", "-U", "%s/%s" % (category, package)]
94   lines = cros_build_lib.RunCommand(equery_cmd, error_code_ok=True,
95                                     print_cmd=False,
96                                     redirect_stdout=True).output.splitlines()
97   # Look for lines like "Remote-ID:   cpe:/a:kernel:linux-pam ID: cpe"
98   # and extract the cpe URI.
99   cpes = []
100   for line in lines:
101     if "ID: cpe" not in line:
102       continue
103     cpes.append("%s:%s" % (line.split()[1], version.replace("_", "")))
104   # Note that we're assuming we can combine the root of the CPE, taken
105   # from metadata.xml, and tack on the version number as used by
106   # Portage, and come up with a legitimate CPE. This works so long as
107   # Portage and CPE agree on the precise formatting of the version
108   # number, which they almost always do. The major exception we've
109   # identified thus far is that our ebuilds have a pattern of inserting
110   # underscores prior to patchlevels, that neither upstream nor CPE
111   # use. For example, our code will decide we have
112   # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
113   # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
114   # is "right" in this example, in that it matches www.sudo.ws.)
115   #
116   # Removing underscores seems to improve our chances of correctly
117   # arriving at the CPE used by NVD. However, at the end of the day,
118   # ebuild version numbers are rev'd by people who don't have "try to
119   # match NVD" as one of their goals, and there is always going to be
120   # some risk of minor formatting disagreements at the version number
121   # level, if not from stray underscores then from something else.
122   #
123   # This is livable so long as you do some fuzzy version number
124   # comparison in your vulnerability monitoring, between what-we-have
125   # and what-the-advisory-says-is-affected.
126   return cpes
127
128
129 def ExtractCPEList(deps_list):
130   cpe_dump = []
131   for cpv, record in deps_list.items():
132     if record["cpes"]:
133       name = "%s/%s" % (record["category"], record["name"])
134       cpe_dump.append({"ComponentName": name,
135                        "Repository": "cros",
136                        "Targets": sorted(record["cpes"])})
137     else:
138       cros_build_lib.Warning("No CPE entry for %s", cpv)
139   return sorted(cpe_dump, key=lambda k: k["ComponentName"])
140
141
142 def main(argv):
143   parser = commandline.ArgumentParser(description="""
144 This extracts the dependency tree for the specified package, and outputs it
145 to stdout, in a serialized JSON format.""")
146   parser.add_argument("--board", required=True,
147                       help="The board to use when computing deps.")
148   parser.add_argument("--format", default="deps",
149                       choices=["deps", "cpe"],
150                       help="Output either traditional deps or CPE-only JSON")
151   # Even though this is really just a pass-through to DepGraphGenerator,
152   # handling it as a known arg here allows us to specify a default more
153   # elegantly than testing for its presence in the unknown_args later.
154   parser.add_argument("--root-deps", default="rdeps",
155                       help="Which deps to report (defaults to rdeps)")
156   known_args, unknown_args = parser.parse_known_args(argv)
157
158   lib_argv = ["--board=%s" % known_args.board,
159               "--root-deps=%s" % known_args.root_deps,
160               "--quiet", "--pretend", "--emptytree"]
161   lib_argv.extend(unknown_args)
162
163   deps = DepGraphGenerator()
164   deps.Initialize(lib_argv)
165   deps_tree, _deps_info = deps.GenDependencyTree()
166   deps_list = FlattenDepTree(deps_tree)
167   if known_args.format == "cpe":
168     deps_list = ExtractCPEList(deps_list)
169   print(json.dumps(deps_list, sort_keys=True, indent=2))