forked from Kistler-Group/sdbus-cpp
* feat: add async property get/set convenience support classes * feat: add no-reply and async overloads to Properties_proxy * feat: add convenience functions for GetAll functionality * test: add tests for new functionality * add codegen IDL support and documentation
405 lines
16 KiB
C++
405 lines
16 KiB
C++
/**
|
|
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
|
* (C) 2016 - 2022 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
|
|
*
|
|
* @file ProxyGenerator.cpp
|
|
*
|
|
* Created on: Feb 1, 2017
|
|
* Project: sdbus-c++
|
|
* Description: High-level D-Bus IPC C++ library based on sd-bus
|
|
*
|
|
* This file is part of sdbus-c++.
|
|
*
|
|
* sdbus-c++ is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* sdbus-c++ is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// Own
|
|
#include "generator_utils.h"
|
|
#include "ProxyGenerator.h"
|
|
|
|
// STL
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <regex>
|
|
|
|
using std::endl;
|
|
|
|
using sdbuscpp::xml::Document;
|
|
using sdbuscpp::xml::Node;
|
|
using sdbuscpp::xml::Nodes;
|
|
|
|
/**
|
|
* Generate proxy code - client glue
|
|
*/
|
|
int ProxyGenerator::transformXmlToFileImpl(const Document &doc, const char *filename) const
|
|
{
|
|
Node &root = *(doc.root);
|
|
Nodes interfaces = root["interface"];
|
|
|
|
std::ostringstream code;
|
|
code << createHeader(filename, StubType::PROXY);
|
|
|
|
for (const auto& interface : interfaces)
|
|
{
|
|
code << processInterface(*interface);
|
|
}
|
|
|
|
code << "#endif" << endl;
|
|
|
|
return writeToFile(filename, code.str());
|
|
}
|
|
|
|
|
|
std::string ProxyGenerator::processInterface(Node& interface) const
|
|
{
|
|
std::string ifaceName = interface.get("name");
|
|
std::cout << "Generating proxy code for interface " << ifaceName << endl;
|
|
|
|
unsigned int namespacesCount = 0;
|
|
std::string namespacesStr;
|
|
std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
|
|
|
|
std::ostringstream body;
|
|
body << namespacesStr;
|
|
|
|
std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
|
|
+ "_proxy";
|
|
|
|
body << "class " << className << endl
|
|
<< "{" << endl
|
|
<< "public:" << endl
|
|
<< tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl
|
|
<< "protected:" << endl
|
|
<< tab << className << "(sdbus::IProxy& proxy)" << endl
|
|
<< tab << tab << ": proxy_(&proxy)" << endl;
|
|
|
|
Nodes methods = interface["method"];
|
|
Nodes signals = interface["signal"];
|
|
Nodes properties = interface["property"];
|
|
|
|
std::string registration, declaration;
|
|
std::tie(registration, declaration) = processSignals(signals);
|
|
|
|
body << tab << "{" << endl
|
|
<< registration
|
|
<< tab << "}" << endl << endl;
|
|
|
|
// Rule of Five
|
|
body << tab << className << "(const " << className << "&) = delete;" << endl;
|
|
body << tab << className << "& operator=(const " << className << "&) = delete;" << endl;
|
|
body << tab << className << "(" << className << "&&) = default;" << endl;
|
|
body << tab << className << "& operator=(" << className << "&&) = default;" << endl << endl;
|
|
|
|
body << tab << "~" << className << "() = default;" << endl << endl;
|
|
|
|
if (!declaration.empty())
|
|
body << declaration << endl;
|
|
|
|
std::string methodDefinitions, asyncDeclarationsMethods;
|
|
std::tie(methodDefinitions, asyncDeclarationsMethods) = processMethods(methods);
|
|
std::string propertyDefinitions, asyncDeclarationsProperties;
|
|
std::tie(propertyDefinitions, asyncDeclarationsProperties) = processProperties(properties);
|
|
|
|
if (!asyncDeclarationsMethods.empty())
|
|
{
|
|
body << asyncDeclarationsMethods << endl;
|
|
}
|
|
if (!asyncDeclarationsProperties.empty())
|
|
{
|
|
body << asyncDeclarationsProperties << endl;
|
|
}
|
|
|
|
if (!methodDefinitions.empty())
|
|
{
|
|
body << "public:" << endl << methodDefinitions;
|
|
}
|
|
|
|
if (!propertyDefinitions.empty())
|
|
{
|
|
body << "public:" << endl << propertyDefinitions;
|
|
}
|
|
|
|
body << "private:" << endl
|
|
<< tab << "sdbus::IProxy* proxy_;" << endl
|
|
<< "};" << endl << endl
|
|
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
|
|
|
|
return body.str();
|
|
}
|
|
|
|
std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes& methods) const
|
|
{
|
|
const std::regex patternTimeout{R"(^(\d+)(min|s|ms|us)?$)"};
|
|
|
|
std::ostringstream definitionSS, asyncDeclarationSS;
|
|
|
|
for (const auto& method : methods)
|
|
{
|
|
auto name = method->get("name");
|
|
auto nameSafe = mangle_name(name);
|
|
Nodes args = (*method)["arg"];
|
|
Nodes inArgs = args.select("direction" , "in");
|
|
Nodes outArgs = args.select("direction" , "out");
|
|
|
|
bool dontExpectReply{false};
|
|
bool async{false};
|
|
bool future{false}; // Async methods implemented by means of either std::future or callbacks
|
|
std::string timeoutValue;
|
|
std::smatch smTimeout;
|
|
|
|
Nodes annotations = (*method)["annotation"];
|
|
for (const auto& annotation : annotations)
|
|
{
|
|
const auto annotationName = annotation->get("name");
|
|
const auto annotationValue = annotation->get("value");
|
|
|
|
if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true")
|
|
dontExpectReply = true;
|
|
else
|
|
{
|
|
if (annotationName == "org.freedesktop.DBus.Method.Async"
|
|
&& (annotationValue == "client" || annotationValue == "clientserver" || annotationValue == "client-server"))
|
|
async = true;
|
|
else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && annotationValue == "callback")
|
|
future = false;
|
|
else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
|
|
future = true;
|
|
}
|
|
if (annotationName == "org.freedesktop.DBus.Method.Timeout")
|
|
timeoutValue = annotationValue;
|
|
}
|
|
if (dontExpectReply && outArgs.size() > 0)
|
|
{
|
|
std::cerr << "Function: " << name << ": ";
|
|
std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl;
|
|
dontExpectReply = false;
|
|
}
|
|
if (!timeoutValue.empty() && dontExpectReply)
|
|
{
|
|
std::cerr << "Function: " << name << ": ";
|
|
std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' not allowed for 'NoReply' methods! Option ignored..." << std::endl;
|
|
timeoutValue.clear();
|
|
}
|
|
|
|
if (!timeoutValue.empty() && !std::regex_match(timeoutValue, smTimeout, patternTimeout))
|
|
{
|
|
std::cerr << "Function: " << name << ": ";
|
|
std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' has unsupported timeout value! Option ignored..." << std::endl;
|
|
timeoutValue.clear();
|
|
}
|
|
|
|
auto retType = outArgsToType(outArgs);
|
|
auto retTypeBare = outArgsToType(outArgs, true);
|
|
std::string inArgStr, inArgTypeStr;
|
|
std::tie(inArgStr, inArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(inArgs);
|
|
std::string outArgStr, outArgTypeStr;
|
|
std::tie(outArgStr, outArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(outArgs);
|
|
|
|
const std::string realRetType = (async && !dontExpectReply ? (future ? "std::future<" + retType + ">" : "sdbus::PendingAsyncCall") : async ? "void" : retType);
|
|
definitionSS << tab << realRetType << " " << nameSafe << "(" << inArgTypeStr << ")" << endl
|
|
<< tab << "{" << endl;
|
|
|
|
if (!timeoutValue.empty())
|
|
{
|
|
definitionSS << tab << tab << "using namespace std::chrono_literals;" << endl;
|
|
}
|
|
|
|
if (outArgs.size() > 0 && !async)
|
|
{
|
|
definitionSS << tab << tab << retType << " result;" << endl;
|
|
}
|
|
|
|
definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "")
|
|
<< "proxy_->callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)";
|
|
|
|
if (!timeoutValue.empty())
|
|
{
|
|
const auto val = smTimeout.str(1);
|
|
const auto unit = smTimeout.str(2);
|
|
definitionSS << ".withTimeout(" << val << (unit.empty() ? "us" : unit) << ")";
|
|
}
|
|
|
|
if (inArgs.size() > 0)
|
|
{
|
|
definitionSS << ".withArguments(" << inArgStr << ")";
|
|
}
|
|
|
|
if (async && !dontExpectReply)
|
|
{
|
|
auto nameBigFirst = name;
|
|
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
|
|
|
if (future) // Async methods implemented through future
|
|
{
|
|
definitionSS << ".getResultAsFuture<" << retTypeBare << ">()";
|
|
}
|
|
else // Async methods implemented through callbacks
|
|
{
|
|
definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
|
|
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })";
|
|
|
|
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
|
|
<< outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl;
|
|
}
|
|
}
|
|
else if (outArgs.size() > 0)
|
|
{
|
|
definitionSS << ".storeResultsTo(result);" << endl
|
|
<< tab << tab << "return result";
|
|
}
|
|
else if (dontExpectReply)
|
|
{
|
|
definitionSS << ".dontExpectReply()";
|
|
}
|
|
|
|
definitionSS << ";" << endl << tab << "}" << endl << endl;
|
|
}
|
|
|
|
return std::make_tuple(definitionSS.str(), asyncDeclarationSS.str());
|
|
}
|
|
|
|
std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
|
|
{
|
|
std::ostringstream registrationSS, declarationSS;
|
|
|
|
for (const auto& signal : signals)
|
|
{
|
|
auto name = signal->get("name");
|
|
Nodes args = (*signal)["arg"];
|
|
|
|
auto nameBigFirst = name;
|
|
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
|
|
|
std::string argStr, argTypeStr;
|
|
std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args);
|
|
|
|
registrationSS << tab << tab << "proxy_"
|
|
"->uponSignal(\"" << name << "\")"
|
|
".onInterface(INTERFACE_NAME)"
|
|
".call([this](" << argTypeStr << ")"
|
|
"{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl;
|
|
|
|
declarationSS << tab << "virtual void on" << nameBigFirst << "(" << argTypeStr << ") = 0;" << endl;
|
|
}
|
|
|
|
return std::make_tuple(registrationSS.str(), declarationSS.str());
|
|
}
|
|
|
|
std::tuple<std::string, std::string> ProxyGenerator::processProperties(const Nodes& properties) const
|
|
{
|
|
std::ostringstream propertySS, asyncDeclarationSS;
|
|
for (const auto& property : properties)
|
|
{
|
|
auto propertyName = property->get("name");
|
|
auto propertyNameSafe = mangle_name(propertyName);
|
|
auto propertyAccess = property->get("access");
|
|
auto propertySignature = property->get("type");
|
|
|
|
auto propertyType = signature_to_type(propertySignature);
|
|
auto propertyArg = std::string("value");
|
|
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
|
|
|
|
bool asyncGet{false};
|
|
bool futureGet{false}; // Async property getter implemented by means of either std::future or callbacks
|
|
bool asyncSet{false};
|
|
bool futureSet{false}; // Async property setter implemented by means of either std::future or callbacks
|
|
|
|
Nodes annotations = (*property)["annotation"];
|
|
for (const auto& annotation : annotations)
|
|
{
|
|
const auto annotationName = annotation->get("name");
|
|
const auto annotationValue = annotation->get("value");
|
|
|
|
if (annotationName == "org.freedesktop.DBus.Property.Get.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
|
|
asyncGet = true;
|
|
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && annotationValue == "callback")
|
|
futureGet = false;
|
|
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
|
|
futureGet = true;
|
|
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
|
|
asyncSet = true;
|
|
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && annotationValue == "callback")
|
|
futureSet = false;
|
|
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
|
|
futureSet = true;
|
|
}
|
|
|
|
if (propertyAccess == "read" || propertyAccess == "readwrite")
|
|
{
|
|
const std::string realRetType = (asyncGet ? (futureGet ? "std::future<sdbus::Variant>" : "sdbus::PendingAsyncCall") : propertyType);
|
|
|
|
propertySS << tab << realRetType << " " << propertyNameSafe << "()" << endl
|
|
<< tab << "{" << endl;
|
|
propertySS << tab << tab << "return proxy_->getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")"
|
|
".onInterface(INTERFACE_NAME)";
|
|
if (asyncGet)
|
|
{
|
|
auto nameBigFirst = propertyName;
|
|
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
|
|
|
if (futureGet) // Async methods implemented through future
|
|
{
|
|
propertySS << ".getResultAsFuture()";
|
|
}
|
|
else // Async methods implemented through callbacks
|
|
{
|
|
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value)"
|
|
"{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), error); })";
|
|
|
|
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply("
|
|
<< "const " << propertyType << "& value, const sdbus::Error* error) = 0;" << endl;
|
|
}
|
|
}
|
|
propertySS << ";" << endl << tab << "}" << endl << endl;
|
|
}
|
|
|
|
if (propertyAccess == "readwrite" || propertyAccess == "write")
|
|
{
|
|
const std::string realRetType = (asyncSet ? (futureSet ? "std::future<void>" : "sdbus::PendingAsyncCall") : "void");
|
|
|
|
propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
|
|
<< tab << "{" << endl;
|
|
propertySS << tab << tab << (asyncSet ? "return " : "") << "proxy_->setProperty" << (asyncSet ? "Async" : "")
|
|
<< "(\"" << propertyName << "\")"
|
|
".onInterface(INTERFACE_NAME)"
|
|
".toValue(" << propertyArg << ")";
|
|
|
|
if (asyncSet)
|
|
{
|
|
auto nameBigFirst = propertyName;
|
|
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
|
|
|
if (futureSet) // Async methods implemented through future
|
|
{
|
|
propertySS << ".getResultAsFuture()";
|
|
}
|
|
else // Async methods implemented through callbacks
|
|
{
|
|
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error)"
|
|
"{ this->on" << nameBigFirst << "PropertySetReply(error); })";
|
|
|
|
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply("
|
|
<< "const sdbus::Error* error) = 0;" << endl;
|
|
}
|
|
}
|
|
|
|
propertySS << ";" << endl << tab << "}" << endl << endl;
|
|
}
|
|
}
|
|
|
|
return std::make_tuple(propertySS.str(), asyncDeclarationSS.str());
|
|
}
|