You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1064 lines
35 KiB
1064 lines
35 KiB
/*
|
|
* Copyright (C) 2016 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 "Coordinator.h"
|
|
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <hidl-hash/Hash.h>
|
|
#include <hidl-util/Formatter.h>
|
|
#include <hidl-util/StringHelper.h>
|
|
#include <iostream>
|
|
|
|
#include "AST.h"
|
|
#include "Interface.h"
|
|
#include "hidl-gen_l.h"
|
|
|
|
static bool existdir(const char *name) {
|
|
DIR *dir = opendir(name);
|
|
if (dir == nullptr) {
|
|
return false;
|
|
}
|
|
closedir(dir);
|
|
return true;
|
|
}
|
|
|
|
namespace android {
|
|
|
|
const std::string &Coordinator::getRootPath() const {
|
|
return mRootPath;
|
|
}
|
|
|
|
void Coordinator::setRootPath(const std::string &rootPath) {
|
|
mRootPath = rootPath;
|
|
|
|
if (!mRootPath.empty() && !StringHelper::EndsWith(mRootPath, "/")) {
|
|
mRootPath += "/";
|
|
}
|
|
}
|
|
|
|
void Coordinator::setOutputPath(const std::string& outputPath) {
|
|
mOutputPath = outputPath;
|
|
}
|
|
|
|
void Coordinator::setVerbose(bool verbose) {
|
|
mVerbose = verbose;
|
|
}
|
|
|
|
void Coordinator::setRequireFrozen(bool requireFrozen) {
|
|
mRequireFrozen = requireFrozen;
|
|
}
|
|
|
|
bool Coordinator::isVerbose() const {
|
|
return mVerbose;
|
|
}
|
|
|
|
void Coordinator::setDepFile(const std::string& depFile) {
|
|
mDepFile = depFile;
|
|
}
|
|
|
|
const std::string& Coordinator::getOwner() const {
|
|
return mOwner;
|
|
}
|
|
void Coordinator::setOwner(const std::string& owner) {
|
|
mOwner = owner;
|
|
}
|
|
|
|
status_t Coordinator::addPackagePath(const std::string& root, const std::string& path, std::string* error) {
|
|
FQName package = FQName(root, "0.0", "");
|
|
for (const PackageRoot &packageRoot : mPackageRoots) {
|
|
if (packageRoot.root.inPackage(root) || package.inPackage(packageRoot.root.package())) {
|
|
if (error != nullptr) {
|
|
*error = "ERROR: conflicting package roots " +
|
|
packageRoot.root.package() +
|
|
" and " +
|
|
root;
|
|
}
|
|
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
mPackageRoots.push_back({path, package});
|
|
return OK;
|
|
}
|
|
void Coordinator::addDefaultPackagePath(const std::string& root, const std::string& path) {
|
|
addPackagePath(root, path, nullptr /* error */);
|
|
}
|
|
|
|
Formatter Coordinator::getFormatter(const FQName& fqName, Location location,
|
|
const std::string& fileName) const {
|
|
if (location == Location::STANDARD_OUT) {
|
|
return Formatter(stdout);
|
|
}
|
|
|
|
std::string filepath;
|
|
status_t err = getFilepath(fqName, location, fileName, &filepath);
|
|
if (err != OK) {
|
|
return Formatter::invalid();
|
|
}
|
|
|
|
onFileAccess(filepath, "w");
|
|
|
|
if (!Coordinator::MakeParentHierarchy(filepath)) {
|
|
fprintf(stderr, "ERROR: could not make directories for %s.\n", filepath.c_str());
|
|
return Formatter::invalid();
|
|
}
|
|
|
|
FILE* file = fopen(filepath.c_str(), "w");
|
|
|
|
if (file == nullptr) {
|
|
fprintf(stderr, "ERROR: could not open file %s: %d\n", filepath.c_str(), errno);
|
|
return Formatter::invalid();
|
|
}
|
|
|
|
return Formatter(file);
|
|
}
|
|
|
|
status_t Coordinator::getFilepath(const FQName& fqName, Location location,
|
|
const std::string& fileName, std::string* path) const {
|
|
status_t err;
|
|
std::string packagePath;
|
|
std::string packageRootPath;
|
|
|
|
switch (location) {
|
|
case Location::DIRECT: { /* nothing */
|
|
*path = mOutputPath + fileName;
|
|
} break;
|
|
case Location::PACKAGE_ROOT: {
|
|
err = getPackagePath(fqName, false /* relative */, false /* sanitized */, &packagePath);
|
|
if (err != OK) return err;
|
|
|
|
*path = mOutputPath + packagePath + fileName;
|
|
} break;
|
|
case Location::GEN_OUTPUT: {
|
|
err = convertPackageRootToPath(fqName, &packageRootPath);
|
|
if (err != OK) return err;
|
|
err = getPackagePath(fqName, true /* relative */, false /* sanitized */, &packagePath);
|
|
if (err != OK) return err;
|
|
|
|
*path = mOutputPath + packageRootPath + packagePath + fileName;
|
|
} break;
|
|
case Location::GEN_SANITIZED: {
|
|
err = convertPackageRootToPath(fqName, &packageRootPath);
|
|
if (err != OK) return err;
|
|
err = getPackagePath(fqName, true /* relative */, true /* sanitized */, &packagePath);
|
|
if (err != OK) return err;
|
|
|
|
*path = mOutputPath + packageRootPath + packagePath + fileName;
|
|
} break;
|
|
default: { CHECK(false) << "Invalid location: " << static_cast<size_t>(location); }
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
void Coordinator::onFileAccess(const std::string& path, const std::string& mode) const {
|
|
if (mode == "r") {
|
|
// This is a global list. It's not cleared when a second fqname is processed for
|
|
// two reasons:
|
|
// 1). If there is a bug in hidl-gen, the dependencies on the first project from
|
|
// the second would be required to recover correctly when the bug is fixed.
|
|
// 2). This option is never used in Android builds.
|
|
mReadFiles.insert(makeRelative(path));
|
|
}
|
|
|
|
if (!mVerbose) {
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"VERBOSE: file access %s %s\n", path.c_str(), mode.c_str());
|
|
}
|
|
|
|
status_t Coordinator::writeDepFile(const std::string& forFile) const {
|
|
// No dep file requested
|
|
if (mDepFile.empty()) return OK;
|
|
|
|
onFileAccess(mDepFile, "w");
|
|
|
|
FILE* file = fopen(mDepFile.c_str(), "w");
|
|
if (file == nullptr) {
|
|
fprintf(stderr, "ERROR: could not open dep file at %s.\n", mDepFile.c_str());
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
Formatter out(file, 2 /* spacesPerIndent */);
|
|
out << StringHelper::LTrim(forFile, mOutputPath) << ": \\\n";
|
|
out.indent([&] {
|
|
for (const std::string& file : mReadFiles) {
|
|
out << makeRelative(file) << " \\\n";
|
|
}
|
|
});
|
|
return OK;
|
|
}
|
|
|
|
AST* Coordinator::parse(const FQName& fqName, std::set<AST*>* parsedASTs,
|
|
Enforce enforcement) const {
|
|
AST* ret;
|
|
status_t err = parseOptional(fqName, &ret, parsedASTs, enforcement);
|
|
if (err != OK) CHECK(ret == nullptr); // internal consistency
|
|
|
|
// only in a handful of places do we want to distinguish between
|
|
// a missing file and a bad AST. Everywhere else, we just want to
|
|
// throw an error if we expect an AST to be present but it is not.
|
|
return ret;
|
|
}
|
|
|
|
status_t Coordinator::parseOptional(const FQName& fqName, AST** ast, std::set<AST*>* parsedASTs,
|
|
Enforce enforcement) const {
|
|
CHECK(fqName.isFullyQualified());
|
|
|
|
auto it = mCache.find(fqName);
|
|
if (it != mCache.end()) {
|
|
*ast = (*it).second;
|
|
|
|
if (*ast != nullptr && parsedASTs != nullptr) {
|
|
parsedASTs->insert(*ast);
|
|
}
|
|
|
|
if (*ast == nullptr) {
|
|
// circular import OR that AST has errors in it
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
// Add this to the cache immediately, so we can discover circular imports.
|
|
mCache[fqName] = nullptr;
|
|
|
|
std::string packagePath;
|
|
status_t err =
|
|
getPackagePath(fqName, false /* relative */, false /* sanitized */, &packagePath);
|
|
if (err != OK) return err;
|
|
|
|
const std::string path = makeAbsolute(packagePath + fqName.name() + ".hal");
|
|
|
|
*ast = new AST(this, &Hash::getHash(path));
|
|
|
|
if (fqName.name() != "types") {
|
|
// If types.hal for this AST's package existed, make it's defined
|
|
// types available to the (about to be parsed) AST right away.
|
|
(*ast)->addImplicitImport(fqName.getTypesForPackage());
|
|
}
|
|
|
|
std::unique_ptr<FILE, std::function<void(FILE*)>> file(fopen(path.c_str(), "rb"), fclose);
|
|
|
|
if (file == nullptr) {
|
|
mCache.erase(fqName); // nullptr in cache is used to find circular imports
|
|
delete *ast;
|
|
*ast = nullptr;
|
|
return OK; // File does not exist, nullptr AST* == file doesn't exist.
|
|
}
|
|
|
|
onFileAccess(path, "r");
|
|
|
|
// parse file takes ownership of file
|
|
if (parseFile(*ast, std::move(file)) != OK || (*ast)->postParse() != OK) {
|
|
delete *ast;
|
|
*ast = nullptr;
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
if ((*ast)->package().package() != fqName.package() ||
|
|
(*ast)->package().version() != fqName.version()) {
|
|
fprintf(stderr,
|
|
"ERROR: File at '%s' does not match expected package and/or "
|
|
"version.\n",
|
|
path.c_str());
|
|
|
|
err = UNKNOWN_ERROR;
|
|
} else {
|
|
if ((*ast)->isInterface()) {
|
|
if (fqName.name() == "types") {
|
|
fprintf(stderr,
|
|
"ERROR: File at '%s' declares an interface '%s' "
|
|
"instead of the expected types common to the package.\n",
|
|
path.c_str(), (*ast)->getInterface()->definedName().c_str());
|
|
|
|
err = UNKNOWN_ERROR;
|
|
} else if ((*ast)->getInterface()->definedName() != fqName.name()) {
|
|
fprintf(stderr,
|
|
"ERROR: File at '%s' does not declare interface type "
|
|
"'%s'.\n",
|
|
path.c_str(),
|
|
fqName.name().c_str());
|
|
|
|
err = UNKNOWN_ERROR;
|
|
}
|
|
} else if (fqName.name() != "types") {
|
|
fprintf(stderr,
|
|
"ERROR: File at '%s' declares types rather than the "
|
|
"expected interface type '%s'.\n",
|
|
path.c_str(),
|
|
fqName.name().c_str());
|
|
|
|
err = UNKNOWN_ERROR;
|
|
} else if ((*ast)->definesInterfaces()) {
|
|
fprintf(stderr,
|
|
"ERROR: types.hal file at '%s' declares at least one "
|
|
"interface type.\n",
|
|
path.c_str());
|
|
|
|
err = UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
if (err != OK) {
|
|
delete *ast;
|
|
*ast = nullptr;
|
|
return err;
|
|
}
|
|
|
|
if (parsedASTs != nullptr) {
|
|
parsedASTs->insert(*ast);
|
|
}
|
|
|
|
// put it into the cache now, so that enforceRestrictionsOnPackage can
|
|
// parse fqName.
|
|
mCache[fqName] = *ast;
|
|
|
|
// For each .hal file that hidl-gen parses, the whole package will be checked.
|
|
err = enforceRestrictionsOnPackage(fqName, enforcement);
|
|
if (err != OK) {
|
|
mCache[fqName] = nullptr;
|
|
delete *ast;
|
|
*ast = nullptr;
|
|
return err;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
const Coordinator::PackageRoot* Coordinator::findPackageRoot(const FQName& fqName) const {
|
|
CHECK(!fqName.package().empty()) << fqName.string();
|
|
|
|
// Find the right package prefix and path for this FQName. For
|
|
// example, if FQName is "android.hardware.nfc@1.0::INfc", and the
|
|
// prefix:root is set to [ "android.hardware:hardware/interfaces",
|
|
// "vendor.qcom.hardware:vendor/qcom"], then we will identify the
|
|
// prefix "android.hardware" and the package root
|
|
// "hardware/interfaces".
|
|
|
|
auto ret = mPackageRoots.end();
|
|
for (auto it = mPackageRoots.begin(); it != mPackageRoots.end(); it++) {
|
|
if (!fqName.inPackage(it->root.package())) {
|
|
continue;
|
|
}
|
|
|
|
if (ret != mPackageRoots.end()) {
|
|
std::cerr << "ERROR: Multiple package roots found for " << fqName.string() << " ("
|
|
<< it->root.package() << " and " << ret->root.package() << ")\n";
|
|
return nullptr;
|
|
}
|
|
|
|
ret = it;
|
|
}
|
|
|
|
if (ret == mPackageRoots.end()) {
|
|
std::cerr << "ERROR: Package root not specified for " << fqName.string() << "\n";
|
|
return nullptr;
|
|
}
|
|
|
|
return &(*ret);
|
|
}
|
|
|
|
std::string Coordinator::makeAbsolute(const std::string& path) const {
|
|
if (StringHelper::StartsWith(path, "/") || mRootPath.empty()) {
|
|
return path;
|
|
}
|
|
|
|
return mRootPath + path;
|
|
}
|
|
|
|
std::string Coordinator::makeRelative(const std::string& filename) const {
|
|
return StringHelper::LTrim(filename, mRootPath);
|
|
}
|
|
|
|
status_t Coordinator::getPackageRoot(const FQName& fqName, std::string* root) const {
|
|
const PackageRoot* packageRoot = findPackageRoot(fqName);
|
|
if (packageRoot == nullptr) {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*root = packageRoot->root.package();
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::getPackageRootPath(const FQName& fqName, std::string* path) const {
|
|
const PackageRoot* packageRoot = findPackageRoot(fqName);
|
|
if (packageRoot == nullptr) {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
*path = packageRoot->path;
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::getPackagePath(const FQName& fqName, bool relative, bool sanitized,
|
|
std::string* path) const {
|
|
const PackageRoot* packageRoot = findPackageRoot(fqName);
|
|
if (packageRoot == nullptr) return UNKNOWN_ERROR;
|
|
|
|
// Given FQName of "android.hardware.nfc.test@1.0::IFoo" and a prefix
|
|
// "android.hardware", the suffix is "nfc.test".
|
|
std::string suffix = StringHelper::LTrim(fqName.package(), packageRoot->root.package());
|
|
suffix = StringHelper::LTrim(suffix, ".");
|
|
|
|
std::vector<std::string> suffixComponents;
|
|
StringHelper::SplitString(suffix, '.', &suffixComponents);
|
|
|
|
std::vector<std::string> components;
|
|
if (!relative) {
|
|
components.push_back(StringHelper::RTrimAll(packageRoot->path, "/"));
|
|
}
|
|
components.insert(components.end(), suffixComponents.begin(), suffixComponents.end());
|
|
components.push_back(sanitized ? fqName.sanitizedVersion() : fqName.version());
|
|
|
|
*path = StringHelper::JoinStrings(components, "/") + "/";
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::getPackageInterfaceFiles(
|
|
const FQName &package,
|
|
std::vector<std::string> *fileNames) const {
|
|
if (fileNames) fileNames->clear();
|
|
|
|
std::string packagePath;
|
|
status_t err =
|
|
getPackagePath(package, false /* relative */, false /* sanitized */, &packagePath);
|
|
if (err != OK) return err;
|
|
|
|
const std::string path = makeAbsolute(packagePath);
|
|
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(path.c_str()), closedir);
|
|
|
|
if (dir == nullptr) {
|
|
fprintf(stderr, "ERROR: Could not open package path %s for package %s:\n%s\n",
|
|
packagePath.c_str(), package.string().c_str(), path.c_str());
|
|
return -errno;
|
|
}
|
|
|
|
if (fileNames == nullptr) {
|
|
return OK;
|
|
}
|
|
|
|
struct dirent *ent;
|
|
while ((ent = readdir(dir.get())) != nullptr) {
|
|
// filesystems may not support d_type and return DT_UNKNOWN
|
|
if (ent->d_type == DT_UNKNOWN) {
|
|
struct stat sb;
|
|
const auto filename = packagePath + std::string(ent->d_name);
|
|
if (stat(filename.c_str(), &sb) == -1) {
|
|
fprintf(stderr, "ERROR: Could not stat %s\n", filename.c_str());
|
|
return -errno;
|
|
}
|
|
if ((sb.st_mode & S_IFMT) != S_IFREG) {
|
|
continue;
|
|
}
|
|
} else if (ent->d_type != DT_REG) {
|
|
continue;
|
|
}
|
|
|
|
const auto suffix = ".hal";
|
|
const auto suffix_len = std::strlen(suffix);
|
|
const auto d_namelen = strlen(ent->d_name);
|
|
|
|
if (d_namelen < suffix_len
|
|
|| strcmp(ent->d_name + d_namelen - suffix_len, suffix)) {
|
|
continue;
|
|
}
|
|
|
|
fileNames->push_back(std::string(ent->d_name, d_namelen - suffix_len));
|
|
}
|
|
|
|
std::sort(fileNames->begin(), fileNames->end(),
|
|
[](const std::string& lhs, const std::string& rhs) -> bool {
|
|
if (lhs == "types") {
|
|
return true;
|
|
}
|
|
if (rhs == "types") {
|
|
return false;
|
|
}
|
|
return lhs < rhs;
|
|
});
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::appendPackageInterfacesToVector(
|
|
const FQName &package,
|
|
std::vector<FQName> *packageInterfaces) const {
|
|
packageInterfaces->clear();
|
|
|
|
std::vector<std::string> fileNames;
|
|
status_t err = getPackageInterfaceFiles(package, &fileNames);
|
|
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
|
|
for (const auto &fileName : fileNames) {
|
|
FQName subFQName(package.package(), package.version(), fileName);
|
|
packageInterfaces->push_back(subFQName);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::convertPackageRootToPath(const FQName& fqName, std::string* path) const {
|
|
std::string packageRoot;
|
|
status_t err = getPackageRoot(fqName, &packageRoot);
|
|
if (err != OK) return err;
|
|
|
|
if (*(packageRoot.end()--) != '.') {
|
|
packageRoot += '.';
|
|
}
|
|
|
|
std::replace(packageRoot.begin(), packageRoot.end(), '.', '/');
|
|
|
|
*path = packageRoot; // now converted to a path
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::isTypesOnlyPackage(const FQName& package, bool* result) const {
|
|
std::vector<FQName> packageInterfaces;
|
|
|
|
status_t err = appendPackageInterfacesToVector(package, &packageInterfaces);
|
|
|
|
if (err != OK) {
|
|
*result = false;
|
|
return err;
|
|
}
|
|
|
|
*result = packageInterfaces.size() == 1 && packageInterfaces[0].name() == "types";
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::addUnreferencedTypes(const std::vector<FQName>& packageInterfaces,
|
|
std::set<FQName>* unreferencedDefinitions,
|
|
std::set<FQName>* unreferencedImports) const {
|
|
CHECK(unreferencedDefinitions != nullptr);
|
|
CHECK(unreferencedImports != nullptr);
|
|
|
|
std::set<FQName> packageDefinedTypes;
|
|
std::set<FQName> packageReferencedTypes;
|
|
std::set<FQName> packageImportedTypes;
|
|
std::set<FQName> typesDefinedTypes; // only types.hal types
|
|
|
|
for (const auto& fqName : packageInterfaces) {
|
|
AST* ast = parse(fqName, nullptr /*imported*/, Coordinator::Enforce::NONE);
|
|
if (!ast) {
|
|
std::cerr << "ERROR: Could not parse " << fqName.string() << ". Aborting." << std::endl;
|
|
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
ast->addDefinedTypes(&packageDefinedTypes);
|
|
ast->addReferencedTypes(&packageReferencedTypes);
|
|
ast->getAllImportedNamesGranular(&packageImportedTypes);
|
|
|
|
if (fqName.name() == "types") {
|
|
ast->addDefinedTypes(&typesDefinedTypes);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
for (const auto &fqName : packageDefinedTypes) {
|
|
std::cout << "VERBOSE: DEFINED " << fqName.string() << std::endl;
|
|
}
|
|
|
|
for (const auto &fqName : packageImportedTypes) {
|
|
std::cout << "VERBOSE: IMPORTED " << fqName.string() << std::endl;
|
|
}
|
|
|
|
for (const auto &fqName : packageReferencedTypes) {
|
|
std::cout << "VERBOSE: REFERENCED " << fqName.string() << std::endl;
|
|
}
|
|
|
|
for (const auto &fqName : typesDefinedTypes) {
|
|
std::cout << "VERBOSE: DEFINED in types.hal " << fqName.string() << std::endl;
|
|
}
|
|
#endif
|
|
|
|
for (const auto& fqName : packageReferencedTypes) {
|
|
packageDefinedTypes.erase(fqName);
|
|
packageImportedTypes.erase(fqName);
|
|
}
|
|
|
|
// A package implicitly imports its own types.hal, only track them in one set.
|
|
for (const auto& fqName : typesDefinedTypes) {
|
|
packageImportedTypes.erase(fqName);
|
|
}
|
|
|
|
// defined but not referenced
|
|
unreferencedDefinitions->insert(packageDefinedTypes.begin(), packageDefinedTypes.end());
|
|
// imported but not referenced
|
|
unreferencedImports->insert(packageImportedTypes.begin(), packageImportedTypes.end());
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::enforceRestrictionsOnPackage(const FQName& fqName,
|
|
Enforce enforcement) const {
|
|
CHECK(enforcement == Enforce::FULL || enforcement == Enforce::NO_HASH ||
|
|
enforcement == Enforce::NONE);
|
|
|
|
// need fqName to be something like android.hardware.foo@1.0.
|
|
// name and valueName is ignored.
|
|
if (fqName.package().empty() || fqName.version().empty()) {
|
|
std::cerr << "ERROR: Cannot enforce restrictions on package " << fqName.string()
|
|
<< ": package or version is missing." << std::endl;
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
if (enforcement == Enforce::NONE) {
|
|
return OK;
|
|
}
|
|
|
|
FQName package = fqName.getPackageAndVersion();
|
|
// look up cache.
|
|
if (mPackagesEnforced.find(package) != mPackagesEnforced.end()) {
|
|
return OK;
|
|
}
|
|
|
|
// enforce all rules.
|
|
status_t err;
|
|
|
|
err = enforceMinorVersionUprevs(package, enforcement);
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
|
|
if (enforcement != Enforce::NO_HASH) {
|
|
err = enforceHashes(package);
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
// cache it so that it won't need to be enforced again.
|
|
mPackagesEnforced.insert(package);
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::packageExists(const FQName& package, bool* result) const {
|
|
std::string packagePath;
|
|
status_t err =
|
|
getPackagePath(package, false /* relative */, false /* sanitized */, &packagePath);
|
|
if (err != OK) return err;
|
|
|
|
if (existdir(makeAbsolute(packagePath).c_str())) {
|
|
*result = true;
|
|
return OK;
|
|
}
|
|
|
|
*result = false;
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::enforceMinorVersionUprevs(const FQName& currentPackage,
|
|
Enforce enforcement) const {
|
|
if(!currentPackage.hasVersion()) {
|
|
std::cerr << "ERROR: Cannot enforce minor version uprevs for " << currentPackage.string()
|
|
<< ": missing version." << std::endl;
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
if (currentPackage.getPackageMinorVersion() == 0) {
|
|
return OK; // ignore for @x.0
|
|
}
|
|
|
|
bool hasPrevPackage = false;
|
|
FQName prevPackage = currentPackage;
|
|
while (prevPackage.getPackageMinorVersion() > 0) {
|
|
prevPackage = prevPackage.downRev();
|
|
|
|
bool result;
|
|
status_t err = packageExists(prevPackage, &result);
|
|
if (err != OK) return err;
|
|
|
|
if (result) {
|
|
hasPrevPackage = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasPrevPackage) {
|
|
// no @x.z, where z < y, exist.
|
|
return OK;
|
|
}
|
|
|
|
if (prevPackage != currentPackage.downRev()) {
|
|
std::cerr << "ERROR: Cannot enforce minor version uprevs for " << currentPackage.string()
|
|
<< ": Found package " << prevPackage.string() << " but missing "
|
|
<< currentPackage.downRev().string() << "; you cannot skip a minor version."
|
|
<< std::endl;
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
bool prevIsTypesOnly;
|
|
status_t err = isTypesOnlyPackage(prevPackage, &prevIsTypesOnly);
|
|
if (err != OK) return err;
|
|
|
|
if (prevIsTypesOnly) {
|
|
// A types only package can be extended in any way.
|
|
return OK;
|
|
}
|
|
|
|
std::vector<FQName> packageInterfaces;
|
|
err = appendPackageInterfacesToVector(currentPackage, &packageInterfaces);
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
|
|
bool extendedInterface = false;
|
|
for (const FQName ¤tFQName : packageInterfaces) {
|
|
if (currentFQName.name() == "types") {
|
|
continue; // ignore types.hal
|
|
}
|
|
|
|
const Interface *iface = nullptr;
|
|
AST* currentAST = parse(currentFQName, nullptr /* parsedASTs */, enforcement);
|
|
if (currentAST != nullptr) {
|
|
iface = currentAST->getInterface();
|
|
}
|
|
if (iface == nullptr) {
|
|
if (currentAST == nullptr) {
|
|
std::cerr << "WARNING: Skipping " << currentFQName.string()
|
|
<< " because it could not be found or parsed"
|
|
<< " or " << currentPackage.string() << " doesn't pass all requirements."
|
|
<< std::endl;
|
|
} else {
|
|
std::cerr << "WARNING: Skipping " << currentFQName.string()
|
|
<< " because the file might contain more than one interface."
|
|
<< std::endl;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (iface->superType() == nullptr) {
|
|
CHECK(iface->isIBase());
|
|
continue;
|
|
}
|
|
|
|
// Assume that currentFQName == android.hardware.foo@2.2::IFoo.
|
|
FQName lastFQName(prevPackage.package(), prevPackage.version(),
|
|
currentFQName.name());
|
|
AST *lastAST = parse(lastFQName);
|
|
|
|
for (; lastFQName.getPackageMinorVersion() > 0 &&
|
|
(lastAST == nullptr || lastAST->getInterface() == nullptr)
|
|
; lastFQName = lastFQName.downRev(), lastAST = parse(lastFQName)) {
|
|
// nothing
|
|
}
|
|
|
|
// Then lastFQName == android.hardware.foo@2.1::IFoo or
|
|
// lastFQName == android.hardware.foo@2.0::IFoo if 2.1 doesn't exist.
|
|
|
|
bool lastFQNameExists = lastAST != nullptr && lastAST->getInterface() != nullptr;
|
|
|
|
if (!lastFQNameExists) {
|
|
continue;
|
|
}
|
|
|
|
if (iface->superType()->fqName() != lastFQName) {
|
|
std::cerr << "ERROR: Cannot enforce minor version uprevs for "
|
|
<< currentPackage.string() << ": " << iface->fqName().string() << " extends "
|
|
<< iface->superType()->fqName().string()
|
|
<< ", which is not allowed. It must extend " << lastFQName.string()
|
|
<< std::endl;
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
// at least one interface must extend the previous version
|
|
// @2.0::IFoo does not work. It must be @2.1::IFoo for at least one interface.
|
|
if (lastFQName.getPackageAndVersion() == prevPackage.getPackageAndVersion()) {
|
|
extendedInterface = true;
|
|
}
|
|
|
|
if (mVerbose) {
|
|
std::cout << "VERBOSE: EnforceMinorVersionUprevs: " << currentFQName.string()
|
|
<< " passes." << std::endl;
|
|
}
|
|
}
|
|
|
|
if (!extendedInterface) {
|
|
// No interface extends the interface with the same name in @x.(y-1).
|
|
std::cerr << "ERROR: " << currentPackage.string()
|
|
<< " doesn't pass minor version uprev requirement. "
|
|
<< "Requires at least one interface to extend an interface with the same name "
|
|
<< "from " << prevPackage.string() << "." << std::endl;
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
Coordinator::HashStatus Coordinator::checkHash(const FQName& fqName) const {
|
|
AST* ast = parse(fqName);
|
|
if (ast == nullptr) return HashStatus::ERROR;
|
|
|
|
std::string rootPath;
|
|
status_t err = getPackageRootPath(fqName, &rootPath);
|
|
if (err != OK) return HashStatus::ERROR;
|
|
|
|
std::string hashPath = makeAbsolute(rootPath) + "/current.txt";
|
|
std::string error;
|
|
bool fileExists;
|
|
std::vector<std::string> frozen =
|
|
Hash::lookupHash(hashPath, fqName.string(), &error, &fileExists);
|
|
if (fileExists) onFileAccess(hashPath, "r");
|
|
|
|
if (error.size() > 0) {
|
|
std::cerr << "ERROR: " << error << std::endl;
|
|
return HashStatus::ERROR;
|
|
}
|
|
|
|
// hash not defined, interface not frozen
|
|
if (frozen.size() == 0) {
|
|
if (isVerbose()) {
|
|
std::cerr << "VERBOSE: clearing runtime hash for " << fqName.string()
|
|
<< " because it is not frozen and so its hash cannot be depended upon as an "
|
|
"indication of stability."
|
|
<< std::endl;
|
|
}
|
|
// This ensures that it can be detected.
|
|
Hash::clearHash(ast->getFilename());
|
|
|
|
if (mRequireFrozen) {
|
|
std::cerr << "ERROR: Unfrozen " << fqName.string()
|
|
<< " cannot be referenced from context specifying -F (require_frozen)."
|
|
<< std::endl;
|
|
return HashStatus::ERROR;
|
|
}
|
|
|
|
return HashStatus::UNFROZEN;
|
|
}
|
|
|
|
std::string currentHash = ast->getFileHash()->hexString();
|
|
|
|
if (std::find(frozen.begin(), frozen.end(), currentHash) == frozen.end()) {
|
|
std::cerr << "ERROR: " << fqName.string() << " has hash " << currentHash
|
|
<< " which does not match hash on record. This interface has "
|
|
<< "been frozen. Do not change it!" << std::endl;
|
|
return HashStatus::CHANGED;
|
|
}
|
|
|
|
return HashStatus::FROZEN;
|
|
}
|
|
|
|
status_t Coordinator::getUnfrozenDependencies(const FQName& fqName,
|
|
std::set<FQName>* result) const {
|
|
CHECK(result != nullptr);
|
|
|
|
AST* ast = parse(fqName);
|
|
if (ast == nullptr) return UNKNOWN_ERROR;
|
|
|
|
std::set<FQName> imported;
|
|
ast->getImportedPackages(&imported);
|
|
|
|
// consider current package as dependency for types.hal and also to make
|
|
// sure that if anything is frozen in a package, everything is.
|
|
imported.insert(fqName.getPackageAndVersion());
|
|
|
|
// no circular dependency is already guaranteed by parsing
|
|
// indirect dependencies will be checked when the imported interface frozen checks are done
|
|
for (const FQName& importedPackage : imported) {
|
|
std::vector<FQName> packageInterfaces;
|
|
status_t err = appendPackageInterfacesToVector(importedPackage, &packageInterfaces);
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
|
|
for (const FQName& importedName : packageInterfaces) {
|
|
HashStatus status = checkHash(importedName);
|
|
switch (status) {
|
|
case HashStatus::CHANGED:
|
|
case HashStatus::ERROR:
|
|
return UNKNOWN_ERROR;
|
|
case HashStatus::FROZEN:
|
|
continue;
|
|
case HashStatus::UNFROZEN:
|
|
result->insert(importedName);
|
|
continue;
|
|
default:
|
|
LOG(FATAL) << static_cast<uint64_t>(status);
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t Coordinator::enforceHashes(const FQName& currentPackage) const {
|
|
std::vector<FQName> packageInterfaces;
|
|
status_t err = appendPackageInterfacesToVector(currentPackage, &packageInterfaces);
|
|
if (err != OK) {
|
|
return err;
|
|
}
|
|
|
|
for (const FQName& currentFQName : packageInterfaces) {
|
|
HashStatus status = checkHash(currentFQName);
|
|
switch (status) {
|
|
case HashStatus::CHANGED:
|
|
case HashStatus::ERROR:
|
|
return UNKNOWN_ERROR;
|
|
case HashStatus::FROZEN: {
|
|
std::set<FQName> unfrozenDependencies;
|
|
err = getUnfrozenDependencies(currentFQName, &unfrozenDependencies);
|
|
if (err != OK) return err;
|
|
|
|
if (!unfrozenDependencies.empty()) {
|
|
std::cerr << "ERROR: Frozen interface " << currentFQName.string()
|
|
<< " cannot depend or be adjacent to unfrozen thing(s):" << std::endl;
|
|
for (const FQName& name : unfrozenDependencies) {
|
|
std::cerr << " (unfrozen) " << name.string() << std::endl;
|
|
}
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
continue;
|
|
case HashStatus::UNFROZEN:
|
|
continue;
|
|
default:
|
|
LOG(FATAL) << static_cast<uint64_t>(status);
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
bool Coordinator::MakeParentHierarchy(const std::string &path) {
|
|
static const mode_t kMode = 0755;
|
|
|
|
size_t start = 1; // Ignore leading '/'
|
|
size_t slashPos;
|
|
while ((slashPos = path.find('/', start)) != std::string::npos) {
|
|
std::string partial = path.substr(0, slashPos);
|
|
|
|
struct stat st;
|
|
if (stat(partial.c_str(), &st) < 0) {
|
|
if (errno != ENOENT) {
|
|
return false;
|
|
}
|
|
|
|
int res = mkdir(partial.c_str(), kMode);
|
|
if (res < 0) {
|
|
return false;
|
|
}
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
return false;
|
|
}
|
|
|
|
start = slashPos + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Coordinator::emitOptionsUsageString(Formatter& out) {
|
|
out << "[-p <root path>] (-r <interface root>)+ [-R] [-F] [-v] [-d <depfile>]";
|
|
}
|
|
|
|
void Coordinator::emitOptionsDetailString(Formatter& out) {
|
|
out << "-p <root path>: Android build root, defaults to $ANDROID_BUILD_TOP or pwd.\n"
|
|
<< "-R: Do not add default package roots if not specified in -r.\n"
|
|
<< "-F: Require all referenced ASTs to be frozen.\n"
|
|
<< "-r <package:path root>: E.g., android.hardware:hardware/interfaces.\n"
|
|
<< "-v: verbose output.\n"
|
|
<< "-d <depfile>: location of depfile to write to.\n";
|
|
}
|
|
|
|
void Coordinator::parseOptions(int argc, char** argv, const std::string& options,
|
|
const HandleArg& handleArg) {
|
|
// reset global state for getopt
|
|
optind = 1;
|
|
|
|
bool suppressDefaultPackagePaths = false;
|
|
|
|
int res;
|
|
std::string optstr = options + "p:r:RFvd:";
|
|
while ((res = getopt(argc, argv, optstr.c_str())) >= 0) {
|
|
switch (res) {
|
|
case 'v': {
|
|
setVerbose(true);
|
|
break;
|
|
}
|
|
case 'd': {
|
|
setDepFile(optarg);
|
|
break;
|
|
}
|
|
case 'p': {
|
|
if (!getRootPath().empty()) {
|
|
fprintf(stderr, "ERROR: -p <root path> can only be specified once.\n");
|
|
exit(1);
|
|
}
|
|
setRootPath(optarg);
|
|
break;
|
|
}
|
|
case 'r': {
|
|
std::string val(optarg);
|
|
auto index = val.find_first_of(':');
|
|
if (index == std::string::npos) {
|
|
fprintf(stderr, "ERROR: -r option must contain ':': %s\n", val.c_str());
|
|
exit(1);
|
|
}
|
|
|
|
auto root = val.substr(0, index);
|
|
auto path = val.substr(index + 1);
|
|
|
|
std::string error;
|
|
status_t err = addPackagePath(root, path, &error);
|
|
if (err != OK) {
|
|
fprintf(stderr, "%s\n", error.c_str());
|
|
exit(1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 'R': {
|
|
suppressDefaultPackagePaths = true;
|
|
break;
|
|
}
|
|
case 'F': {
|
|
setRequireFrozen(true);
|
|
break;
|
|
}
|
|
// something downstream should handle these cases
|
|
default: { handleArg(res, optarg); }
|
|
}
|
|
}
|
|
|
|
if (getRootPath().empty()) {
|
|
const char* ANDROID_BUILD_TOP = getenv("ANDROID_BUILD_TOP");
|
|
if (ANDROID_BUILD_TOP != nullptr) {
|
|
setRootPath(ANDROID_BUILD_TOP);
|
|
}
|
|
}
|
|
|
|
if (!suppressDefaultPackagePaths) {
|
|
addDefaultPackagePath("android.hardware", "hardware/interfaces");
|
|
addDefaultPackagePath("android.hidl", "system/libhidl/transport");
|
|
addDefaultPackagePath("android.frameworks", "frameworks/hardware/interfaces");
|
|
addDefaultPackagePath("android.system", "system/hardware/interfaces");
|
|
}
|
|
}
|
|
|
|
} // namespace android
|
|
|