versioner: Build SymbolDatabase from version scripts

This commit adds a version script parser so that versioner can build
SymbolDatabase from version scripts.

Bug: 113263746
Test: ./bionic/tools/versioner/run_tests.py
Change-Id: Iedcfe36b51a62693668e07b86aa13592096262db
diff --git a/tools/versioner/platforms/crtbegin.map.txt b/tools/versioner/platforms/crtbegin.map.txt
new file mode 100644
index 0000000..2c08da6
--- /dev/null
+++ b/tools/versioner/platforms/crtbegin.map.txt
@@ -0,0 +1,7 @@
+# This file lists the libc functions are included in `crtbegin.o` and not
+# exported by `libc.so`.
+
+CRTBEGIN {
+  global:
+    atexit;  # arm64 x86 x86_64 mips mips64
+};
diff --git a/tools/versioner/platforms/libc.map.txt b/tools/versioner/platforms/libc.map.txt
new file mode 120000
index 0000000..8527b2e
--- /dev/null
+++ b/tools/versioner/platforms/libc.map.txt
@@ -0,0 +1 @@
+../../../libc/libc.map.txt
\ No newline at end of file
diff --git a/tools/versioner/src/Android.bp b/tools/versioner/src/Android.bp
index f138cc3..c3721ef 100644
--- a/tools/versioner/src/Android.bp
+++ b/tools/versioner/src/Android.bp
@@ -14,6 +14,7 @@
         "Driver.cpp",
         "Preprocessor.cpp",
         "SymbolDatabase.cpp",
+        "SymbolFileParser.cpp",
         "Utils.cpp",
         "VFS.cpp",
     ],
diff --git a/tools/versioner/src/Arch.cpp b/tools/versioner/src/Arch.cpp
index 8d1d41e..4cd9e07 100644
--- a/tools/versioner/src/Arch.cpp
+++ b/tools/versioner/src/Arch.cpp
@@ -44,20 +44,19 @@
   errx(1, "unknown arch '%zu'", size_t(arch));
 }
 
-Arch arch_from_string(const std::string& name) {
-  if (name == "arm") {
-    return Arch::arm;
-  } else if (name == "arm64") {
-    return Arch::arm64;
-  } else if (name == "mips") {
-    return Arch::mips;
-  } else if (name == "mips64") {
-    return Arch::mips64;
-  } else if (name == "x86") {
-    return Arch::x86;
-  } else if (name == "x86_64") {
-    return Arch::x86_64;
-  }
+static const std::unordered_map<std::string, Arch> arch_name_map{
+  {"arm", Arch::arm},
+  {"arm64", Arch::arm64},
+  {"mips", Arch::mips},
+  {"mips64", Arch::mips64},
+  {"x86", Arch::x86},
+  {"x86_64", Arch::x86_64},
+};
 
-  errx(1, "unknown architecture '%s'", name.c_str());
+std::optional<Arch> arch_from_string(const std::string& name) {
+  auto it = arch_name_map.find(name);
+  if (it == arch_name_map.end()) {
+    return std::nullopt;
+  }
+  return std::make_optional(it->second);
 }
diff --git a/tools/versioner/src/Arch.h b/tools/versioner/src/Arch.h
index 41574c1..bac9ec4 100644
--- a/tools/versioner/src/Arch.h
+++ b/tools/versioner/src/Arch.h
@@ -20,8 +20,10 @@
 
 #include <array>
 #include <initializer_list>
+#include <optional>
 #include <set>
 #include <string>
+#include <unordered_map>
 
 enum class Arch : size_t {
   arm = 0,
@@ -33,7 +35,7 @@
 };
 
 std::string to_string(const Arch& arch);
-Arch arch_from_string(const std::string& name);
+std::optional<Arch> arch_from_string(const std::string& name);
 
 template <typename T>
 class ArchMapIterator;
@@ -146,3 +148,23 @@
   { Arch::x86, 9 },
   { Arch::x86_64, 21 },
 };
