header-checker: Add config file support
This commit add config file support to header-abi-diff so that we can
have per-branch configuration.
Bug: 112760591
Test: ./tests/test.py
Test: ${ANDROID_HOST_OUT}/nativetest64/header-checker-unittests/\
header-checker-unittests
Change-Id: I1fbd76c93991a2b36121a5f4397c62eb72d486ec
This commit is contained in:
@@ -165,6 +165,7 @@ cc_library_host_static {
|
||||
"src/repr/symbol/so_file_parser.cpp",
|
||||
"src/repr/symbol/version_script_parser.cpp",
|
||||
"src/utils/api_level.cpp",
|
||||
"src/utils/config_file.cpp",
|
||||
"src/utils/collect_exported_headers.cpp",
|
||||
"src/utils/string_utils.cpp",
|
||||
],
|
||||
@@ -198,6 +199,7 @@ cc_test_host {
|
||||
"src/repr/symbol/exported_symbol_set_test.cpp",
|
||||
"src/repr/symbol/version_script_parser_test.cpp",
|
||||
"src/utils/api_level_test.cpp",
|
||||
"src/utils/config_file_test.cpp",
|
||||
"src/utils/string_utils_test.cpp",
|
||||
],
|
||||
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
|
||||
#include "diff/abi_diff.h"
|
||||
|
||||
#include "utils/config_file.h"
|
||||
#include "utils/string_utils.h"
|
||||
|
||||
#include <llvm/ADT/SmallString.h>
|
||||
#include <llvm/Support/CommandLine.h>
|
||||
#include <llvm/Support/FileSystem.h>
|
||||
#include <llvm/Support/Path.h>
|
||||
#include <llvm/Support/raw_ostream.h>
|
||||
|
||||
#include <fstream>
|
||||
@@ -25,6 +30,9 @@ using header_checker::diff::HeaderAbiDiff;
|
||||
using header_checker::repr::CompatibilityStatusIR;
|
||||
using header_checker::repr::DiffPolicyOptions;
|
||||
using header_checker::repr::TextFormatIR;
|
||||
using header_checker::utils::ConfigFile;
|
||||
using header_checker::utils::ConfigParser;
|
||||
using header_checker::utils::ParseBool;
|
||||
|
||||
|
||||
static llvm::cl::OptionCategory header_checker_category(
|
||||
@@ -70,10 +78,6 @@ static llvm::cl::opt<bool> check_all_apis(
|
||||
" the dynsym table of a shared library are checked"),
|
||||
llvm::cl::Optional, llvm::cl::cat(header_checker_category));
|
||||
|
||||
static llvm::cl::opt<bool> suppress_local_warnings(
|
||||
"suppress_local_warnings", llvm::cl::desc("suppress local warnings"),
|
||||
llvm::cl::Optional, llvm::cl::cat(header_checker_category));
|
||||
|
||||
static llvm::cl::opt<bool> allow_extensions(
|
||||
"allow-extensions",
|
||||
llvm::cl::desc("Do not return a non zero status on extensions"),
|
||||
@@ -142,6 +146,39 @@ static std::set<std::string> LoadIgnoredSymbols(std::string &symbol_list_path) {
|
||||
return ignored_symbols;
|
||||
}
|
||||
|
||||
static std::string GetConfigFilePath(const std::string &dump_file_path) {
|
||||
llvm::SmallString<128> config_file_path(dump_file_path);
|
||||
llvm::sys::path::remove_filename(config_file_path);
|
||||
llvm::sys::path::append(config_file_path, "config.ini");
|
||||
return config_file_path.str();
|
||||
}
|
||||
|
||||
static void ReadConfigFile(const std::string &config_file_path) {
|
||||
ConfigFile cfg = ConfigParser::ParseFile(config_file_path);
|
||||
if (cfg.HasSection("global")) {
|
||||
for (auto &&[key, value] : cfg.GetSection("global")) {
|
||||
bool value_bool = ParseBool(value);
|
||||
if (key == "allow_adding_removing_weak_symbols") {
|
||||
allow_adding_removing_weak_symbols = value_bool;
|
||||
} else if (key == "advice_only") {
|
||||
advice_only = value_bool;
|
||||
} else if (key == "elf_unreferenced_symbol_errors") {
|
||||
elf_unreferenced_symbol_errors = value_bool;
|
||||
} else if (key == "check_all_apis") {
|
||||
check_all_apis = value_bool;
|
||||
} else if (key == "allow_extensions") {
|
||||
allow_extensions = value_bool;
|
||||
} else if (key == "allow_unreferenced_elf_symbol_changes") {
|
||||
allow_unreferenced_elf_symbol_changes = value_bool;
|
||||
} else if (key == "allow_unreferenced_changes") {
|
||||
allow_unreferenced_changes = value_bool;
|
||||
} else if (key == "consider_opaque_types_different") {
|
||||
consider_opaque_types_different = value_bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char kWarn[] = "\033[36;1mwarning: \033[0m";
|
||||
static const char kError[] = "\033[31;1merror: \033[0m";
|
||||
|
||||
@@ -157,11 +194,16 @@ bool ShouldEmitWarningMessage(CompatibilityStatusIR status) {
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
llvm::cl::ParseCommandLineOptions(argc, argv, "header-checker");
|
||||
|
||||
ReadConfigFile(GetConfigFilePath(old_dump));
|
||||
|
||||
std::set<std::string> ignored_symbols;
|
||||
if (llvm::sys::fs::exists(ignore_symbol_list)) {
|
||||
ignored_symbols = LoadIgnoredSymbols(ignore_symbol_list);
|
||||
}
|
||||
|
||||
DiffPolicyOptions diff_policy_options(consider_opaque_types_different);
|
||||
|
||||
HeaderAbiDiff judge(lib_name, arch, old_dump, new_dump, compatibility_report,
|
||||
ignored_symbols, allow_adding_removing_weak_symbols,
|
||||
diff_policy_options, check_all_apis, text_format_old,
|
||||
|
||||
87
vndk/tools/header-checker/src/utils/config_file.cpp
Normal file
87
vndk/tools/header-checker/src/utils/config_file.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2019 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 "utils/config_file.h"
|
||||
|
||||
#include "utils/string_utils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace header_checker {
|
||||
namespace utils {
|
||||
|
||||
|
||||
ConfigFile ConfigParser::ParseFile(std::istream &istream) {
|
||||
ConfigParser parser(istream);
|
||||
return parser.ParseFile();
|
||||
}
|
||||
|
||||
|
||||
ConfigFile ConfigParser::ParseFile(const std::string &path) {
|
||||
std::ifstream stream(path, std::ios_base::in);
|
||||
return ParseFile(stream);
|
||||
}
|
||||
|
||||
|
||||
ConfigFile ConfigParser::ParseFile() {
|
||||
size_t line_no = 0;
|
||||
std::string line;
|
||||
while (std::getline(stream_, line)) {
|
||||
ParseLine(++line_no, line);
|
||||
}
|
||||
return std::move(cfg_);
|
||||
}
|
||||
|
||||
|
||||
void ConfigParser::ParseLine(size_t line_no, std::string_view line) {
|
||||
if (line.empty() || line[0] == ';' || line[0] == '#') {
|
||||
// Skip empty or comment line.
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse section name line.
|
||||
if (line[0] == '[') {
|
||||
std::string::size_type pos = line.rfind(']');
|
||||
if (pos == std::string::npos) {
|
||||
ReportError(line_no, "bad section name line");
|
||||
return;
|
||||
}
|
||||
std::string_view section_name = line.substr(1, pos - 1);
|
||||
section_ = &cfg_.map_[std::string(section_name)];
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse key-value line.
|
||||
std::string::size_type pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
ReportError(line_no, "bad key-value line");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add key-value entry to current section.
|
||||
std::string_view key = Trim(line.substr(0, pos));
|
||||
std::string_view value = Trim(line.substr(pos + 1));
|
||||
|
||||
if (!section_) {
|
||||
section_ = &cfg_.map_[""];
|
||||
}
|
||||
section_->map_[std::string(key)] = std::string(value);
|
||||
}
|
||||
|
||||
|
||||
} // namespace utils
|
||||
} // namespace header_checker
|
||||
193
vndk/tools/header-checker/src/utils/config_file.h
Normal file
193
vndk/tools/header-checker/src/utils/config_file.h
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright (C) 2019 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.
|
||||
|
||||
#ifndef CONFIG_FILE_H_
|
||||
#define CONFIG_FILE_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace header_checker {
|
||||
namespace utils {
|
||||
|
||||
|
||||
class ConfigParser;
|
||||
|
||||
|
||||
class ConfigSection {
|
||||
public:
|
||||
using MapType = std::map<std::string, std::string>;
|
||||
using const_iterator = MapType::const_iterator;
|
||||
|
||||
|
||||
public:
|
||||
ConfigSection() = default;
|
||||
ConfigSection(ConfigSection &&) = default;
|
||||
ConfigSection &operator=(ConfigSection &&) = default;
|
||||
|
||||
bool HasProperty(const std::string &name) const {
|
||||
return map_.find(name) != map_.end();
|
||||
}
|
||||
|
||||
std::string GetProperty(const std::string &name) const {
|
||||
auto &&it = map_.find(name);
|
||||
if (it == map_.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string operator[](const std::string &name) const {
|
||||
return GetProperty(name);
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return map_.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return map_.end();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ConfigSection(const ConfigSection &) = delete;
|
||||
ConfigSection &operator=(const ConfigSection &) = delete;
|
||||
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> map_;
|
||||
|
||||
friend class ConfigParser;
|
||||
};
|
||||
|
||||
|
||||
class ConfigFile {
|
||||
public:
|
||||
using MapType = std::map<std::string, ConfigSection>;
|
||||
using const_iterator = MapType::const_iterator;
|
||||
|
||||
|
||||
public:
|
||||
ConfigFile() = default;
|
||||
ConfigFile(ConfigFile &&) = default;
|
||||
ConfigFile &operator=(ConfigFile &&) = default;
|
||||
|
||||
bool HasSection(const std::string §ion_name) const {
|
||||
return map_.find(section_name) != map_.end();
|
||||
}
|
||||
|
||||
const ConfigSection &GetSection(const std::string §ion_name) const {
|
||||
auto &&it = map_.find(section_name);
|
||||
assert(it != map_.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const ConfigSection &operator[](const std::string §ion_name) const {
|
||||
return GetSection(section_name);
|
||||
}
|
||||
|
||||
bool HasProperty(const std::string §ion_name,
|
||||
const std::string &property_name) const {
|
||||
auto &&it = map_.find(section_name);
|
||||
if (it == map_.end()) {
|
||||
return false;
|
||||
}
|
||||
return it->second.HasProperty(property_name);
|
||||
}
|
||||
|
||||
std::string GetProperty(const std::string §ion_name,
|
||||
const std::string &property_name) const {
|
||||
auto &&it = map_.find(section_name);
|
||||
if (it == map_.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second.GetProperty(property_name);
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return map_.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return map_.end();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ConfigFile(const ConfigFile &) = delete;
|
||||
ConfigFile &operator=(const ConfigFile &) = delete;
|
||||
|
||||
|
||||
private:
|
||||
std::map<std::string, ConfigSection> map_;
|
||||
|
||||
friend class ConfigParser;
|
||||
};
|
||||
|
||||
|
||||
class ConfigParser {
|
||||
public:
|
||||
using ErrorListener = std::function<void (size_t, const char *)>;
|
||||
|
||||
|
||||
public:
|
||||
ConfigParser(std::istream &stream)
|
||||
: stream_(stream), section_(nullptr) { }
|
||||
|
||||
ConfigFile ParseFile();
|
||||
|
||||
static ConfigFile ParseFile(std::istream &istream);
|
||||
|
||||
static ConfigFile ParseFile(const std::string &path);
|
||||
|
||||
void SetErrorListener(ErrorListener listener) {
|
||||
error_listener_ = std::move(listener);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void ParseLine(size_t line_no, std::string_view line);
|
||||
|
||||
void ReportError(size_t line_no, const char *cause) {
|
||||
if (error_listener_) {
|
||||
error_listener_(line_no, cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ConfigParser(const ConfigParser &) = delete;
|
||||
ConfigParser &operator=(const ConfigParser &) = delete;
|
||||
|
||||
|
||||
private:
|
||||
std::istream &stream_;
|
||||
|
||||
ErrorListener error_listener_;
|
||||
|
||||
ConfigSection *section_;
|
||||
|
||||
ConfigFile cfg_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace utils
|
||||
} // namespace header_checker
|
||||
|
||||
|
||||
#endif // CONFIG_FILE_H_
|
||||
111
vndk/tools/header-checker/src/utils/config_file_test.cpp
Normal file
111
vndk/tools/header-checker/src/utils/config_file_test.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (C) 2019 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 "utils/config_file.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
namespace header_checker {
|
||||
namespace utils {
|
||||
|
||||
|
||||
TEST(ConfigParserTest, Parse) {
|
||||
std::stringstream stream(R"(
|
||||
# Comment line starts with hash symbol
|
||||
; Comment line starts with semicolon
|
||||
|
||||
[section1]
|
||||
key1 = value1
|
||||
key2 = value2
|
||||
|
||||
[section2]
|
||||
key1 = true
|
||||
key2 = false
|
||||
)");
|
||||
|
||||
auto &&cfg = ConfigParser::ParseFile(stream);
|
||||
EXPECT_TRUE(cfg.HasSection("section1"));
|
||||
EXPECT_TRUE(cfg.HasSection("section2"));
|
||||
EXPECT_FALSE(cfg.HasSection("section3"));
|
||||
|
||||
auto &§ion1 = cfg.GetSection("section1");
|
||||
EXPECT_TRUE(section1.HasProperty("key1"));
|
||||
EXPECT_EQ("value1", section1.GetProperty("key1"));
|
||||
EXPECT_TRUE(section1.HasProperty("key2"));
|
||||
EXPECT_EQ("value2", section1.GetProperty("key2"));
|
||||
|
||||
EXPECT_FALSE(section1.HasProperty("key3"));
|
||||
EXPECT_EQ("", section1.GetProperty("key3"));
|
||||
|
||||
auto &§ion2 = cfg.GetSection("section2");
|
||||
EXPECT_TRUE(section2.HasProperty("key1"));
|
||||
EXPECT_EQ("true", section2.GetProperty("key1"));
|
||||
EXPECT_TRUE(section2.HasProperty("key2"));
|
||||
EXPECT_EQ("false", section2.GetProperty("key2"));
|
||||
|
||||
EXPECT_EQ("value1", cfg.GetProperty("section1", "key1"));
|
||||
EXPECT_EQ("value2", cfg.GetProperty("section1", "key2"));
|
||||
|
||||
EXPECT_EQ("value1", cfg["section1"]["key1"]);
|
||||
EXPECT_EQ("value2", cfg["section1"]["key2"]);
|
||||
}
|
||||
|
||||
|
||||
TEST(ConfigParserTest, BadSectionNameLine) {
|
||||
std::stringstream stream(R"(
|
||||
[section1
|
||||
key1 = value1
|
||||
)");
|
||||
|
||||
size_t num_errors = 0;
|
||||
|
||||
ConfigParser parser(stream);
|
||||
parser.SetErrorListener(
|
||||
[&num_errors](size_t line_no, const char *cause) {
|
||||
++num_errors;
|
||||
EXPECT_EQ(2, line_no);
|
||||
EXPECT_STREQ("bad section name line", cause);
|
||||
});
|
||||
parser.ParseFile();
|
||||
|
||||
EXPECT_EQ(1, num_errors);
|
||||
}
|
||||
|
||||
|
||||
TEST(ConfigParserTest, BadKeyValueLine) {
|
||||
std::stringstream stream(R"(
|
||||
[section1]
|
||||
key1
|
||||
)");
|
||||
|
||||
size_t num_errors = 0;
|
||||
|
||||
ConfigParser parser(stream);
|
||||
parser.SetErrorListener(
|
||||
[&num_errors](size_t line_no, const char *cause) {
|
||||
++num_errors;
|
||||
EXPECT_EQ(3, line_no);
|
||||
EXPECT_STREQ("bad key-value line", cause);
|
||||
});
|
||||
parser.ParseFile();
|
||||
|
||||
EXPECT_EQ(1, num_errors);
|
||||
}
|
||||
|
||||
|
||||
} // namespace utils
|
||||
} // namespace header_checker
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include "utils/string_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -86,6 +88,13 @@ std::optional<int> ParseInt(const std::string &s) {
|
||||
}
|
||||
|
||||
|
||||
bool ParseBool(const std::string &s) {
|
||||
std::string value(s);
|
||||
std::transform(value.begin(), value.end(), value.begin(), std::tolower);
|
||||
return (value == "true" || value == "on" || value == "1");
|
||||
}
|
||||
|
||||
|
||||
bool IsGlobPattern(std::string_view s) {
|
||||
return s.find_first_of("*?[") != std::string::npos;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ std::vector<std::string_view> Split(std::string_view s,
|
||||
|
||||
std::optional<int> ParseInt(const std::string &s);
|
||||
|
||||
bool ParseBool(const std::string &s);
|
||||
|
||||
bool IsGlobPattern(std::string_view s);
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,20 @@ TEST(StringUtilsTest, ParseInt) {
|
||||
}
|
||||
|
||||
|
||||
TEST(StringUtilsTest, ParseBool) {
|
||||
EXPECT_FALSE(ParseBool(""));
|
||||
EXPECT_FALSE(ParseBool("false"));
|
||||
EXPECT_FALSE(ParseBool("off"));
|
||||
EXPECT_FALSE(ParseBool("0"));
|
||||
|
||||
EXPECT_TRUE(ParseBool("TRUE"));
|
||||
EXPECT_TRUE(ParseBool("True"));
|
||||
EXPECT_TRUE(ParseBool("true"));
|
||||
EXPECT_TRUE(ParseBool("ON"));
|
||||
EXPECT_TRUE(ParseBool("1"));
|
||||
}
|
||||
|
||||
|
||||
TEST(StringUtilsTest, IsGlobPattern) {
|
||||
EXPECT_TRUE(IsGlobPattern("*.so"));
|
||||
EXPECT_TRUE(IsGlobPattern("[ab].txt"));
|
||||
|
||||
Reference in New Issue
Block a user