Merge "Add dump_hals_for_release.py and analyze_matrix."
diff --git a/analyze_matrix/Android.bp b/analyze_matrix/Android.bp
new file mode 100644
index 0000000..1d8beb1
--- /dev/null
+++ b/analyze_matrix/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary_host {
+    name: "analyze_matrix",
+    defaults: ["libvintf-defaults"],
+    shared_libs: [
+        "libbase",
+        "libhidl-gen-utils",
+        "libgflags",
+        "libvintf",
+    ],
+    cpp_std: "experimental",
+    srcs: [
+        "analyze_matrix.cpp",
+    ],
+}
+
+python_binary_host {
+    name: "dump_hals_for_release",
+    srcs: [
+        "dump_hals_for_release.py",
+    ],
+    version: {
+        py2: {
+          enabled: true,
+        },
+        py3: {
+          enabled: false,
+        },
+    },
+}
diff --git a/analyze_matrix/analyze_matrix.cpp b/analyze_matrix/analyze_matrix.cpp
new file mode 100644
index 0000000..49134ef
--- /dev/null
+++ b/analyze_matrix/analyze_matrix.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <optional>
+#include <set>
+
+#include <android-base/logging.h>
+#include <gflags/gflags.h>
+#include <hidl-util/FqInstance.h>
+#include <vintf/FileSystem.h>
+#include <vintf/parse_string.h>
+#include <vintf/parse_xml.h>
+
+namespace android {
+namespace vintf {
+
+namespace {
+
+template <typename T>
+std::optional<T> readObject(const std::string& path, const XmlConverter<T>& converter) {
+    std::string xml;
+    std::string error;
+    status_t err = details::FileSystemImpl().fetch(path, &xml, &error);
+    if (err != OK) {
+        LOG(ERROR) << "Cannot read '" << path << "': " << error;
+        return std::nullopt;
+    }
+    auto ret = std::make_optional<T>();
+    if (!converter(&ret.value(), xml, &error)) {
+        LOG(ERROR) << "Cannot parse '" << path << "': " << error;
+        return std::nullopt;
+    }
+    return ret;
+}
+
+std::optional<std::set<std::string>> getInterfaces(const CompatibilityMatrix& mat) {
+    auto set = std::make_optional<std::set<std::string>>();
+    mat.forEachInstance([&set](const auto& matrixInstance) {
+        for (auto minorVer = matrixInstance.versionRange().minMinor;
+             minorVer <= matrixInstance.versionRange().maxMinor; ++minorVer) {
+            FqInstance fqInstance;
+            if (!fqInstance.setTo(matrixInstance.package(), matrixInstance.versionRange().majorVer,
+                                  minorVer, matrixInstance.interface())) {
+                LOG(ERROR) << "Matrix not valid; '" << matrixInstance.package() << "@"
+                           << matrixInstance.versionRange().majorVer << "." << minorVer
+                           << "::" << matrixInstance.interface() << "' is not a valid FQName.";
+                set = std::nullopt;
+                return false;  // break
+            }
+
+            set->insert(fqInstance.string());
+        }
+        return true;  // continue
+    });
+    return set;
+}
+
+}  // namespace
+
+}  // namespace vintf
+}  // namespace android
+
+DEFINE_string(input, "", "Input compatibility matrix file");
+static bool ValidateInput(const char* /* flagname */, const std::string& value) {
+    return !value.empty();
+}
+DEFINE_validator(input, &ValidateInput);
+
+DEFINE_bool(level, false, "Write level (FCM version) of the compatibility matrix.");
+DEFINE_bool(interfaces, false, "Write strings like \"android.hardware.foo@1.0::IFoo\".");
+
+int main(int argc, char** argv) {
+    using namespace android::vintf;
+
+    gflags::ParseCommandLineFlags(&argc, &argv, true /* remove flags */);
+
+    auto mat = readObject(FLAGS_input, gCompatibilityMatrixConverter);
+    if (!mat) {
+        return 1;
+    }
+
+    bool written = false;
+
+    if (FLAGS_level) {
+        if (mat->level() == Level::UNSPECIFIED) {
+            LOG(WARNING) << "FCM version is unspecified.";
+        }
+        std::cout << mat->level() << std::endl;
+
+        written = true;
+    }
+
+    if (FLAGS_interfaces) {
+        auto pvs = getInterfaces(*mat);
+        if (!pvs) {
+            return 1;
+        }
+        if (pvs->empty()) {
+            LOG(WARNING) << "No package and versions are found.";
+        }
+
+        for (const auto& pv : *pvs) {
+            std::cout << pv << std::endl;
+        }
+
+        written = true;
+    }
+
+    if (!written) {
+        LOG(ERROR) << "No output format is set.";
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/analyze_matrix/dump_hals_for_release.py b/analyze_matrix/dump_hals_for_release.py
new file mode 100755
index 0000000..47519c6
--- /dev/null
+++ b/analyze_matrix/dump_hals_for_release.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Dump new HIDL types that are introduced in each FCM version.
+"""
+
+from __future__ import print_function
+
+import argparse
+import collections
+import json
+import os
+import subprocess
+import sys
+
+class Globals:
+    pass
+
+def call(args):
+    if Globals.verbose:
+        print(' '.join(args), file=sys.stderr)
+    sp = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = sp.communicate()
+    return sp.returncode, out.decode(), err.decode()
+
+def check_call(args):
+    r, o, e = call(args)
+    assert not r, '`{}` returns {}'.format(' '.join(args), r)
+    return o, e
+
+def lines(o):
+    return filter(lambda line: line, (line.strip() for line in o.split()))
+
+def to_level(s):
+    if s == 'legacy': return 0
+    if s == '': return float('inf')
+    return int(s)
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('--verbose', help='Verbose mode', action='store_true')
+    parser.add_argument('--pretty', help='Print pretty JSON', action='store_true')
+    parser.add_argument('--hidl-gen', help='Location of hidl-gen', required=True)
+    parser.add_argument('--analyze-matrix', help='Location of analyze_matrix', required=True)
+    parser.add_argument('--compatibility-matrix', metavar='FILE',
+        help='Location of framework compatibility matrices', nargs='+', required=True)
+    parser.add_argument('--package-root', metavar='PACKAGE:PATH',
+        help='package roots provided to hidl-gen, e.g. android.hardware:hardware/interfaces',
+        nargs='+')
+    parser.parse_args(namespace=Globals)
+
+    interfaces_for_level = dict()
+    for matrix_path in Globals.compatibility_matrix:
+        r, o, _ = call([Globals.analyze_matrix, '--input', matrix_path, '--level'])
+        if r:
+            # Not a compatibility matrix, ignore
+            continue
+        # stderr may contain warning message if level is empty
+        level = o.strip()
+
+        o, _ = check_call([Globals.analyze_matrix, '--input', matrix_path, '--interfaces'])
+        # stderr may contain warning message if no interfaces
+
+        interfaces = list(lines(o))
+        if not interfaces: continue
+
+        if level not in interfaces_for_level:
+            interfaces_for_level[level] = set()
+
+        # Put top level interfaces
+        interfaces_for_level[level].update(interfaces)
+
+        # Put interfaces referenced by top level interfaces
+        args = [Globals.hidl_gen, '-Ldependencies']
+        if Globals.package_root:
+            args.append('-R')
+            for package_root in Globals.package_root:
+                args.extend(['-r', package_root])
+        args.extend(interfaces)
+        o, e = check_call(args)
+        assert not e, '`{}` has written to stderr:\n{}'.format(' '.join(args), e)
+        interfaces_for_level[level].update(lines(o))
+
+    seen_interfaces = set()
+    new_interfaces_for_level = collections.OrderedDict()
+    for level, interfaces in sorted(interfaces_for_level.items(), key=lambda tup: to_level(tup[0])):
+        new_interfaces_for_level[level] = sorted(interfaces - seen_interfaces)
+        seen_interfaces.update(interfaces)
+
+    print(json.dumps(new_interfaces_for_level,
+        separators=None if Globals.pretty else (',',':'),
+        indent=4 if Globals.pretty else None))
+
+if __name__ == '__main__':
+    main()