+
+static constexpr int future_api = 10000;
+
+static const std::unordered_map<std::string, int> api_codename_map{
+  {"G", 9},
+  {"I", 14},
+  {"J", 16},
+  {"J-MR1", 17},
+  {"J-MR2", 18},
+  {"K", 19},
+  {"L", 21},
+  {"L-MR1", 22},
+  {"M", 23},
+  {"N", 24},
+  {"N-MR1", 25},
+  {"O", 26},
+  {"O-MR1", 27},
+  {"P", 28},
+  {"Q", 9001},
+};
diff --git a/tools/versioner/src/SymbolDatabase.cpp b/tools/versioner/src/SymbolDatabase.cpp
index 5b8ed5a..c483c0f 100644
--- a/tools/versioner/src/SymbolDatabase.cpp
+++ b/tools/versioner/src/SymbolDatabase.cpp
@@ -16,6 +16,8 @@
 
 #include "SymbolDatabase.h"
 
+#include "SymbolFileParser.h"
+
 #include <err.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -61,3 +63,53 @@
 
   return result;
 }
+
+static std::map<std::string, NdkSymbolType> parsePlatform(const CompilationType& type,
+                                                          const std::string& platform_dir) {
+  static const std::pair<const char*, bool> wanted_files[] = {
+    {"crtbegin.map.txt", false},
+    {"libc.map.txt", true},
+  };
+
+  std::map<std::string, NdkSymbolType> result;
+
+  for (auto&& [filename, required] : wanted_files) {
+    std::string path = platform_dir + "/" + filename;
+
+    std::optional<SymbolMap> symbols = parseSymbolFile(path, type);
+    if (!symbols) {
+      if (required) {
+        errx(1, "error: failed to load: %s", path.c_str());
+      }
+      continue;
+    }
+
+    for (auto&& [symbol_name, symbol_type] : *symbols) {
+      if (symbol_name.empty()) {
+        continue;
+      }
+
+      if (result.count(symbol_name) != 0) {
+        if (strict) {
+          printf("duplicated symbol '%s' in '%s'\n", symbol_name.c_str(), path.c_str());
+        }
+      }
+
+      result[symbol_name] = symbol_type;
+    }
+  }
+
+  return result;
+}
+
+std::optional<NdkSymbolDatabase> parsePlatforms(const std::set<CompilationType>& types,
+                                                const std::string& platform_dir) {
+  NdkSymbolDatabase result;
+  for (const CompilationType& type : types) {
+    std::map<std::string, NdkSymbolType> symbols = parsePlatform(type, platform_dir);
+    for (const auto& it : symbols) {
+      result[it.first][type] = it.second;
+    }
+  }
+  return std::make_optional(std::move(result));
+}
diff --git a/tools/versioner/src/SymbolDatabase.h b/tools/versioner/src/SymbolDatabase.h
index c5b89d7..dbbba4f 100644
--- a/tools/versioner/src/SymbolDatabase.h
+++ b/tools/versioner/src/SymbolDatabase.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <map>
+#include <optional>
 #include <set>
 #include <string>
 #include <unordered_set>
@@ -32,3 +33,5 @@
 };
 
 using NdkSymbolDatabase = std::map<std::string, std::map<CompilationType, NdkSymbolType>>;
