vndk-def: Add deps-insight

deps-insight is a HTML-based tool to show the dependencies between the
libraries.  This is designed to ease the module dependencies review
work.

Test: Run deps-insight subcommand against sailfish tree.

Change-Id: I20f23f7f604da289a22477ed82abe4361e5252c7
diff --git a/vndk/tools/definition-tool/assets/index.html b/vndk/tools/definition-tool/assets/index.html
new file mode 100644
index 0000000..bd935cc
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+
+<html>
+	<head>
+		<meta charset="utf-8" />
+		<title>VNDK Dependency Insight</title>
+		<link rel="stylesheet" href="insight.css" media="all" />
+		<script type="text/javascript" src="insight.js"></script>
+		<script type="text/javascript" src="insight-data.js"></script>
+	</head>
+
+	<body></body>
+</html>
diff --git a/vndk/tools/definition-tool/assets/insight-data.js b/vndk/tools/definition-tool/assets/insight-data.js
new file mode 100644
index 0000000..9ef7a65
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/insight-data.js
@@ -0,0 +1,21 @@
+(function () {
+    var strs = [
+        '/system/lib/libc.so',
+        '/system/lib/libm.so',
+        '/system/lib/libdl.so',
+        '/system/lib64/libc.so',
+        '/system/lib64/libm.so',
+        '/system/lib64/libdl.so',
+        'll-ndk',
+        'hl-ndk',
+    ];
+    var mods = [
+        [0, 32, [6], [[1, 2]], []],
+        [1, 32, [6], [], [0]],
+        [2, 32, [6], [], [0]],
+        [3, 64, [6], [[5], [4]], []],
+        [4, 64, [7], [[5]], [3]],
+        [5, 64, [7], [], [3, 4]],
+    ];
+    insight.init(document, strs, mods);
+})();
diff --git a/vndk/tools/definition-tool/assets/insight.css b/vndk/tools/definition-tool/assets/insight.css
new file mode 100644
index 0000000..7cff186
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/insight.css
@@ -0,0 +1,103 @@
+p, li, td, th, h1, h2, h3 {
+	line-height: 1.6;
+}
+
+#control {
+	background-color: #eefaff;
+	border: 1px solid #3399ff;
+	border-collapse: collapse;
+	margin: 0px 0px 20px 0px;
+	width: 100%;
+}
+
+#control td {
+	padding: 3px;
+}
+
+#control_menu:link {
+	display: block;
+	width: 5em;
+	height: 20pt;
+	text-align: center;
+	line-height: 20pt;
+	color: #ffffff;
+	background-color: #3399ff;
+	position: fixed;
+	top: 0px;
+	right: 0px;
+}
+
+#control_menu:visited {
+	color: #ffffff;
+}
+
+#control_menu:hover {
+	color: #ffffff;
+	background-color: #0000ff;
+}
+
+.menu {
+	list-style-type: none;
+	margin: 0px;
+	padding: 0px;
+}
+
+.menu li {
+	display: inline;
+	padding: 0px 1em 0px 0px;
+}
+
+a:link {
+	color: #2244ff;
+	text-decoration: none;
+	font-family: monospace;
+}
+
+a:visited {
+	color: #2244ff;
+	text-decoration: none;
+}
+
+a:hover {
+	color: #ff3322;
+	text-decoration: underline;
+}
+
+a:active {
+	color: #ff3322;
+	text-decoration: underline;
+}
+
+#module_table {
+	border-collapse: collapse;
+}
+
+#module_tbody td,
+#module_tbody th {
+	border: 1px solid #dddddd;
+	padding: 3px 20px;
+	vertical-align: top;
+}
+
+#module_tbody th {
+	background-color: #eeeeee;
+}
+
+#module_tbody h1,
+#module_tbody h2,
+#module_tbody p {
+	font-size: 100%;
+	margin: 0px;
+	padding: 0px;
+}
+
+#module_tbody ol,
+#module_tbody ul {
+	margin: .3em 0px;
+	padding: 0px 0px 0px 2em;
+}
+
+#module_tbody tr:target {
+	background-color: #fffff0;
+	transition: background-color 800ms ease;
+}
diff --git a/vndk/tools/definition-tool/assets/insight.js b/vndk/tools/definition-tool/assets/insight.js
new file mode 100644
index 0000000..2a9202f
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/insight.js
@@ -0,0 +1,439 @@
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        define([], factory);
+    } else if (typeof module === 'object' && module.exports) {
+        module.exports = factory();
+    } else {
+        root.insight = factory();
+    }
+} (this, function () {
+    'use strict';
+
+    let document;
+    let strsData, mods, tagIds;
+    let domPathInput;
+    let domTBody;
+
+    //--------------------------------------------------------------------------
+    // DOM Helper Functions
+    //--------------------------------------------------------------------------
+    function domNewText(text) {
+        return document.createTextNode(text);
+    }
+
+    function domNewElem(type) {
+        let dom = document.createElement(type);
+        for (let i = 1; i < arguments.length; ++i) {
+            let arg = arguments[i];
+            if (typeof(arg) == 'string' || typeof(arg) == 'number') {
+                arg = domNewText(arg)
+            }
+            dom.appendChild(arg);
+        }
+        return dom;
+    }
+
+    function domNewLink(text, onClick) {
+        let dom = domNewElem('a', text);
+        dom.setAttribute('href', '#');
+        dom.addEventListener('click', onClick);
+        return dom;
+    }
+
+    //--------------------------------------------------------------------------
+    // Module Row
+    //--------------------------------------------------------------------------
+    function countDeps(deps) {
+        let direct = 0;
+        let indirect = 0;
+        if (deps.length > 0) {
+            direct = deps[0].length;
+            for (let i = 1; i < deps.length; ++i) {
+                indirect += deps[i].length;
+            }
+        }
+        return [direct, indirect];
+    }
+
+    function Module(id, modData) {
+        this.id = id;
+        this.path = strsData[modData[0]];
+        this.cls = modData[1];
+        this.tagIds = new Set(modData[2]);
+        this.deps = modData[3];
+        this.users = modData[4];
+
+        [this.numDirectDeps, this.numIndirectDeps] = countDeps(this.deps);
+        this.numUsers = this.users.length;
+
+        this.dom = null;
+        this.visible = false;
+
+        this.linkDoms = Object.create(null);
+    }
+
+    Module.prototype.isTagged = function (tagId) {
+        return this.tagIds.has(tagId);
+    }
+
+    Module.prototype.createModuleLinkDom = function (mod) {
+        let dom = domNewElem('a', mod.path);
+        dom.setAttribute('href', '#mod_' + mod.id);
+        dom.setAttribute('data-mod-id', mod.id);
+        dom.setAttribute('data-owner-id', this.id);
+        dom.addEventListener('click', onModuleLinkClicked);
+        dom.addEventListener('mouseover', onModuleLinkMouseOver);
+        dom.addEventListener('mouseout', onModuleLinkMouseOut);
+
+        this.linkDoms[mod.id] = dom;
+
+        return dom;
+    }
+
+    Module.prototype.createModuleRelationsDom = function (parent, label,
+                                                          modIds) {
+        parent.appendChild(domNewElem('h2', label));
+
+        let domOl = domNewElem('ol');
+        parent.appendChild(domOl);
+        for (let modId of modIds) {
+            domOl.appendChild(
+                    domNewElem('li', this.createModuleLinkDom(mods[modId])));
+        }
+    }
+
+    Module.prototype.createModulePathTdDom = function (parent) {
+        parent.appendChild(domNewElem('td', this.createModuleLinkDom(this)));
+    }
+
+    Module.prototype.createTagsTdDom = function (parent) {
+        let domTd = domNewElem('td');
+        for (let tag of this.tagIds) {
+            domTd.appendChild(domNewElem('p', strsData[tag]));
+        }
+        parent.appendChild(domTd);
+    }
+
+    Module.prototype.createDepsTdDom = function (parent) {
+        let domTd = domNewElem(
+                'td', this.numDirectDeps + ' + ' + this.numIndirectDeps);
+
+        let deps = this.deps;
+        if (deps.length > 0) {
+            this.createModuleRelationsDom(domTd, 'Direct', deps[0]);
+
+            for (let i = 1; i < deps.length; ++i) {
+                this.createModuleRelationsDom(domTd, 'Indirect #' + i, deps[i]);
+            }
+        }
+
+        parent.appendChild(domTd);
+    }
+
+    Module.prototype.createUsersTdDom = function (parent) {
+        let domTd = domNewElem('td', this.numUsers);
+
+        let users = this.users;
+        if (users.length > 0) {
+            this.createModuleRelationsDom(domTd, 'Direct', users);
+        }
+
+        parent.appendChild(domTd);
+    }
+
+    Module.prototype.createDom = function () {
+        let dom = this.dom = domNewElem('tr');
+        dom.setAttribute('id', 'mod_'  + this.id);
+
+        this.createModulePathTdDom(dom);
+        this.createTagsTdDom(dom);
+        this.createDepsTdDom(dom);
+        this.createUsersTdDom(dom)
+    }
+
+    Module.prototype.showDom = function () {
+        if (this.visible) {
+            return;
+        }
+        domTBody.appendChild(this.dom);
+        this.visible = true;
+    }
+
+    Module.prototype.hideDom = function () {
+        if (!this.visible) {
+            return;
+        }
+        this.dom.parentNode.removeChild(this.dom);
+        this.visible = false;
+    }
+
+    function createModulesFromData(stringsData, modulesData) {
+        return modulesData.map(function (modData, id) {
+            return new Module(id, modData);
+        });
+    }
+
+    function createTagIdsFromData(stringsData, mods) {
+        let tagIds = new Set();
+        for (let mod of mods) {
+            for (let tag of mod.tagIds) {
+                tagIds.add(tag);
+            }
+        }
+
+        tagIds = Array.from(tagIds);
+        tagIds.sort(function (a, b) {
+            return strsData[a].localeCompare(strsData[b]);
+        });
+
+        return tagIds;
+    }
+
+    //--------------------------------------------------------------------------
+    // Data
+    //--------------------------------------------------------------------------
+    function init(doc, stringsData, modulesData) {
+        document = doc;
+        strsData = stringsData;
+
+        mods = createModulesFromData(stringsData, modulesData);
+        tagIds = createTagIdsFromData(stringsData, mods);
+
+        document.addEventListener('DOMContentLoaded', function (evt) {
+            createControlDom(document.body);
+            createTableDom(document.body);
+        });
+    }
+
+    //--------------------------------------------------------------------------
+    // Control
+    //--------------------------------------------------------------------------
+    function createControlDom(parent) {
+        let domTBody = domNewElem('tbody');
+
+        createSelectionTrDom(domTBody);
+        createAddByTagsTrDom(domTBody);
+        createAddByPathTrDom(domTBody);
+
+        let domTable = domNewElem('table', domTBody);
+        domTable.id = 'control';
+
+        let domFixedLink = domNewElem('a', 'Menu');
+        domFixedLink.href = '#control';
+        domFixedLink.id = 'control_menu';
+
+        parent.appendChild(domFixedLink);
+        parent.appendChild(domTable);
+    }
+
+    function createControlMenuTr(parent, label, items) {
+        let domUl = domNewElem('ul');
+        domUl.className = 'menu';
+        for (let [txt, callback] of items) {
+            domUl.appendChild(domNewElem('li', domNewLink(txt, callback)));
+        }
+
+        let domTr = domNewElem('tr',
+                               createControlLabelTdDom(label),
+                               domNewElem('td', domUl));
+
+        parent.appendChild(domTr);
+    }
+
+    function createSelectionTrDom(parent) {
+        const items = [
+            ['All', onAddAll],
+            ['32-bit', onAddAll32],
+            ['64-bit', onAddAll64],
+            ['Clear', onClear],
+        ];
+
+        createControlMenuTr(parent, 'Selection:', items);
+    }
+
+    function createAddByTagsTrDom(parent) {
+        if (tagIds.length == 0) {
+            return;
+        }
+
+        const items = tagIds.map(function (tagId) {
+            return [strsData[tagId], function (evt) {
+                evt.preventDefault(true);
+                showModulesByTagId(tagId);
+            }];
+        });
+
+        createControlMenuTr(parent, 'Add by Tags:', items);
+    }
+
+    function createAddByPathTrDom(parent) {
+        let domForm = domNewElem('form');
+        domForm.addEventListener('submit', onAddModuleByPath);
+
+        domPathInput = domNewElem('input');
+        domPathInput.type = 'text';
+        domForm.appendChild(domPathInput);
+
+        let domBtn = domNewElem('input');
+        domBtn.type = 'submit';
+        domBtn.value = 'Add';
+        domForm.appendChild(domBtn);
+
+        let domTr = domNewElem('tr',
+                               createControlLabelTdDom('Add by Path:'),
+                               domNewElem('td', domForm));
+
+        parent.appendChild(domTr);
+    }
+
+    function createControlLabelTdDom(text) {
+        return domNewElem('td', domNewElem('strong', text));
+    }
+
+
+    //--------------------------------------------------------------------------
+    // Table
+    //--------------------------------------------------------------------------
+    function createTableDom(parent) {
+        domTBody = domNewElem('tbody');
+        domTBody.id = 'module_tbody';
+
+        createTableHeaderDom(domTBody);
+        createAllModulesDom();
+        showAllModules();
+
+        let domTable  = domNewElem('table', domTBody);
+        domTable.id = 'module_table';
+
+        parent.appendChild(domTable);
+    }
+
+    function createTableHeaderDom(parent) {
+        const labels = [
+            'Name',
+            'Tags',
+            'Dependencies (Direct + Indirect)',
+            'Users',
+        ];
+
+        let domTr = domNewElem('tr');
+        for (let label of labels) {
+            domTr.appendChild(domNewElem('th', label));
+        }
+
+        parent.appendChild(domTr);
+    }
+
+    function createAllModulesDom() {
+        for (let mod of mods) {
+            mod.createDom();
+        }
+    }
+
+    function hideAllModules() {
+        for (let mod of mods) {
+            mod.hideDom();
+        }
+    }
+
+    function showAllModules() {
+        for (let mod of mods) {
+            mod.showDom();
+        }
+    }
+
+    function showModulesByFilter(pred) {
+        for (let mod of mods) {
+            if (pred(mod)) {
+                mod.showDom();
+            }
+        }
+    }
+
+    function showModulesByTagId(tagId) {
+        showModulesByFilter(function (mod) {
+            return mod.isTagged(tagId);
+        });
+    }
+
+
+    //--------------------------------------------------------------------------
+    // Events
+    //--------------------------------------------------------------------------
+
+    function onAddModuleByPath(evt) {
+        evt.preventDefault();
+
+        let path = domPathInput.value;
+        domPathInput.value = '';
+
+        for (let mod of mods) {
+            if (mod.path == path) {
+                mod.showDom();
+                return;
+            }
+        }
+        alert('Path not found: ' + path);
+    }
+
+    function onAddAll(evt) {
+        evt.preventDefault(true);
+        hideAllModules();
+        showAllModules();
+    }
+
+    function onAddAllClass(evt, cls) {
+        evt.preventDefault(true);
+        hideAllModules();
+        showModulesByFilter(function (mod) {
+            return mod.cls == cls;
+        });
+    }
+
+    function onAddAll32(evt) {
+        onAddAllClass(evt, 32);
+    }
+
+    function onAddAll64(evt) {
+        onAddAllClass(evt, 64);
+    }
+
+    function onClear(evt) {
+        evt.preventDefault(true);
+        hideAllModules();
+    }
+
+    function onModuleLinkClicked(evt) {
+        let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10);
+        mods[modId].showDom();
+    }
+
+    function setDirectDepBackgroundColor(modId, ownerId, color) {
+        let mod = mods[modId];
+        let owner = mods[ownerId];
+        let ownerLinkDoms = owner.linkDoms;
+        if (mod.deps.length > 0) {
+            for (let depId of mod.deps[0]) {
+                if (depId in ownerLinkDoms) {
+                    ownerLinkDoms[depId].style.backgroundColor = color;
+                }
+            }
+        }
+    }
+
+    function onModuleLinkMouseOver(evt) {
+        let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10);
+        let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10);
+        setDirectDepBackgroundColor(modId, ownerId, '#ffff00');
+    }
+
+    function onModuleLinkMouseOut(evt) {
+        let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10);
+        let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10);
+        setDirectDepBackgroundColor(modId, ownerId, 'transparent');
+    }
+
+    return {
+        'init': init,
+    };
+}));
diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py
index 75b8f79..f125f9b 100755
--- a/vndk/tools/definition-tool/vndk_definition_tool.py
+++ b/vndk/tools/definition-tool/vndk_definition_tool.py
@@ -5,8 +5,10 @@
 import argparse
 import collections
 import itertools
