blob: ca606bd49a52f1f98092804420f1eba279a8a242 [file] [log] [blame]
/*
* 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 <android-base/logging.h>
#include <android-base/strings.h>
#include <hidl-util/FQName.h>
#include <hidl-util/Formatter.h>
#include <algorithm>
#include <iostream>
#include <vector>
#include "AST.h"
#include "AidlHelper.h"
#include "CompoundType.h"
#include "Coordinator.h"
#include "DocComment.h"
#include "Interface.h"
using namespace android;
static void usage(const char* me) {
Formatter out(stderr);
out << "Usage: " << me << " [-fh] [-o <output path>] [-l <header file>] ";
Coordinator::emitOptionsUsageString(out);
out << " FQNAME\n\n";
out << "Converts FQNAME, PACKAGE(.SUBPACKAGE)*@[0-9]+.[0-9]+(::TYPE)? to an aidl "
"equivalent.\n\n";
out.indent();
out.indent();
out << "-f: Force hidl2aidl to convert older packages\n";
out << "-h: Prints this menu.\n";
out << "-o <output path>: Location to output files.\n";
out << "-l <header file>: File containing a header to prepend to generated files.\n";
Coordinator::emitOptionsDetailString(out);
out.unindent();
out.unindent();
}
static const FQName& getNewerFQName(const FQName& lhs, const FQName& rhs) {
CHECK(lhs.package() == rhs.package());
CHECK(lhs.name() == rhs.name());
if (lhs.getPackageMajorVersion() > rhs.getPackageMajorVersion()) return lhs;
if (lhs.getPackageMajorVersion() < rhs.getPackageMajorVersion()) return rhs;
if (lhs.getPackageMinorVersion() > rhs.getPackageMinorVersion()) return lhs;
return rhs;
}
// If similar FQName is not found, the same one is returned
static FQName getLatestMinorVersionFQNameFromList(const FQName& fqName,
const std::vector<FQName>& list) {
FQName currentCandidate = fqName;
bool found = false;
for (const FQName& current : list) {
if (current.package() == currentCandidate.package() &&
current.name() == currentCandidate.name() &&
current.getPackageMajorVersion() == currentCandidate.getPackageMajorVersion()) {
// Prioritize elements in the list over the provided fqName
currentCandidate = found ? getNewerFQName(current, currentCandidate) : current;
found = true;
}
}
return currentCandidate;
}
static FQName getLatestMinorVersionNamedTypeFromList(const FQName& fqName,
const std::set<const NamedType*>& list) {
FQName currentCandidate = fqName;
bool found = false;
for (const NamedType* currentNamedType : list) {
const FQName& current = currentNamedType->fqName();
if (current.package() == currentCandidate.package() &&
current.name() == currentCandidate.name() &&
current.getPackageMajorVersion() == currentCandidate.getPackageMajorVersion()) {
// Prioritize elements in the list over the provided fqName
currentCandidate = found ? getNewerFQName(current, currentCandidate) : current;
found = true;
}
}
return currentCandidate;
}
static bool packageExists(const Coordinator& coordinator, const FQName& fqName) {
bool result;
status_t err = coordinator.packageExists(fqName, &result);
if (err != OK) {
std::cerr << "Error trying to find package " << fqName.string() << std::endl;
exit(1);
}
return result;
}
// assuming fqName exists, find oldest version which does exist
// e.g. android.hardware.foo@1.7 -> android.hardware.foo@1.1 (if foo@1.0 doesn't
// exist)
static FQName getLowestExistingFqName(const Coordinator& coordinator, const FQName& fqName) {
FQName lowest(fqName);
while (lowest.getPackageMinorVersion() != 0) {
if (!packageExists(coordinator, lowest.downRev())) break;
lowest = lowest.downRev();
}
return lowest;
}
// assuming fqName exists, find newest version which does exist
// e.g. android.hardware.foo@1.1 -> android.hardware.foo@1.7 if that's the
// newest
static FQName getHighestExistingFqName(const Coordinator& coordinator, const FQName& fqName) {
FQName highest(fqName);
while (packageExists(coordinator, highest.upRev())) {
highest = highest.upRev();
}
return highest;
}
static AST* parse(const Coordinator& coordinator, const FQName& target) {
AST* ast = coordinator.parse(target);
if (ast == nullptr) {
std::cerr << "ERROR: Could not parse " << target.name() << ". Aborting." << std::endl;
exit(1);
}
if (!ast->getUnhandledComments().empty()) {
AidlHelper::notes()
<< "Unhandled comments from " << target.string()
<< " follow. Consider using hidl-lint to locate these and fixup as many "
<< "as possible.\n";
for (const DocComment* docComment : ast->getUnhandledComments()) {
docComment->emit(AidlHelper::notes());
}
AidlHelper::notes() << "\n";
}
return ast;
}
static void getSubTypes(const NamedType& namedType, std::set<const NamedType*>* types) {
if (namedType.isScope()) {
const Scope& compoundType = static_cast<const Scope&>(namedType);
for (const NamedType* subType : compoundType.getSubTypes()) {
types->insert(subType);
getSubTypes(*subType, types);
}
}
}
static void emitAidlSharedLibs(Formatter& out, FQName fqName, AidlBackend backend) {
if (backend == AidlBackend::NDK) {
out << " \"libbinder_ndk\",\n";
out << " \"libhidlbase\",\n";
out << " \"" << AidlHelper::getAidlPackage(fqName) << "-ndk_platform\",\n";
} else if (backend == AidlBackend::CPP) {
out << " \"libbinder\",\n";
out << " \"libhidlbase\",\n";
out << " \"" << AidlHelper::getAidlPackage(fqName) << "-cpp\",\n";
out << " \"libutils\",\n";
}
}
static void emitHidlSharedLibs(Formatter& out, std::vector<FQName>& targets) {
std::set<std::string> uniquePackages;
for (const auto& target : targets) {
uniquePackages.insert(target.getPackageAndVersion().string());
}
for (const auto& package : uniquePackages) {
out << " \"" << package << "\",\n";
}
}
static std::string aidlTranslateLibraryName(FQName fqName, AidlBackend backend) {
std::string postfix;
if (backend == AidlBackend::NDK) {
postfix = "-ndk";
} else if (backend == AidlBackend::CPP) {
postfix = "-cpp";
} else {
postfix = "";
}
return AidlHelper::getAidlPackage(fqName) + "-translate" + postfix;
}
static void emitBuildFile(Formatter& out, const FQName& fqName, std::vector<FQName>& targets,
bool needsTranslation) {
out << "// This is the expected build file, but it may not be right in all cases\n";
out << "\n";
out << "aidl_interface {\n";
out << " name: \"" << AidlHelper::getAidlPackage(fqName) << "\",\n";
out << " vendor_available: true,\n";
out << " srcs: [\"" << AidlHelper::getAidlPackagePath(fqName) << "/*.aidl\"],\n";
out << " stability: \"vintf\",\n";
out << " backend: {\n";
out << " cpp: {\n";
out << " // FIXME should this be disabled?\n";
out << " // prefer NDK backend which can be used anywhere\n";
out << " enabled: true,\n";
out << " },\n";
out << " java: {\n";
out << " sdk_version: \"module_current\",\n";
out << " },\n";
out << " ndk: {\n";
out << " vndk: {\n";
out << " enabled: true,\n";
out << " },\n";
out << " },\n";
out << " },\n";
out << "}\n\n";
if (!needsTranslation) return;
for (auto backend : {AidlBackend::CPP, AidlBackend::NDK}) {
out << "cc_library {\n";
out << " name: \"" << aidlTranslateLibraryName(fqName, backend) << +"\",\n";
if (backend == AidlBackend::NDK) {
out << " vendor_available: true,\n";
}
out << " srcs: [\"" << AidlHelper::translateSourceFile(fqName, backend) + "\"],\n";
out << " shared_libs: [\n";
emitAidlSharedLibs(out, fqName, backend);
emitHidlSharedLibs(out, targets);
out << " ],\n";
out << " export_include_dirs: [\"include\"],\n";
out << "}\n\n";
}
}
// hidl is intentionally leaky. Turn off LeakSanitizer by default.
extern "C" const char* __asan_default_options() {
return "detect_leaks=0";
}
int main(int argc, char** argv) {
const char* me = argv[0];
if (argc == 1) {
usage(me);
std::cerr << "ERROR: no fqname specified." << std::endl;
exit(1);
}
Coordinator coordinator;
std::string outputPath;
std::string fileHeader;
bool forceConvertOldInterfaces = false;
coordinator.parseOptions(argc, argv, "fho:l:", [&](int res, char* arg) {
switch (res) {
case 'o': {
if (!outputPath.empty()) {
fprintf(stderr, "ERROR: -o <output path> can only be specified once.\n");
exit(1);
}
outputPath = arg;
break;
}
case 'l':
if (!fileHeader.empty()) {
fprintf(stderr, "ERROR: -l <header file> can only be specified once.\n");
exit(1);
}
fileHeader = arg;
break;
case 'f':
forceConvertOldInterfaces = true;
break;
case 'h':
case '?':
default: {
usage(me);
exit(1);
break;
}
}
});
if (!outputPath.empty() && outputPath.back() != '/') {
outputPath += "/";
}
coordinator.setOutputPath(outputPath);
AidlHelper::setFileHeader(fileHeader);
argc -= optind;
argv += optind;
if (argc == 0) {
usage(me);
std::cerr << "ERROR: no fqname specified." << std::endl;
exit(1);
}
if (argc > 1) {
usage(me);
std::cerr << "ERROR: only one fqname can be specified." << std::endl;
exit(1);
}
const char* arg = argv[0];
FQName fqName;
if (!FQName::parse(arg, &fqName)) {
std::cerr << "ERROR: Invalid fully-qualified name as argument: " << arg << "." << std::endl;
exit(1);
}
if (fqName.isFullyQualified()) {
std::cerr << "ERROR: hidl2aidl only supports converting an entire package, try "
"converting "
<< fqName.getPackageAndVersion().string() << " instead." << std::endl;
exit(1);
}
if (!packageExists(coordinator, fqName)) {
std::cerr << "ERROR: Could not get sources for: " << arg << "." << std::endl;
exit(1);
}
if (!forceConvertOldInterfaces) {
const FQName highestFqName = getHighestExistingFqName(coordinator, fqName);
if (fqName != highestFqName) {
std::cerr << "ERROR: A newer minor version of " << fqName.string() << " exists ("
<< highestFqName.string()
<< "). In general, prefer to convert that instead. If you really mean to "
"use an old minor version use '-f'."
<< std::endl;
exit(1);
}
}
// This is the list of all types which should be converted
std::vector<FQName> targets;
for (FQName version = getLowestExistingFqName(coordinator, fqName);
version.getPackageMinorVersion() <= fqName.getPackageMinorVersion();
version = version.upRev()) {
std::vector<FQName> newTargets;
status_t err = coordinator.appendPackageInterfacesToVector(version, &newTargets);
if (err != OK) exit(1);
targets.insert(targets.end(), newTargets.begin(), newTargets.end());
}
// targets should not contain duplicates since appendPackageInterfaces is only called once
// per version. now remove all the elements that are not the "newest"
const auto& newEnd =
std::remove_if(targets.begin(), targets.end(), [&](const FQName& fqName) -> bool {
if (fqName.name() == "types") return false;
return getLatestMinorVersionFQNameFromList(fqName, targets) != fqName;
});
targets.erase(newEnd, targets.end());
// Set up AIDL conversion log
Formatter err =
coordinator.getFormatter(fqName, Coordinator::Location::DIRECT, "conversion.log");
err << "Notes relating to hidl2aidl conversion of " << fqName.string() << " to "
<< AidlHelper::getAidlPackage(fqName) << " (if any) follow:\n";
AidlHelper::setNotes(&err);
// Gather all the types and interfaces
std::set<const NamedType*> namedTypesInPackage;
for (const FQName& target : targets) {
AST* ast = parse(coordinator, target);
CHECK(ast);
const Interface* iface = ast->getInterface();
if (iface) {
namedTypesInPackage.insert(iface);
// Get all of the types defined in the interface chain(includes self)
for (const Interface* interface : iface->typeChain()) {
getSubTypes(*interface, &namedTypesInPackage);
}
} else {
getSubTypes(ast->getRootScope(), &namedTypesInPackage);
}
}
// Remove all of the older versions of types and keep the latest
for (auto it = namedTypesInPackage.begin(); it != namedTypesInPackage.end();) {
if (getLatestMinorVersionNamedTypeFromList((*it)->fqName(), namedTypesInPackage) !=
(*it)->fqName()) {
it = namedTypesInPackage.erase(it);
} else {
it++;
}
}
// Process and flatten all of the types. Many types include fields of older
// versions of that type.
// This step recursively finds all of those fields and adds their fields to
// the most recent top level type.
std::map<const NamedType*, const ProcessedCompoundType> processedTypesInPackage;
for (const auto& namedType : namedTypesInPackage) {
if (namedType->isCompoundType()) {
ProcessedCompoundType processed;
AidlHelper::processCompoundType(static_cast<const CompoundType&>(*namedType),
&processed, std::string());
processedTypesInPackage.insert(
std::pair<const NamedType*, const ProcessedCompoundType>(namedType, processed));
}
}
Formatter buildFile =
coordinator.getFormatter(fqName, Coordinator::Location::DIRECT, "Android.bp");
emitBuildFile(buildFile, fqName, targets, !processedTypesInPackage.empty());
AidlHelper::emitTranslation(coordinator, fqName, namedTypesInPackage, processedTypesInPackage);
// Emit all types and interfaces
// The interfaces and types are still be further manipulated inside
// emitAidl. The interfaces are consolidating methods from their typechains
// and the composite types are being flattened.
for (const auto& namedType : namedTypesInPackage) {
AidlHelper::emitAidl(*namedType, coordinator, processedTypesInPackage);
}
err << "END OF LOG\n";
return 0;
}