+std::optional<NdkSymbolDatabase> parsePlatforms(const std::set<CompilationType>& types,
+                                                const std::string& platform_dir);
diff --git a/tools/versioner/src/SymbolFileParser.cpp b/tools/versioner/src/SymbolFileParser.cpp
new file mode 100644
index 0000000..33308d9
--- /dev/null
+++ b/tools/versioner/src/SymbolFileParser.cpp
@@ -0,0 +1,308 @@
+/*
+ * 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 "SymbolFileParser.h"
+
+#include "Arch.h"
+#include "CompilationType.h"
+
+#include <android-base/strings.h>
+
+#include <fstream>
+#include <ios>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <err.h>
+
+namespace {
+
+using TagList = std::vector<std::string>;
+
+struct SymbolEnt {
+  std::string name;
+  TagList tags;
+};
+
+using SymbolList = std::vector<SymbolEnt>;
+
+struct Version {
+  std::string name;
+  std::string base;
+  SymbolList symbols;
+  TagList tags;
+};
+
+class SymbolFileParser {
+ public:
+  SymbolFileParser(const std::string& path, const CompilationType& type)
+    : file_path(path),
+      compilation_type(type),
+      api_level_arch_prefix("api-level-" + to_string(type.arch) + "="),
+      intro_arch_perfix("introduced-" + to_string(type.arch) + "="),
+      file(path, std::ios_base::in),
+      curr_line_num(0) {
+  }
+
+  // Parse the version script and build a symbol map.
+  std::optional<SymbolMap> parse() {
+    if (!file) {
+      return std::nullopt;
+    }
+
+    SymbolMap symbol_map;
+    while (hasNextLine()) {
+      auto&& version = parseVersion();
+      if (!version) {
+        return std::nullopt;
+      }
+
+      if (isInArch(version->tags) && isInApi(version->tags)) {
+        for (auto&& [name, tags] : version->symbols) {
+          if (isInArch(tags) && isInApi(tags)) {
+            symbol_map[name] = getSymbolType(tags);
+          }
+        }
+      }
+    }
+    return std::make_optional(std::move(symbol_map));
+  }
+
+ private:
+  // Read a non-empty line from the input and split at the first '#' character.
+  bool hasNextLine() {
+    std::string line;
+    while (std::getline(file, line)) {
+      ++curr_line_num;
+
+      size_t hash_pos = line.find('#');
+      curr_line = android::base::Trim(line.substr(0, hash_pos));
+      if (!curr_line.empty()) {
+        if (hash_pos != std::string::npos) {
+          curr_tags = parseTags(line.substr(hash_pos + 1));
+        } else {
+          curr_tags.clear();
+        }
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Tokenize the tags after the '#' character.
+  static std::vector<std::string> parseTags(const std::string& tags_line) {
+    std::vector<std::string> tags = android::base::Split(tags_line, " \t");
+    tags.erase(std::remove(tags.begin(), tags.end(), ""), tags.end());
+    return tags;
+  }
+
+  // Parse a version scope.
+  std::optional<Version> parseVersion() {
+    size_t start_line_num = curr_line_num;
+
+    std::string::size_type lparen_pos = curr_line.find('{');
+    if (lparen_pos == std::string::npos) {
+      errx(1, "%s:%zu: error: expected '{' cannot be found in this line",
+           file_path.c_str(), curr_line_num);
+    }
+
+    // Record the version name and version tags (before hasNextLine()).
+    std::string name = android::base::Trim(curr_line.substr(0, lparen_pos));
+    TagList tags = std::move(curr_tags);
+
+    // Read symbol lines.
+    SymbolList symbols;
+    bool global_scope = true;
+    bool cpp_scope = false;
+    while (hasNextLine()) {
+      size_t rparen_pos = curr_line.find('}');
+      if (rparen_pos != std::string::npos) {
+        size_t semicolon_pos = curr_line.find(';', rparen_pos + 1);
+        if (semicolon_pos == std::string::npos) {
+          errx(1, "%s:%zu: error: the line that ends a scope must end with ';'",
+               file_path.c_str(), curr_line_num);
+        }
+
+        if (cpp_scope) {
+          cpp_scope = false;
+          continue;
+        }
+
+        std::string base = android::base::Trim(
+          curr_line.substr(rparen_pos + 1, semicolon_pos - 1));
+
+        return std::make_optional(Version{std::move(name), std::move(base),
+                                          std::move(symbols), std::move(tags)});
+      }
+
+      if (android::base::StartsWith(curr_line, R"(extern "C++" {)")) {
+        cpp_scope = true;
+        continue;
+      }
+
+      if (cpp_scope) {
+        continue;
+      }
+
+      size_t colon_pos = curr_line.find(':');
+      if (colon_pos != std::string::npos) {
+        std::string visibility =
+          android::base::Trim(curr_line.substr(0, colon_pos));
+
+        if (visibility == "global") {
+          global_scope = true;
+        } else if (visibility == "local") {
+          global_scope = false;
+        } else {
+          errx(1, "%s:%zu: error: unknown version visibility: %s",
+               file_path.c_str(), curr_line_num, visibility.c_str());
+        }
+        continue;
+      }
+
+      if (global_scope) {
+        size_t semicolon_pos = curr_line.find(';');
+        if (semicolon_pos == std::string::npos) {
+          errx(1, "%s:%zu: error: symbol name line must end with ';'",
+               file_path.c_str(), curr_line_num);
+        }
+
+        std::string symbol_name =
+          android::base::Trim(curr_line.substr(0, semicolon_pos));
+
+        size_t asterisk_pos = symbol_name.find('*');
+        if (asterisk_pos != std::string::npos) {
+          errx(1, "%s:%zu: error: global symbol name must not have wildcards",
+               file_path.c_str(), curr_line_num);
+        }
+
+        symbols.push_back(SymbolEnt{std::move(symbol_name),
+                                    std::move(curr_tags)});
+      }
+    }
+
+    errx(1, "%s:%zu: error: scope started from %zu must be closed before EOF",
+         file_path.c_str(), curr_line_num, start_line_num);
+  }
+
+  static NdkSymbolType getSymbolType(const TagList& tags) {
+    for (auto&& tag : tags) {
+      if (tag == "var") {
+        return NdkSymbolType::variable;
+      }
+    }
+    return NdkSymbolType::function;
+  }
+
+  // isInArch() returns true if there is a matching arch-specific tag or there
+  // are no arch-specific tags.
+  bool isInArch(const TagList& tags) const {
+    bool has_arch_tags = false;
+    for (auto&& tag : tags) {
+      std::optional<Arch> arch = arch_from_string(tag);
+      if (!arch) {
+        continue;
+      }
+      if (*arch == compilation_type.arch) {
+        return true;
+      }
+      has_arch_tags = true;
+    }
+    return !has_arch_tags;
+  }
+
+  // isInApi() returns true if the specified API level is equal to the
+  // api-level tag, or the specified API level is greater than or equal to the
+  // introduced tag, or there are no api-level or introduced tags.
+  bool isInApi(const TagList& tags) const {
+    bool api_level_arch = false;
+    bool intro_arch = false;
+    std::string api_level;
+    std::string intro;
+
+    for (const std::string& tag : tags) {
+      // Check api-level tags.
+      if (android::base::StartsWith(tag, "api-level=") && !api_level_arch) {
+        api_level = tag;
+        continue;
+      }
+      if (android::base::StartsWith(tag, api_level_arch_prefix)) {
+        api_level = tag;
+        api_level_arch = true;
+        continue;
+      }
+
+      // Check introduced tags.
+      if (android::base::StartsWith(tag, "introduced=") && !intro_arch) {
+        intro = tag;
+        continue;
+      }
+      if (android::base::StartsWith(tag, intro_arch_perfix)) {
+        intro = tag;
+        intro_arch = true;
+        continue;
+      }
+
+      if (tag == "future") {
+        return compilation_type.api_level == future_api;
+      }
+    }
+
+    if (intro.empty() && api_level.empty()) {
+      return true;
+    }
+
+    if (!api_level.empty()) {
+      // If an api-level tag is specified, it must be an exact match (mainly
+      // for versioner unit tests).
+      return compilation_type.api_level == decodeApiLevelValue(api_level);
+    }
+
+    return compilation_type.api_level >= decodeApiLevelValue(intro);
+  }
+
+  // Extract and decode the integer API level from api-level or introduced tags.
+  static int decodeApiLevelValue(const std::string& tag) {
+    std::string api_level = tag.substr(tag.find('=') + 1);
+    auto it = api_codename_map.find(api_level);
+    if (it != api_codename_map.end()) {
+      return it->second;
+    }
+    return std::stoi(api_level);
+  }
+
+ private:
+  const std::string& file_path;
+  const CompilationType& compilation_type;
+  const std::string api_level_arch_prefix;
+  const std::string intro_arch_perfix;
+
+  std::ifstream file;
+  std::string curr_line;
+  std::vector<std::string> curr_tags;
+  size_t curr_line_num;
+};
+
+}  // anonymous namespace
+
+
+std::optional<SymbolMap> parseSymbolFile(const std::string& file_path,
+                                         const CompilationType& type) {
+  SymbolFileParser parser(file_path, type);
+  return parser.parse();
+}
diff --git a/tools/versioner/src/SymbolFileParser.h b/tools/versioner/src/SymbolFileParser.h
new file mode 100644
index 0000000..5cdbf2f
--- /dev/null
+++ b/tools/versioner/src/SymbolFileParser.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <optional>
+
+#include "DeclarationDatabase.h"
+#include "SymbolDatabase.h"
+
+using SymbolMap = std::map<std::string, NdkSymbolType>;
+
+std::optional<SymbolMap> parseSymbolFile(const std::string &file,
+                                         const CompilationType& type);
diff --git a/tools/versioner/src/versioner.cpp b/tools/versioner/src/versioner.cpp
index af0c067..73eff0e 100644
--- a/tools/versioner/src/versioner.cpp
+++ b/tools/versioner/src/versioner.cpp
@@ -33,7 +33,6 @@
 #include <iostream>
 #include <map>
 #include <memory>
-#include <optional>
 #include <set>
 #include <sstream>
 #include <string>
@@ -519,8 +518,11 @@
       }
 
       case 'r': {
-        Arch arch = arch_from_string(optarg);
-        selected_architectures.insert(arch);
+        std::optional<Arch> arch = arch_from_string(optarg);
+        if (!arch) {
+          errx(1, "unknown architecture '%s'", optarg);
+        }
+        selected_architectures.insert(*arch);
         break;
       }
 
@@ -613,6 +615,9 @@
     std::string versioner_dir = to_string(top) + "/bionic/tools/versioner";
     location.header_path = versioner_dir + "/current";
     location.dependency_dir = versioner_dir + "/dependencies";
+    if (platform_dir.empty()) {
+      platform_dir = versioner_dir + "/platforms";
+    }
   } else {
     if (!android::base::Realpath(argv[optind], &location.header_path)) {
       err(1, "failed to get realpath for path '%s'", argv[optind]);
@@ -653,6 +658,12 @@
 
   compilation_types = generateCompilationTypes(selected_architectures, selected_levels);
 
+  // Do this before compiling so that we can early exit if the platforms don't match what we
+  // expect.
+  if (!platform_dir.empty()) {
+    symbol_database = parsePlatforms(compilation_types, platform_dir);
+  }
+
   auto start = std::chrono::high_resolution_clock::now();
   std::unique_ptr<HeaderDatabase> declaration_database =
       compileHeaders(compilation_types, location);
diff --git a/tools/versioner/tests/arch_specific/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/arch_specific/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/arch_specific/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/arch_specific/platforms/android-9/arch-x86/symbols/libc.so.functions.txt b/tools/versioner/tests/arch_specific/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
deleted file mode 100644
index 3bd1f0e..0000000
--- a/tools/versioner/tests/arch_specific/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-foo
-bar
diff --git a/tools/versioner/tests/arch_specific/platforms/libc.map.txt b/tools/versioner/tests/arch_specific/platforms/libc.map.txt
new file mode 100644
index 0000000..5aa11ed
--- /dev/null
+++ b/tools/versioner/tests/arch_specific/platforms/libc.map.txt
@@ -0,0 +1,5 @@
+LIBC {
+  global:
+    foo;  # arm x86
+    bar;  # x86
+};
diff --git a/tools/versioner/tests/compilation_error/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/compilation_error/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/compilation_error/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/compilation_error/platforms/libc.map.txt b/tools/versioner/tests/compilation_error/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/compilation_error/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/dependencies/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/dependencies/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/dependencies/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/dependencies/platforms/android-9/arch-x86/symbols/libc.so.functions.txt b/tools/versioner/tests/dependencies/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/dependencies/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/dependencies/platforms/libc.map.txt b/tools/versioner/tests/dependencies/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/dependencies/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/extern_cpp/platforms/android-21/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/extern_cpp/platforms/android-21/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 8b13789..0000000
--- a/tools/versioner/tests/extern_cpp/platforms/android-21/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/tools/versioner/tests/extern_cpp/platforms/android-23/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/extern_cpp/platforms/android-23/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 36fe04f..0000000
--- a/tools/versioner/tests/extern_cpp/platforms/android-23/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-__gnu_basename
diff --git a/tools/versioner/tests/extern_cpp/platforms/libc.map.txt b/tools/versioner/tests/extern_cpp/platforms/libc.map.txt
new file mode 100644
index 0000000..297d1fb
--- /dev/null
+++ b/tools/versioner/tests/extern_cpp/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    __gnu_basename;  # introduced=23
+};
diff --git a/tools/versioner/tests/extern_cpp_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/extern_cpp_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/extern_cpp_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/extern_cpp_mismatch/platforms/libc.map.txt b/tools/versioner/tests/extern_cpp_mismatch/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/extern_cpp_mismatch/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/future/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/future/platforms/libc.map.txt
similarity index 100%
rename from tools/versioner/tests/future/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
rename to tools/versioner/tests/future/platforms/libc.map.txt
diff --git a/tools/versioner/tests/future_arch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/future_arch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/future_arch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/future_arch/platforms/android-9/arch-x86/symbols/libc.so.functions.txt b/tools/versioner/tests/future_arch/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/future_arch/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/future_arch/platforms/libc.map.txt b/tools/versioner/tests/future_arch/platforms/libc.map.txt
new file mode 100644
index 0000000..f56190e
--- /dev/null
+++ b/tools/versioner/tests/future_arch/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;  # arm
+};
diff --git a/tools/versioner/tests/inline/platforms/android-12/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/inline/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/inline/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/inline/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/inline/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/inline/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/inline/platforms/libc.map.txt b/tools/versioner/tests/inline/platforms/libc.map.txt
new file mode 100644
index 0000000..4dced92
--- /dev/null
+++ b/tools/versioner/tests/inline/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;  # introduced=12
+};
diff --git a/tools/versioner/tests/missing_api/platforms/android-12/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/missing_api/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/missing_api/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/missing_api/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/missing_api/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/missing_api/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/missing_api/platforms/libc.map.txt b/tools/versioner/tests/missing_api/platforms/libc.map.txt
new file mode 100644
index 0000000..3701a9d
--- /dev/null
+++ b/tools/versioner/tests/missing_api/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;  # api-level=9
+};
diff --git a/tools/versioner/tests/missing_arch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/missing_arch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/missing_arch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/missing_arch/platforms/android-9/arch-x86/symbols/libc.so.functions.txt b/tools/versioner/tests/missing_arch/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/missing_arch/platforms/android-9/arch-x86/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/missing_arch/platforms/libc.map.txt b/tools/versioner/tests/missing_arch/platforms/libc.map.txt
new file mode 100644
index 0000000..f56190e
--- /dev/null
+++ b/tools/versioner/tests/missing_arch/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;  # arm
+};
diff --git a/tools/versioner/tests/multiple_decl/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/multiple_decl/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/multiple_decl/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/multiple_decl/platforms/libc.map.txt b/tools/versioner/tests/multiple_decl/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/multiple_decl/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/multiple_decl_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/multiple_decl_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/multiple_decl_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/multiple_decl_mismatch/platforms/libc.map.txt b/tools/versioner/tests/multiple_decl_mismatch/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/multiple_decl_mismatch/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/multiple_definition/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/multiple_definition/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/multiple_definition/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/multiple_definition/platforms/libc.map.txt b/tools/versioner/tests/multiple_definition/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/multiple_definition/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/multiple_definition_ok/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/multiple_definition_ok/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/multiple_definition_ok/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/multiple_definition_ok/platforms/libc.map.txt b/tools/versioner/tests/multiple_definition_ok/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/multiple_definition_ok/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/obsoleted/platforms/android-12/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/obsoleted/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/obsoleted/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/obsoleted/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/obsoleted/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/obsoleted/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/obsoleted/platforms/libc.map.txt b/tools/versioner/tests/obsoleted/platforms/libc.map.txt
new file mode 100644
index 0000000..3701a9d
--- /dev/null
+++ b/tools/versioner/tests/obsoleted/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;  # api-level=9
+};
diff --git a/tools/versioner/tests/preprocessor/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/preprocessor/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/preprocessor/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/preprocessor_extern_cpp/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/preprocessor_extern_cpp/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/preprocessor_extern_cpp/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/preprocessor_merging/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/preprocessor_merging/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/preprocessor_merging/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/smoke/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/smoke/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/smoke/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/smoke/platforms/libc.map.txt b/tools/versioner/tests/smoke/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/smoke/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/unnamed_bitfield/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/unnamed_bitfield/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/unnamed_bitfield/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/unnamed_bitfield/platforms/libc.map.txt b/tools/versioner/tests/unnamed_bitfield/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/unnamed_bitfield/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};
diff --git a/tools/versioner/tests/version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/version_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/version_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index 257cc56..0000000
--- a/tools/versioner/tests/version_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
@@ -1 +0,0 @@
-foo
diff --git a/tools/versioner/tests/version_mismatch/platforms/libc.map.txt b/tools/versioner/tests/version_mismatch/platforms/libc.map.txt
new file mode 100644
index 0000000..6cc4a2e
--- /dev/null
+++ b/tools/versioner/tests/version_mismatch/platforms/libc.map.txt
@@ -0,0 +1,4 @@
+LIBC {
+  global:
+    foo;
+};