+import json
 import os
 import re
+import shutil
 import stat
 import struct
 import sys
@@ -1739,31 +1741,15 @@
                 help='sub directory of vendor partition that has system files')
 
 
-class VNDKCommand(ELFGraphCommand):
-    def __init__(self):
-        super(VNDKCommand, self).__init__(
-                'vndk', help='Compute VNDK libraries set')
-
+class VNDKCommandBase(ELFGraphCommand):
     def add_argparser_options(self, parser):
-        super(VNDKCommand, self).add_argparser_options(parser)
+        super(VNDKCommandBase, self).add_argparser_options(parser)
 
         parser.add_argument(
                 '--load-generic-refs',
                 help='compare with generic reference symbols')
 
         parser.add_argument(
-                '--warn-incorrect-partition', action='store_true',
-                help='warn about libraries only have cross partition linkages')
-
-        parser.add_argument(
-                '--warn-high-level-ndk-deps', action='store_true',
-                help='warn about VNDK depends on high-level NDK')
-
-        parser.add_argument(
-                '--warn-banned-vendor-lib-deps', action='store_true',
-                help='warn when a vendor binaries depends on banned lib')
-
-        parser.add_argument(
                 '--ban-vendor-lib-dep', action='append',
                 help='library that must not be used by vendor binaries')
 
