2018-06-23 11:13:38 -04:00
|
|
|
/*
|
|
|
|
* RetroShare JSON API
|
|
|
|
* Copyright (C) 2018 Gioacchino Mazzurco <gio@eigenlab.org>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program 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 Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2018-09-20 16:24:05 -04:00
|
|
|
#include <QtCore/QCoreApplication>
|
|
|
|
#include <QtCore/QDebug>
|
|
|
|
#include <QtXml/QtXml>
|
|
|
|
#include <QtCore/QDirIterator>
|
|
|
|
#include <QtCore/QFileInfo>
|
2018-06-23 11:13:38 -04:00
|
|
|
#include <iterator>
|
2018-09-19 15:28:26 -04:00
|
|
|
#include <functional>
|
|
|
|
#include <QVariant>
|
2018-06-23 11:13:38 -04:00
|
|
|
|
|
|
|
struct MethodParam
|
|
|
|
{
|
2018-08-16 17:34:29 -04:00
|
|
|
MethodParam() :
|
|
|
|
in(false), out(false), isMultiCallback(false), isSingleCallback(false){}
|
2018-06-23 11:13:38 -04:00
|
|
|
|
|
|
|
QString type;
|
|
|
|
QString name;
|
2018-08-25 16:24:49 -04:00
|
|
|
QString defval;
|
2018-06-23 11:13:38 -04:00
|
|
|
bool in;
|
|
|
|
bool out;
|
2018-08-16 17:34:29 -04:00
|
|
|
bool isMultiCallback;
|
|
|
|
bool isSingleCallback;
|
2018-06-23 11:13:38 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
if(argc != 3)
|
2018-09-19 15:28:26 -04:00
|
|
|
{
|
|
|
|
qDebug() << "Usage: jsonapi-generator SOURCE_PATH OUTPUT_PATH";
|
|
|
|
return EINVAL;
|
|
|
|
}
|
2018-06-23 11:13:38 -04:00
|
|
|
|
|
|
|
QString sourcePath(argv[1]);
|
|
|
|
QString outputPath(argv[2]);
|
|
|
|
QString doxPrefix(outputPath+"/xml/");
|
|
|
|
|
2018-08-16 17:34:29 -04:00
|
|
|
QString wrappersDefFilePath(outputPath + "/jsonapi-wrappers.inl");
|
2018-06-23 11:13:38 -04:00
|
|
|
QFile wrappersDefFile(wrappersDefFilePath);
|
|
|
|
wrappersDefFile.remove();
|
|
|
|
if(!wrappersDefFile.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text))
|
|
|
|
qFatal(QString("Can't open: " + wrappersDefFilePath).toLatin1().data());
|
|
|
|
|
2018-08-16 17:34:29 -04:00
|
|
|
QString cppApiIncludesFilePath(outputPath + "/jsonapi-includes.inl");
|
|
|
|
QFile cppApiIncludesFile(cppApiIncludesFilePath);
|
|
|
|
cppApiIncludesFile.remove();
|
|
|
|
if(!cppApiIncludesFile.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text))
|
|
|
|
qFatal(QString("Can't open: " + cppApiIncludesFilePath).toLatin1().data());
|
|
|
|
QSet<QString> cppApiIncludesSet;
|
2018-06-23 11:13:38 -04:00
|
|
|
|
2018-09-19 15:28:26 -04:00
|
|
|
auto fatalError = [&](
|
|
|
|
std::initializer_list<QVariant> errors, int ernum = EINVAL )
|
|
|
|
{
|
|
|
|
QString errorMsg;
|
|
|
|
for(const QVariant& error: errors)
|
|
|
|
errorMsg += error.toString() + " ";
|
|
|
|
errorMsg.chop(1);
|
|
|
|
QByteArray cppError(QString("#error "+errorMsg+"\n").toLocal8Bit());
|
|
|
|
wrappersDefFile.write(cppError);
|
|
|
|
cppApiIncludesFile.write(cppError);
|
|
|
|
qDebug() << errorMsg;
|
|
|
|
return ernum;
|
|
|
|
};
|
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
QDirIterator it(doxPrefix, QStringList() << "*8h.xml", QDir::Files);
|
|
|
|
while(it.hasNext())
|
|
|
|
{
|
|
|
|
QDomDocument hDoc;
|
|
|
|
QString hFilePath(it.next());
|
|
|
|
QString parseError; int line, column;
|
|
|
|
QFile hFile(hFilePath);
|
|
|
|
if (!hFile.open(QIODevice::ReadOnly) ||
|
|
|
|
!hDoc.setContent(&hFile, &parseError, &line, &column))
|
|
|
|
{
|
|
|
|
qWarning() << "Error parsing:" << hFilePath
|
|
|
|
<< parseError << line << column;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-09-19 15:28:26 -04:00
|
|
|
QFileInfo headerFileInfo(hDoc.elementsByTagName("location").at(0)
|
|
|
|
.attributes().namedItem("file").nodeValue());
|
|
|
|
QString headerRelPath = headerFileInfo.dir().dirName() + "/" +
|
|
|
|
headerFileInfo.fileName();
|
2018-06-23 11:13:38 -04:00
|
|
|
|
2018-08-30 13:02:50 -04:00
|
|
|
QDomNodeList sectiondefs = hDoc.elementsByTagName("memberdef");
|
2018-06-23 11:13:38 -04:00
|
|
|
for(int j = 0; j < sectiondefs.size(); ++j)
|
|
|
|
{
|
|
|
|
QDomElement sectDef = sectiondefs.item(j).toElement();
|
|
|
|
|
2018-08-30 13:02:50 -04:00
|
|
|
if( sectDef.attributes().namedItem("kind").nodeValue() != "variable"
|
2018-06-23 11:13:38 -04:00
|
|
|
|| sectDef.elementsByTagName("jsonapi").isEmpty() )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
QString instanceName =
|
|
|
|
sectDef.elementsByTagName("name").item(0).toElement().text();
|
|
|
|
QString typeName =
|
|
|
|
sectDef.elementsByTagName("ref").item(0).toElement().text();
|
|
|
|
QString typeFilePath(doxPrefix);
|
|
|
|
typeFilePath += sectDef.elementsByTagName("ref").item(0)
|
|
|
|
.attributes().namedItem("refid").nodeValue();
|
|
|
|
typeFilePath += ".xml";
|
|
|
|
|
|
|
|
QDomDocument typeDoc;
|
|
|
|
QFile typeFile(typeFilePath);
|
|
|
|
if ( !typeFile.open(QIODevice::ReadOnly) ||
|
|
|
|
!typeDoc.setContent(&typeFile, &parseError, &line, &column) )
|
|
|
|
{
|
|
|
|
qWarning() << "Error parsing:" << typeFilePath
|
|
|
|
<< parseError << line << column;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDomNodeList members = typeDoc.elementsByTagName("member");
|
|
|
|
for (int i = 0; i < members.size(); ++i)
|
|
|
|
{
|
|
|
|
QDomNode member = members.item(i);
|
|
|
|
QString refid(member.attributes().namedItem("refid").nodeValue());
|
|
|
|
QString methodName(member.firstChildElement("name").toElement().text());
|
2018-09-19 15:28:26 -04:00
|
|
|
bool requiresAuth = true;
|
2018-06-26 07:20:24 -04:00
|
|
|
QString defFilePath(doxPrefix + refid.split('_')[0] + ".xml");
|
|
|
|
|
|
|
|
qDebug() << "Looking for" << typeName << methodName << "into"
|
|
|
|
<< typeFilePath;
|
2018-06-23 11:13:38 -04:00
|
|
|
|
|
|
|
QDomDocument defDoc;
|
|
|
|
QFile defFile(defFilePath);
|
|
|
|
if ( !defFile.open(QIODevice::ReadOnly) ||
|
|
|
|
!defDoc.setContent(&defFile, &parseError, &line, &column) )
|
|
|
|
{
|
|
|
|
qWarning() << "Error parsing:" << defFilePath
|
|
|
|
<< parseError << line << column;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDomElement memberdef;
|
2018-06-26 07:20:24 -04:00
|
|
|
QDomNodeList memberdefs = defDoc.elementsByTagName("memberdef");
|
2018-06-23 11:13:38 -04:00
|
|
|
for (int k = 0; k < memberdefs.size(); ++k)
|
|
|
|
{
|
|
|
|
QDomElement tmpMBD = memberdefs.item(k).toElement();
|
|
|
|
QString tmpId = tmpMBD.attributes().namedItem("id").nodeValue();
|
|
|
|
QString tmpKind = tmpMBD.attributes().namedItem("kind").nodeValue();
|
2018-09-19 15:28:26 -04:00
|
|
|
QDomNodeList tmpJsonApiTagList(tmpMBD.elementsByTagName("jsonapi"));
|
|
|
|
|
|
|
|
if( tmpJsonApiTagList.count() && tmpId == refid &&
|
|
|
|
tmpKind == "function")
|
2018-06-23 11:13:38 -04:00
|
|
|
{
|
2018-09-19 15:28:26 -04:00
|
|
|
QDomElement tmpJsonApiTag = tmpJsonApiTagList.item(0).toElement();
|
|
|
|
QString tmpAccessValue;
|
|
|
|
if(tmpJsonApiTag.hasAttribute("access"))
|
|
|
|
tmpAccessValue = tmpJsonApiTag.attributeNode("access").nodeValue();
|
|
|
|
|
|
|
|
requiresAuth = "unauthenticated" != tmpAccessValue;
|
|
|
|
|
|
|
|
if("manualwrapper" != tmpAccessValue)
|
|
|
|
memberdef = tmpMBD;
|
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(memberdef.isNull()) continue;
|
|
|
|
|
|
|
|
QString apiPath("/" + instanceName + "/" + methodName);
|
|
|
|
QString retvalType = memberdef.firstChildElement("type").text();
|
|
|
|
QMap<QString,MethodParam> paramsMap;
|
|
|
|
QStringList orderedParamNames;
|
2018-08-16 17:34:29 -04:00
|
|
|
bool hasInput = false;
|
|
|
|
bool hasOutput = false;
|
|
|
|
bool hasSingleCallback = false;
|
|
|
|
bool hasMultiCallback = false;
|
|
|
|
QString callbackName;
|
|
|
|
QString callbackParams;
|
2018-06-23 11:13:38 -04:00
|
|
|
|
|
|
|
QDomNodeList params = memberdef.elementsByTagName("param");
|
|
|
|
for (int k = 0; k < params.size(); ++k)
|
|
|
|
{
|
|
|
|
QDomElement tmpPE = params.item(k).toElement();
|
|
|
|
MethodParam tmpParam;
|
2018-08-16 17:34:29 -04:00
|
|
|
QString& pName(tmpParam.name);
|
2018-06-23 11:13:38 -04:00
|
|
|
QString& pType(tmpParam.type);
|
2018-08-16 17:34:29 -04:00
|
|
|
pName = tmpPE.firstChildElement("declname").text();
|
2018-08-25 16:24:49 -04:00
|
|
|
QDomElement tmpDefval = tmpPE.firstChildElement("defval");
|
|
|
|
if(!tmpDefval.isNull()) tmpParam.defval = tmpDefval.text();
|
2018-08-16 17:34:29 -04:00
|
|
|
QDomElement tmpType = tmpPE.firstChildElement("type");
|
2018-06-23 11:13:38 -04:00
|
|
|
pType = tmpType.text();
|
|
|
|
if(pType.startsWith("const ")) pType.remove(0,6);
|
2018-08-16 17:34:29 -04:00
|
|
|
if(pType.startsWith("std::function"))
|
|
|
|
{
|
|
|
|
if(pType.endsWith('&')) pType.chop(1);
|
|
|
|
if(pName.startsWith("multiCallback"))
|
|
|
|
{
|
|
|
|
tmpParam.isMultiCallback = true;
|
|
|
|
hasMultiCallback = true;
|
|
|
|
}
|
|
|
|
else if(pName.startsWith("callback"))
|
|
|
|
{
|
|
|
|
tmpParam.isSingleCallback = true;
|
|
|
|
hasSingleCallback = true;
|
|
|
|
}
|
|
|
|
callbackName = pName;
|
|
|
|
callbackParams = pType;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pType.replace(QString("&"), QString());
|
|
|
|
pType.replace(QString(" "), QString());
|
|
|
|
}
|
2018-09-19 15:28:26 -04:00
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
paramsMap.insert(tmpParam.name, tmpParam);
|
|
|
|
orderedParamNames.push_back(tmpParam.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
QDomNodeList parameternames = memberdef.elementsByTagName("parametername");
|
|
|
|
for (int k = 0; k < parameternames.size(); ++k)
|
|
|
|
{
|
|
|
|
QDomElement tmpPN = parameternames.item(k).toElement();
|
|
|
|
MethodParam& tmpParam = paramsMap[tmpPN.text()];
|
|
|
|
QString tmpD = tmpPN.attributes().namedItem("direction").nodeValue();
|
2018-06-26 07:20:24 -04:00
|
|
|
if(tmpD.contains("in"))
|
|
|
|
{
|
|
|
|
tmpParam.in = true;
|
|
|
|
hasInput = true;
|
|
|
|
}
|
|
|
|
if(tmpD.contains("out"))
|
|
|
|
{
|
|
|
|
tmpParam.out = true;
|
|
|
|
hasOutput = true;
|
|
|
|
}
|
2018-06-23 11:13:38 -04:00
|
|
|
}
|
|
|
|
|
2018-09-19 15:28:26 -04:00
|
|
|
// Params sanity check
|
|
|
|
for( const MethodParam& pm : paramsMap)
|
|
|
|
if( !(pm.isMultiCallback || pm.isSingleCallback
|
|
|
|
|| pm.in || pm.out) )
|
|
|
|
return fatalError(
|
|
|
|
{ "Parameter:", pm.name, "of:", apiPath,
|
|
|
|
"declared in:", headerRelPath,
|
|
|
|
"miss doxygen parameter direction attribute!",
|
|
|
|
defFile.fileName() });
|
|
|
|
|
2018-06-26 16:01:17 -04:00
|
|
|
QString functionCall("\t\t");
|
|
|
|
if(retvalType != "void")
|
|
|
|
{
|
|
|
|
functionCall += retvalType + " retval = ";
|
|
|
|
hasOutput = true;
|
|
|
|
}
|
|
|
|
functionCall += instanceName + "->" + methodName + "(";
|
|
|
|
functionCall += orderedParamNames.join(", ") + ");\n";
|
2018-06-26 07:20:24 -04:00
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
qDebug() << instanceName << apiPath << retvalType << typeName
|
|
|
|
<< methodName;
|
|
|
|
for (const QString& pn : orderedParamNames)
|
|
|
|
{
|
|
|
|
const MethodParam& mp(paramsMap[pn]);
|
|
|
|
qDebug() << "\t" << mp.type << mp.name << mp.in << mp.out;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString inputParamsDeserialization;
|
2018-06-26 07:20:24 -04:00
|
|
|
if(hasInput)
|
|
|
|
{
|
|
|
|
inputParamsDeserialization +=
|
|
|
|
"\t\t{\n"
|
|
|
|
"\t\t\tRsGenericSerializer::SerializeContext& ctx(cReq);\n"
|
|
|
|
"\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);\n";
|
|
|
|
}
|
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
QString outputParamsSerialization;
|
2018-06-26 07:20:24 -04:00
|
|
|
if(hasOutput)
|
|
|
|
{
|
|
|
|
outputParamsSerialization +=
|
|
|
|
"\t\t{\n"
|
|
|
|
"\t\t\tRsGenericSerializer::SerializeContext& ctx(cAns);\n"
|
|
|
|
"\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
QString paramsDeclaration;
|
2018-06-23 11:13:38 -04:00
|
|
|
for (const QString& pn : orderedParamNames)
|
|
|
|
{
|
|
|
|
const MethodParam& mp(paramsMap[pn]);
|
2018-08-25 16:24:49 -04:00
|
|
|
paramsDeclaration += "\t\t" + mp.type + " " + mp.name;
|
|
|
|
if(!mp.defval.isEmpty())
|
|
|
|
paramsDeclaration += "(" + mp.defval + ")";
|
|
|
|
paramsDeclaration += ";\n";
|
2018-06-23 11:13:38 -04:00
|
|
|
if(mp.in)
|
|
|
|
inputParamsDeserialization += "\t\t\tRS_SERIAL_PROCESS("
|
|
|
|
+ mp.name + ");\n";
|
|
|
|
if(mp.out)
|
|
|
|
outputParamsSerialization += "\t\t\tRS_SERIAL_PROCESS("
|
|
|
|
+ mp.name + ");\n";
|
|
|
|
}
|
|
|
|
|
2018-06-26 07:20:24 -04:00
|
|
|
if(hasInput) inputParamsDeserialization += "\t\t}\n";
|
|
|
|
if(retvalType != "void")
|
|
|
|
outputParamsSerialization +=
|
|
|
|
"\t\t\tRS_SERIAL_PROCESS(retval);\n";
|
|
|
|
if(hasOutput) outputParamsSerialization += "\t\t}\n";
|
|
|
|
|
2018-08-16 17:34:29 -04:00
|
|
|
QString captureVars;
|
|
|
|
|
|
|
|
QString sessionEarlyClose;
|
|
|
|
if(hasSingleCallback)
|
|
|
|
sessionEarlyClose = "session->close();";
|
|
|
|
|
|
|
|
QString sessionDelayedClose;
|
|
|
|
if(hasMultiCallback)
|
|
|
|
{
|
|
|
|
sessionDelayedClose = "mService.schedule( [session](){session->close();}, std::chrono::seconds(maxWait+120) );";
|
|
|
|
captureVars = "this";
|
|
|
|
}
|
|
|
|
|
|
|
|
QString callbackParamsSerialization;
|
|
|
|
|
|
|
|
if(hasSingleCallback || hasMultiCallback ||
|
|
|
|
((callbackParams.indexOf('(')+2) < callbackParams.indexOf(')')))
|
|
|
|
{
|
|
|
|
QString& cbs(callbackParamsSerialization);
|
|
|
|
|
|
|
|
callbackParams = callbackParams.split('(')[1];
|
|
|
|
callbackParams = callbackParams.split(')')[0];
|
|
|
|
|
2018-08-26 05:39:26 -04:00
|
|
|
cbs += "\t\t\tRsGenericSerializer::SerializeContext ctx;\n";
|
2018-08-16 17:34:29 -04:00
|
|
|
|
|
|
|
for (QString cbPar : callbackParams.split(','))
|
|
|
|
{
|
|
|
|
bool isConst(cbPar.startsWith("const "));
|
|
|
|
QChar pSep(' ');
|
|
|
|
bool isRef(cbPar.contains('&'));
|
|
|
|
if(isRef) pSep = '&';
|
|
|
|
int sepIndex = cbPar.lastIndexOf(pSep)+1;
|
|
|
|
QString cpt(cbPar.mid(0, sepIndex));
|
|
|
|
cpt.remove(0,6);
|
|
|
|
QString cpn(cbPar.mid(sepIndex));
|
|
|
|
|
|
|
|
cbs += "\t\t\tRsTypeSerializer::serial_process(";
|
|
|
|
cbs += "RsGenericSerializer::TO_JSON, ctx, ";
|
|
|
|
if(isConst)
|
|
|
|
{
|
|
|
|
cbs += "const_cast<";
|
|
|
|
cbs += cpt;
|
|
|
|
cbs += ">(";
|
|
|
|
}
|
|
|
|
cbs += cpn;
|
|
|
|
if(isConst) cbs += ")";
|
|
|
|
cbs += ", \"";
|
|
|
|
cbs += cpn;
|
|
|
|
cbs += "\" );\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
QMap<QString,QString> substitutionsMap;
|
|
|
|
substitutionsMap.insert("paramsDeclaration", paramsDeclaration);
|
|
|
|
substitutionsMap.insert("inputParamsDeserialization", inputParamsDeserialization);
|
|
|
|
substitutionsMap.insert("outputParamsSerialization", outputParamsSerialization);
|
2018-09-01 09:29:09 -04:00
|
|
|
substitutionsMap.insert("instanceName", instanceName);
|
2018-06-26 16:01:17 -04:00
|
|
|
substitutionsMap.insert("functionCall", functionCall);
|
2018-08-16 17:34:29 -04:00
|
|
|
substitutionsMap.insert("apiPath", apiPath);
|
|
|
|
substitutionsMap.insert("sessionEarlyClose", sessionEarlyClose);
|
|
|
|
substitutionsMap.insert("sessionDelayedClose", sessionDelayedClose);
|
|
|
|
substitutionsMap.insert("captureVars", captureVars);
|
|
|
|
substitutionsMap.insert("callbackName", callbackName);
|
|
|
|
substitutionsMap.insert("callbackParams", callbackParams);
|
|
|
|
substitutionsMap.insert("callbackParamsSerialization", callbackParamsSerialization);
|
2018-09-19 15:28:26 -04:00
|
|
|
substitutionsMap.insert("requiresAuth", requiresAuth ? "true" : "false");
|
2018-08-16 17:34:29 -04:00
|
|
|
|
|
|
|
QString templFilePath(sourcePath);
|
|
|
|
if(hasMultiCallback || hasSingleCallback)
|
|
|
|
templFilePath.append("/async-method-wrapper-template.cpp.tmpl");
|
|
|
|
else templFilePath.append("/method-wrapper-template.cpp.tmpl");
|
|
|
|
|
|
|
|
QFile templFile(templFilePath);
|
2018-06-23 11:13:38 -04:00
|
|
|
templFile.open(QIODevice::ReadOnly);
|
|
|
|
QString wrapperDef(templFile.readAll());
|
|
|
|
|
|
|
|
QMap<QString,QString>::iterator it;
|
|
|
|
for ( it = substitutionsMap.begin();
|
|
|
|
it != substitutionsMap.end(); ++it )
|
|
|
|
wrapperDef.replace(QString("$%"+it.key()+"%$"), QString(it.value()), Qt::CaseSensitive);
|
|
|
|
|
|
|
|
wrappersDefFile.write(wrapperDef.toLocal8Bit());
|
|
|
|
|
2018-09-19 15:28:26 -04:00
|
|
|
cppApiIncludesSet.insert("#include \"" + headerRelPath + "\"\n");
|
2018-06-23 11:13:38 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-16 17:34:29 -04:00
|
|
|
for(const QString& incl : cppApiIncludesSet)
|
|
|
|
cppApiIncludesFile.write(incl.toLocal8Bit());
|
|
|
|
|
2018-06-23 11:13:38 -04:00
|
|
|
return 0;
|
|
|
|
}
|