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())