@@ -1779,6 +1765,98 @@
                 '--outward-customization-for-vendor', action='append',
                 help='outward customized vndk for vendor partition')
 
+    def _check_arg_dir_exists(self, arg_name, dirs):
+        for path in dirs:
+            if not os.path.exists(path):
+                print('error: Failed to find the directory "{}" specified in {}'
+                        .format(path, arg_name), file=sys.stderr)
+                sys.exit(1)
+            if not os.path.isdir(path):
+                print('error: Path "{}" specified in {} is not a directory'
+                        .format(path, arg_name), file=sys.stderr)
+                sys.exit(1)
+
+    def check_dirs_from_args(self, args):
+        self._check_arg_dir_exists('--system', args.system)
+        self._check_arg_dir_exists('--vendor', args.vendor)
+
+    def _get_generic_refs_from_args(self, args):
+        if not args.load_generic_refs:
+            return None
+        return GenericRefs.create_from_dir(args.load_generic_refs)
+
+    def _get_banned_libs_from_args(self, args):
+        if not args.ban_vendor_lib_dep:
+            return BannedLibDict.create_default()
+
+        banned_libs = BannedLibDict()
+        for name in args.ban_vendor_lib_dep:
+            banned_libs.add(name, 'user-banned', BA_WARN)
+        return banned_libs
+
+    def _get_outward_customized_sets_from_args(self, args, graph):
+        vndk_customized_for_system = set()
+        vndk_customized_for_vendor = set()
+        system_libs = graph.lib_pt[PT_SYSTEM].values()
+
+        if args.outward_customization_default_partition in {'system', 'both'}:
+            vndk_customized_for_system.update(system_libs)
+
+        if args.outward_customization_default_partition in {'vendor', 'both'}:
+            vndk_customized_for_vendor.update(system_libs)
+
+        if args.outward_customization_for_system:
+            vndk_customized_for_system.update(
+                    graph.get_libs(
+                        args.outward_customization_for_system, lambda x: None))
+
+        if args.outward_customization_for_vendor:
+            vndk_customized_for_vendor.update(
+                    graph.get_libs(
+                        args.outward_customization_for_vendor, lambda x: None))
+        return (vndk_customized_for_system, vndk_customized_for_vendor)
+
+    def create_from_args(self, args):
+        """Create all essential data structures for VNDK computation."""
+
+        self.check_dirs_from_args(args)
+
+        generic_refs = self._get_generic_refs_from_args(args)
+        banned_libs = self._get_banned_libs_from_args(args)
+
+        graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
+                                 args.vendor, args.vendor_dir_as_system,
+                                 args.load_extra_deps,
+                                 generic_refs=generic_refs)
+
+        vndk_customized_for_system, vndk_customized_for_vendor = \
+                self._get_outward_customized_sets_from_args(args, graph)
+
+        return (generic_refs, banned_libs, graph, vndk_customized_for_system,
+                vndk_customized_for_vendor)
+
+
+class VNDKCommand(VNDKCommandBase):
+    def __init__(self):
+        super(VNDKCommand, self).__init__(
+                'vndk', help='Compute VNDK libraries set')
+
+    def add_argparser_options(self, parser):
+        super(VNDKCommand, self).add_argparser_options(parser)
+
+        parser.add_argument(
+                '--warn-incorrect-partition', action='store_true',
+                help='warn about libraries only have cross partition linkages')
+
+        parser.add_argument(
+                '--warn-high-level-ndk-deps', action='store_true',
+                help='warn about VNDK depends on high-level NDK')
+
+        parser.add_argument(
+                '--warn-banned-vendor-lib-deps', action='store_true',
+                help='warn when a vendor binaries depends on banned lib')
+
+
     def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg):
         for lib in lib_set.values():
             if not lib.num_users:
