vndk-def: Add VTool prototype in vndk_definition_tool
Using the command "dep-graph" the tool will check and show the VNDK
dependency violations base on the VNDK tag definition.
Test: Run `vndk_definition_tool.py dep-graph \
--system ... --vendor ... -o "html_output" \
--tag-file "[vndk_tag_table].csv"`
Change-Id: Ia5196e94beb0de106176e98ea413e4a921d7f8c5
diff --git a/vndk/tools/definition-tool/assets/visual/dep-data.js b/vndk/tools/definition-tool/assets/visual/dep-data.js
new file mode 100644
index 0000000..1d038dc
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/dep-data.js
@@ -0,0 +1,45 @@
+var violatedLibs = {"system.private.bin": [
+ ["/system/bin/systembin", 2],
+ ["/system/bin/otherbin", 1]],
+ "system.private.fwk-only": [
+ ["/system/lib/test.so", 1]],
+ "vendor.private.bin": [
+ ["/vendor/lib/libvendor.so", 2],
+ ["/vendor/lib/vendor_2_lib.so", 1]]};
+var depData = [
+ {"name": "/system/lib/oklib.so",
+ "violate_count": 0,
+ "violates": [],
+ "depends": [],
+ "tag": "system.public.vndk"},
+ {"name": "/system/lib/oklib2.so",
+ "violate_count": 0,
+ "violates": [],
+ "depends": ["/system/lib/oklib.so"],
+ "tag": "system.private.fwk_only"},
+ {"name": "/system/bin/systembin",
+ "violate_count": 2,
+ "violates": ["/vendor/lib/libvendor.so", "/vendor/lib/vendor_2_lib.so"],
+ "depends": ["/system/lib/oklib.so"],
+ "tag": "system.private.bin"},
+ {"name": "/system/bin/otherbin",
+ "violate_count": 1,
+ "violates": ["/vendor/lib/libvendor.so"],
+ "depends": ["/system/lib/oklib2.so"],
+ "tag": "system.private.bin"},
+ {"name": "/system/lib/test.so",
+ "violate_count": 1,
+ "violates": ["/vendor/lib/libvendor.so"],
+ "depends": ["/system/lib/oklib.so"],
+ "tag": "system.private.fwk-only"},
+ {"name": "/vendor/lib/libvendor.so",
+ "violate_count": 2,
+ "violates": ["/system/lib/test.so", "/system/lib/oklib2.so"],
+ "depends": [],
+ "tag": "vendor.private.bin"},
+ {"name": "/vendor/lib/vendor_2_lib.so",
+ "violate_count": 1,
+ "violates": ["/system/lib/test.so"],
+ "depends": [],
+ "tag": "vendor.private.bin"}
+];
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/assets/visual/dep-graph.css b/vndk/tools/definition-tool/assets/visual/dep-graph.css
new file mode 100644
index 0000000..ecee4ff
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/dep-graph.css
@@ -0,0 +1,123 @@
+.node {
+ font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ fill: #bbb;
+ fill-opacity: 0.2;
+}
+
+.node--sys-pri {
+ font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ fill: #ffaaaa;
+ fill-opacity: 0.2;
+}
+
+.node--sys-pub {
+ font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ fill: #aaffaa;
+ fill-opacity: 0.2;
+}
+
+.node--source {
+ fill: #2ca02c;
+ fill-opacity: 1;
+}
+
+.node--target {
+ fill: #d62728;
+ fill-opacity: 1;
+}
+
+.node--selected {
+ fill: #ff7000;
+ fill-opacity: 1;
+ font-weight: 700;
+}
+
+.node:hover,
+.node--sys-pri:hover,
+.node--sys-pub:hover {
+ fill: #000;
+ fill-opacity: 1;
+}
+
+.node:hover,
+.node--sys-pri:hover,
+.node--sys-pub:hover,
+.node--source,
+.node--target {
+ font-weight: 700;
+}
+
+.link {
+ stroke: steelblue;
+ stroke-opacity: 0.01;
+ fill: none;
+ pointer-events: none;
+}
+
+.link--violate {
+ stroke: crimson;
+ stroke-opacity: 0.1;
+ stroke-width: 1.0px;
+ fill: none;
+ pointer-events: none;
+}
+
+.link--source,
+.link--target {
+ stroke-opacity: 1;
+ stroke-width: 2.5px;
+}
+
+.link--source {
+ stroke: orange;
+}
+
+.link--target {
+ stroke: #2ca02c;
+}
+
+button.violate {
+ background-color: white;
+ color: #333;
+ cursor: pointer;
+ padding: 5px;
+ width: 100%;
+ border: none;
+ text-align: left;
+ outline: none;
+ font-size: 15px;
+ transition: 0.4s;
+}
+
+button.violate.active,
+button.violate:hover {
+ background-color: #aaa;
+}
+
+.violate-list {
+ background-color: #ddd;
+ padding: 3px;
+ width: 100%;
+ border: none;
+ text-align: left;
+ font-size: 14px;
+}
+
+#violate_list_column {
+ width: 20%;
+ height: 99vh;
+ float: left;
+ overflow: scroll;
+}
+
+#dep_graph_column {
+ width: 80%;
+ height: 99vh;
+ float: right;
+ overflow: scroll;
+}
+
+#reset_btn {
+ position: absolute;
+ margin: 20px;
+}
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/assets/visual/dep-graph.js b/vndk/tools/definition-tool/assets/visual/dep-graph.js
new file mode 100644
index 0000000..89ce76d
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/dep-graph.js
@@ -0,0 +1,251 @@
+(function() {
+ 'use strict';
+
+ let diameter = 1280;
+ let radius = diameter / 2;
+ let innerRadius = radius - 240;
+
+ let cluster = d3.cluster();
+ cluster.size([ 360, innerRadius ]);
+
+ let line = d3.radialLine();
+ line.curve(d3.curveBundle.beta(0.85));
+ line.radius(function(d) { return d.y; });
+ line.angle(function(d) { return d.x / 180 * Math.PI; });
+
+ let link;
+ let node;
+ let selectedNode;
+
+ function init() {
+ let domListCol = document.createElement("div");
+ domListCol.id = "violate_list_column";
+ let domGraphCol = document.createElement("div");
+ domGraphCol.id = "dep_graph_column";
+ let domResetBtn = document.createElement("button");
+ domResetBtn.id = "reset_btn";
+ domResetBtn.innerHTML = "Reset";
+ domGraphCol.appendChild(domResetBtn);
+
+ document.body.appendChild(domListCol);
+ document.body.appendChild(domGraphCol);
+
+ let canvas = d3.select("#dep_graph_column").append("svg");
+ canvas.attr("width", diameter + 200);
+ canvas.attr("height", diameter);
+
+ let svg = canvas.append("g");
+ svg.attr("transform", "translate(" + (radius + 100) + "," + radius + ")");
+
+ link = svg.append("g").selectAll(".link");
+ node = svg.append("g").selectAll(".node");
+
+ showResult(depData, violatedLibs);
+ }
+
+ function showList(depMap, violatedLibs) {
+ function makeTitle(tagName) {
+ let domTitle = document.createElement("div");
+ let domText = document.createElement("h3");
+ domText.innerHTML = tagName;
+ domTitle.appendChild(domText);
+ return domTitle;
+ }
+ function makeButton(libName, count) {
+ let domButton = document.createElement("button");
+ domButton.className = "violate";
+ domButton.innerHTML = libName + " (" + count + ")";
+ domButton.onclick = function() {
+ this.classList.toggle("active");
+ let currentList = this.nextElementSibling;
+ if (currentList.style.display === "block") {
+ currentList.style.display = "none";
+ selectedNode = undefined;
+ resetclicked();
+ } else {
+ currentList.style.display = "block";
+ if (selectedNode) {
+ selectedNode.classList.toggle("active");
+ selectedNode.nextElementSibling.style.display = "none";
+ }
+ selectedNode = domButton;
+ mouseclicked(depMap[libName]);
+ }
+ };
+ return domButton;
+ }
+ function makeList(domList, list)
+ {
+ for (let i = 0; i < list.length; i++) {
+ domList.appendChild(makeButton(list[i][0], list[i][1]));
+ let domDepList = document.createElement("div");
+ let depItem = depMap[list[i][0]];
+ let violates = depItem.data.violates;
+ for (let j = 0; j < violates.length; j++) {
+ let domDepLib = document.createElement("div");
+ let tag = depMap[violates[j]].data.tag;
+ domDepLib.className = "violate-list";
+ domDepLib.innerHTML = violates[j] + " ["
+ + tag.substring(tag.lastIndexOf(".") + 1) + "]";
+ domDepList.appendChild(domDepLib);
+ }
+ domList.appendChild(domDepList);
+ domDepList.style.display = "none";
+ }
+ }
+
+ let domViolatedList = document.getElementById("violate_list_column");
+ if ("vendor.private.bin" in violatedLibs) {
+ let list = violatedLibs["vendor.private.bin"];
+ domViolatedList.appendChild(makeTitle("VENDOR (" + list.length + ")"));
+ makeList(domViolatedList, list);
+ }
+ for (let tag in violatedLibs) {
+ if (tag === "vendor.private.bin")
+ continue;
+ let list = violatedLibs[tag];
+ if (tag === "system.private.bin")
+ tag = "SYSTEM";
+ else
+ tag = tag.substring(tag.lastIndexOf(".") + 1).toUpperCase();
+ domViolatedList.appendChild(makeTitle(tag + " (" + list.length + ")"));
+ makeList(domViolatedList, list);
+ }
+ }
+
+ function showResult(depDumps, violatedLibs) {
+ let root = tagHierarchy(depDumps).sum(function(d) { return 1; });
+ cluster(root);
+
+ let libsDepData = libsDepends(root.leaves());
+ showList(libsDepData[1], violatedLibs);
+ link = link.data(libsDepData[0])
+ .enter()
+ .append("path")
+ .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
+ .attr("class", function(d) { return d.allow ? "link" : "link--violate" })
+ .attr("d", line);
+
+ node = node.data(root.leaves())
+ .enter()
+ .append("text")
+ .attr("class",
+ function(d) {
+ return d.data.parent.parent.parent.key == "system" ?
+ (d.data.parent.parent.key == "system.public" ?
+ "node--sys-pub" :
+ "node--sys-pri") :
+ "node";
+ })
+ .attr("dy", "0.31em")
+ .attr("transform",
+ function(d) {
+ return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" +
+ (d.x < 180 ? "" : "rotate(180)");
+ })
+ .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
+ .text(function(d) { return d.data.key; })
+ .on("click", mouseclicked);
+ document.getElementById("reset_btn").onclick = resetclicked;
+ }
+
+ function resetclicked() {
+ if (selectedNode) {
+ selectedNode.classList.toggle("active");
+ selectedNode.nextElementSibling.style.display = "none";
+ selectedNode = undefined;
+ }
+ link.classed("link--target", false)
+ .classed("link--source", false);
+ node.classed("node--target", false)
+ .classed("node--source", false)
+ .classed("node--selected", false);
+ }
+
+ function mouseclicked(d) {
+ node.each(function(n) { n.target = n.source = false; });
+
+ link.classed("link--target",
+ function(l) {
+ if (l.target === d) {
+ l.source.source = true;
+ return true;
+ } else {
+ return false;
+ }
+ })
+ .classed("link--source",
+ function(l) {
+ if (l.source === d) {
+ l.target.target = true;
+ return true;
+ } else {
+ return false;
+ }
+ })
+ .filter(function(l) { return l.target === d || l.source === d; })
+ .raise();
+
+ node.classed("node--target",
+ function(n) {
+ return n.target;
+ })
+ .classed("node--source",
+ function(n) { return n.source; })
+ .classed("node--selected",
+ function(n) {
+ return n === d;
+ });
+ }
+
+ function tagHierarchy(depDumps) {
+ let map = {};
+
+ function find(name, tag, data) {
+ let node = map[name], i;
+ if (!node) {
+ node = map[name] = data || { name : name, children : [] };
+ if (name.length) {
+ node.parent = find(tag, tag.substring(0, tag.lastIndexOf(".")));
+ node.parent.children.push(node);
+ node.key = name;
+ }
+ }
+ return node;
+ }
+
+ depDumps.forEach(function(d) { find(d.name, d.tag, d); });
+
+ return d3.hierarchy(map[""]);
+ }
+
+ function libsDepends(nodes) {
+ let map = {}, depends = [];
+
+ // Compute a map from name to node.
+ nodes.forEach(function(d) { map[d.data.name] = d; });
+
+ // For each dep, construct a link from the source to target node.
+ nodes.forEach(function(d) {
+ if (d.data.depends)
+ d.data.depends.forEach(function(i) {
+ let l = map[d.data.name].path(map[i]);
+ l.allow = true;
+ depends.push(l);
+ });
+ if (d.data.violates.length) {
+ map[d.data.name].not_allow = true;
+ d.data.violates.forEach(function(i) {
+ map[i].not_allow = true;
+ let l = map[d.data.name].path(map[i]);
+ l.allow = false;
+ depends.push(l);
+ });
+ }
+ });
+
+ return [ depends, map ];
+ }
+
+ window.onload = init;
+})();
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/assets/visual/index.html b/vndk/tools/definition-tool/assets/visual/index.html
new file mode 100644
index 0000000..9a5547f
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>VNDK Dependency Graph</title>
+ <link rel="stylesheet" href="dep-graph.css" media="all" />
+ <script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
+ <script type="text/javascript" src="dep-data.js"></script>
+ <script type="text/javascript" src="dep-graph.js"></script>
+ </head>
+
+ <body></body>
+</html>
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py
index 45c7f36..7c11dce 100755
--- a/vndk/tools/definition-tool/vndk_definition_tool.py
+++ b/vndk/tools/definition-tool/vndk_definition_tool.py
@@ -2339,6 +2339,130 @@
return 0 if num_errors == 0 else 1
+class DepGraphCommand(ELFGraphCommand):
+ def __init__(self):
+ super(DepGraphCommand, self).__init__(
+ 'dep-graph', help='Show the eligible dependencies graph')
+
+ def add_argparser_options(self, parser):
+ super(DepGraphCommand, self).add_argparser_options(parser)
+
+ parser.add_argument('--tag-file', required=True)
+ parser.add_argument(
+ '--output', '-o', help='output directory')
+
+ def _load_tag_file(self, tag_file_path):
+ res = TaggedLibDict(set(), set(), set(), set(), set(), set(), set(),
+ set())
+
+ mapping = {
+ 'll-ndk': res.ll_ndk,
+ 'll-ndk-indirect': res.ndk_indirect,
+ 'sp-ndk': res.sp_ndk,
+ 'sp-ndk-indirect': res.ndk_indirect,
+ 'hl-ndk': res.hl_ndk,
+ 'vndk-sp-hal': res.vndk_sp,
+ 'vndk-sp-both': res.vndk_sp,
+ 'vndk-sp-indirect': res.vndk, # Visible to non-SP-HAL
+ 'vndk': res.vndk,
+ 'vndk-indirect': res.vndk_indirect,
+ 'fwk-only': res.fwk_only,
+ 'remove': set(), # Drop from system lib. Tag as vendor lib.
+ }
+
+ with open(tag_file_path, 'r') as tag_file:
+ csv_reader = csv.reader(tag_file)
+ for lib_name, tag in csv_reader:
+ mapping[tag.lower()].add(os.path.basename(lib_name))
+
+ return res
+
+ def _get_tag_from_lib(self, lib, tags):
+ # Tempororily add the TAG_DEP_TABLE to show permission
+ TAG_DEP_TABLE = {
+ 'vendor': ('ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk', 'vndk_indirect'),
+ }
+
+ tag_hierarchy = []
+ eligible_for_vendor = TAG_DEP_TABLE['vendor']
+ for tag in TAGGED_LIB_DICT_FIELDS:
+ if tag == 'vendor':
+ tag_hierarchy.append('vendor.private.bin')
+ else:
+ pub = 'public' if tag in eligible_for_vendor else 'private'
+ tag_hierarchy.append('system.{}.{}'.format(pub, tag))
+ lib_name = os.path.basename(lib.path)
+ if lib.partition == PT_SYSTEM:
+ for i, lib_set in enumerate(tags):
+ if lib_name in lib_set:
+ return tag_hierarchy[i]
+ return 'system.private.bin'
+ else:
+ return 'vendor.private.bin'
+
+ def _check_if_allowed(self, my_tag, other_tag):
+ my = my_tag.split('.')
+ other = other_tag.split('.')
+ if my[0] == 'system' and other[0] == 'vendor':
+ return False
+ if my[0] == 'vendor' and other[0] == 'system' \
+ and other[1] == 'private':
+ return False
+ return True
+
+ def _get_dep_graph(self, graph, tags):
+ data = []
+ violate_libs = dict()
+ system_libs = graph.lib_pt[PT_SYSTEM].values()
+ vendor_libs = graph.lib_pt[PT_VENDOR].values()
+ for lib in itertools.chain(system_libs, vendor_libs):
+ tag = self._get_tag_from_lib(lib, tags)
+ violate_count = 0
+ lib_item = {
+ 'name': lib.path,
+ 'tag': tag,
+ 'depends': [],
+ 'violates': [],
+ }
+ for dep in lib.deps:
+ if self._check_if_allowed(tag,
+ self._get_tag_from_lib(dep, tags)):
+ lib_item['depends'].append(dep.path)
+ else:
+ lib_item['violates'].append(dep.path)
+ violate_count += 1;
+ lib_item['violate_count'] = violate_count
+ if violate_count > 0:
+ if not tag in violate_libs:
+ violate_libs[tag] = []
+ violate_libs[tag].append((lib.path, violate_count))
+ data.append(lib_item)
+ return data, violate_libs
+
+ def main(self, args):
+ graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
+ args.vendor, args.vendor_dir_as_system,
+ args.load_extra_deps)
+
+ tags = self._load_tag_file(args.tag_file)
+ data, violate_libs = self._get_dep_graph(graph, tags)
+ data.sort(key=lambda lib_item: (lib_item['tag'],
+ lib_item['violate_count']))
+ for libs in violate_libs.values():
+ libs.sort(key=lambda libs: libs[1], reverse=True)
+
+ makedirs(args.output, exist_ok=True)
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ for name in ('index.html', 'dep-graph.js', 'dep-graph.css'):
+ shutil.copyfile(os.path.join(script_dir, 'assets/visual', name),
+ os.path.join(args.output, name))
+ with open(os.path.join(args.output, 'dep-data.js'), 'w') as f:
+ f.write('var violatedLibs = ' + json.dumps(violate_libs) +
+ '\nvar depData = ' + json.dumps(data) + ';')
+
+ return 0
+
+
class VNDKSPCommand(ELFGraphCommand):
def __init__(self):
super(VNDKSPCommand, self).__init__(
@@ -2401,6 +2525,7 @@
register_subcmd(DepsClosureCommand())
register_subcmd(DepsInsightCommand())
register_subcmd(CheckDepCommand())
+ register_subcmd(DepGraphCommand())
register_subcmd(SpLibCommand())
register_subcmd(VNDKSPCommand())