@@ -1822,73 +1900,20 @@
                     print('warning: {}: NDK library should not be extended.'
                             .format(lib.path), file=sys.stderr)
 
-    def _check_arg_dir_exists(self, arg_name, dirs):
-        for path in dirs:
-            if not os.path.exists(path):
-                print('error: Failed to find the directory "{}" specified in {}'
-                        .format(path, arg_name), file=sys.stderr)
-                sys.exit(1)
-            if not os.path.isdir(path):
-                print('error: Path "{}" specified in {} is not a directory'
-                        .format(path, arg_name), file=sys.stderr)
-                sys.exit(1)
-
     def main(self, args):
-        # Check the command line options.
-        self._check_arg_dir_exists('--system', args.system)
-        self._check_arg_dir_exists('--vendor', args.vendor)
-
-        # Load the generic reference.
-        generic_refs = None
-        if args.load_generic_refs:
-            generic_refs = GenericRefs.create_from_dir(args.load_generic_refs)
-
-        # Link ELF objects.
-        graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
-                                 args.vendor, args.vendor_dir_as_system,
-                                 args.load_extra_deps,
-                                 generic_refs=generic_refs)
+        generic_refs, banned_libs, graph, vndk_customized_for_system, \
+                vndk_customized_for_vendor = self.create_from_args(args)
 
         # Check the API extensions to NDK libraries.
         if generic_refs:
             self._check_ndk_extensions(graph, generic_refs)
 
-        # Create banned libraries.
-        if not args.ban_vendor_lib_dep:
-            banned_libs = BannedLibDict.create_default()
-        else:
-            banned_libs = BannedLibDict()
-            for name in args.ban_vendor_lib_dep:
-                banned_libs.add(name, 'user-banned', BA_WARN)
-
         if args.warn_incorrect_partition:
             self._warn_incorrect_partition(graph)
 
         if args.warn_banned_vendor_lib_deps:
             self._warn_banned_vendor_lib_deps(graph, banned_libs)
 
-        # User may specify the partition for outward-customized vndk libs.  The
-        # following code converts the path into ELFLinkData.
-        vndk_customized_for_system = set()
-        vndk_customized_for_vendor = set()
-
-        system_libs = graph.lib_pt[PT_SYSTEM].values()
-        if args.outward_customization_default_partition in {'system', 'both'}:
-            vndk_customized_for_system.update(system_libs)
-
-        if args.outward_customization_default_partition in {'vendor', 'both'}:
-            vndk_customized_for_vendor.update(system_libs)
-
-        if args.outward_customization_for_system:
-            vndk_customized_for_system.update(
-                    graph.get_libs(
-                        args.outward_customization_for_system, lambda x: None))
-
-        if args.outward_customization_for_vendor:
-            vndk_customized_for_vendor.update(
-                    graph.get_libs(
-                        args.outward_customization_for_vendor, lambda x: None))
-
         # Compute vndk heuristics.
         vndk_lib = graph.compute_vndk(vndk_customized_for_system,
                                       vndk_customized_for_vendor, generic_refs,
@@ -1904,6 +1929,128 @@
         return 0
 
 
+class DepsInsightCommand(VNDKCommandBase):
+    def __init__(self):
+        super(DepsInsightCommand, self).__init__(
+                'deps-insight', help='Generate HTML to show dependencies')
+
+    def add_argparser_options(self, parser):
+        super(DepsInsightCommand, self).add_argparser_options(parser)
+
+        parser.add_argument(
+                '--output', '-o', help='output directory')
+
+    def main(self, args):
+        generic_refs, banned_libs, graph, vndk_customized_for_system, \
+                vndk_customized_for_vendor = self.create_from_args(args)
+
+        # Compute vndk heuristics.
+        vndk_lib = graph.compute_vndk(vndk_customized_for_system,
+                                      vndk_customized_for_vendor, generic_refs,
+                                      banned_libs)
+
+        # Serialize data.
+        strs = []
+        strs_dict = dict()
+
+        libs = list(graph.lib32.values()) + list(graph.lib64.values())
+        libs.sort(key=lambda lib: lib.path)
+        libs_dict = {lib: i for i, lib in enumerate(libs)}
+
+        def get_str_idx(s):
+            try:
+                return strs_dict[s]
+            except KeyError:
+                idx = len(strs)
+                strs_dict[s] = idx
+                strs.append(s)
+                return idx
+
+        def collect_path_sorted_lib_idxs(libs):
+            libs = sorted(libs, key=lambda lib: lib.path)
+            return [libs_dict[lib] for lib in libs]
+
+        def collect_deps(lib):
+            queue = list(lib.deps)
+            visited = set(queue)
+            visited.add(lib)
+            deps = []
+
+            # Traverse dependencies with breadth-first search.
+            while queue:
+                # Collect dependencies for next queue.
+                next_queue = []
+                for lib in queue:
+                    for dep in lib.deps:
+                        if dep not in visited:
+                            next_queue.append(dep)
+                            visited.add(dep)
+
+                # Append current queue to result.
+                deps.append(collect_path_sorted_lib_idxs(queue))
+
+                queue = next_queue
+
+            return deps
+
+        def collect_tags(lib):
+            tags = []
+            if lib.is_ll_ndk:
+                tags.append(get_str_idx('ll-ndk'))
+            if lib.is_sp_ndk:
+                tags.append(get_str_idx('sp-ndk'))
+            if lib.is_hl_ndk:
+                tags.append(get_str_idx('hl-ndk'))
+
+            if lib in vndk_lib.sp_hal:
+                tags.append(get_str_idx('sp-hal'))
+            if lib in vndk_lib.sp_hal_dep:
+                tags.append(get_str_idx('sp-hal-dep'))
+            if lib in vndk_lib.sp_hal_vndk_stable:
+                tags.append(get_str_idx('sp-hal-vndk-stable'))
+
+            if lib in vndk_lib.sp_ndk_vndk_stable:
+                tags.append(get_str_idx('sp-ndk-vndk-stable'))
+            if lib in vndk_lib.sp_both_vndk_stable:
+                tags.append(get_str_idx('sp-both-vndk-stable'))
+
+            if lib in vndk_lib.vndk_core:
+                tags.append(get_str_idx('vndk-core'))
+            if lib in vndk_lib.vndk_indirect:
+                tags.append(get_str_idx('vndk-indirect'))
+            if lib in vndk_lib.vndk_fwk_ext:
+                tags.append(get_str_idx('vndk-fwk-ext'))
+            if lib in vndk_lib.vndk_vnd_ext:
+                tags.append(get_str_idx('vndk-vnd-ext'))
+            if lib in vndk_lib.extra_vendor_lib:
+                tags.append(get_str_idx('extra-vendor-lib'))
+            return tags
+
+        mods = []
+        for lib in libs:
+            mods.append([get_str_idx(lib.path),
+                         32 if lib.elf.is_32bit else 64,
+                         collect_tags(lib),
+                         collect_deps(lib),
+                         collect_path_sorted_lib_idxs(lib.users)])
+
+        # Generate output files.
+        makedirs(args.output, exist_ok=True)
+        script_dir = os.path.dirname(os.path.abspath(__file__))
+        for name in ('index.html', 'insight.css', 'insight.js'):
+            shutil.copyfile(os.path.join(script_dir, 'assets', name),
+                            os.path.join(args.output, name))
+
+        with open(os.path.join(args.output, 'insight-data.js'), 'w') as f:
+            f.write('''(function () {
+    var strs = ''' + json.dumps(strs) + ''';
+    var mods = ''' + json.dumps(mods) + ''';
+    insight.init(document, strs, mods);
+})();''')
+
+        return 0
+
+
 class VNDKCapCommand(ELFGraphCommand):
     def __init__(self):
         super(VNDKCapCommand, self).__init__(
@@ -2092,6 +2239,7 @@
     register_subcmd(VNDKCapCommand())
     register_subcmd(DepsCommand())
     register_subcmd(DepsClosureCommand())
+    register_subcmd(DepsInsightCommand())
     register_subcmd(SpLibCommand())
     register_subcmd(VNDKStableCommand())