| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2019 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include "qmltypescreator.h" |
| 30 | |
| 31 | #include <QCoreApplication> |
| 32 | #include <QCommandLineParser> |
| 33 | #include <QtDebug> |
| 34 | #include <QJsonDocument> |
| 35 | #include <QJsonArray> |
| 36 | #include <QJsonValue> |
| 37 | #include <QJsonObject> |
| 38 | #include <QFile> |
| 39 | #include <QScopedPointer> |
| 40 | #include <QSaveFile> |
| 41 | #include <QQueue> |
| 42 | |
| 43 | #include <cstdlib> |
| 44 | |
| 45 | struct ScopedPointerFileCloser |
| 46 | { |
| 47 | static inline void cleanup(FILE *handle) { if (handle) fclose(stream: handle); } |
| 48 | }; |
| 49 | |
| 50 | enum RegistrationMode { |
| 51 | NoRegistration, |
| 52 | ObjectRegistration, |
| 53 | GadgetRegistration, |
| 54 | NamespaceRegistration |
| 55 | }; |
| 56 | |
| 57 | static RegistrationMode qmlTypeRegistrationMode(const QJsonObject &classDef) |
| 58 | { |
| 59 | const QJsonArray classInfos = classDef[QLatin1String("classInfos" )].toArray(); |
| 60 | for (const QJsonValue &info: classInfos) { |
| 61 | const QString name = info[QLatin1String("name" )].toString(); |
| 62 | if (name == QLatin1String("QML.Element" )) { |
| 63 | if (classDef[QLatin1String("object" )].toBool()) |
| 64 | return ObjectRegistration; |
| 65 | if (classDef[QLatin1String("gadget" )].toBool()) |
| 66 | return GadgetRegistration; |
| 67 | if (classDef[QLatin1String("namespace" )].toBool()) |
| 68 | return NamespaceRegistration; |
| 69 | qWarning() << "Not registering classInfo which is neither an object, " |
| 70 | "nor a gadget, nor a namespace:" |
| 71 | << name; |
| 72 | break; |
| 73 | } |
| 74 | } |
| 75 | return NoRegistration; |
| 76 | } |
| 77 | |
| 78 | static QVector<QJsonObject> foreignRelatedTypes(const QVector<QJsonObject> &types, |
| 79 | const QVector<QJsonObject> &foreignTypes) |
| 80 | { |
| 81 | const QLatin1String classInfosKey("classInfos" ); |
| 82 | const QLatin1String nameKey("name" ); |
| 83 | const QLatin1String qualifiedClassNameKey("qualifiedClassName" ); |
| 84 | const QLatin1String qmlNamePrefix("QML." ); |
| 85 | const QLatin1String qmlForeignName("QML.Foreign" ); |
| 86 | const QLatin1String qmlAttachedName("QML.Attached" ); |
| 87 | const QLatin1String valueKey("value" ); |
| 88 | const QLatin1String superClassesKey("superClasses" ); |
| 89 | const QLatin1String accessKey("access" ); |
| 90 | const QLatin1String publicAccess("public" ); |
| 91 | |
| 92 | QSet<QString> processedRelatedNames; |
| 93 | QQueue<QJsonObject> typeQueue; |
| 94 | typeQueue.append(t: types.toList()); |
| 95 | QVector<QJsonObject> relatedTypes; |
| 96 | |
| 97 | // First mark all classes registered from this module as already processed. |
| 98 | for (const QJsonObject &type : types) { |
| 99 | processedRelatedNames.insert(value: type.value(key: qualifiedClassNameKey).toString()); |
| 100 | const auto classInfos = type.value(key: classInfosKey).toArray(); |
| 101 | for (const QJsonValue &classInfo : classInfos) { |
| 102 | const QJsonObject obj = classInfo.toObject(); |
| 103 | if (obj.value(key: nameKey).toString() == qmlForeignName) { |
| 104 | processedRelatedNames.insert(value: obj.value(key: valueKey).toString()); |
| 105 | break; |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | // Then mark all classes registered from other modules as already processed. |
| 111 | // We don't want to generate them again for this module. |
| 112 | for (const QJsonObject &foreignType : foreignTypes) { |
| 113 | const auto classInfos = foreignType.value(key: classInfosKey).toArray(); |
| 114 | bool seenQmlPrefix = false; |
| 115 | for (const QJsonValue &classInfo : classInfos) { |
| 116 | const QJsonObject obj = classInfo.toObject(); |
| 117 | const QString name = obj.value(key: nameKey).toString(); |
| 118 | if (!seenQmlPrefix && name.startsWith(s: qmlNamePrefix)) { |
| 119 | processedRelatedNames.insert(value: foreignType.value(key: qualifiedClassNameKey).toString()); |
| 120 | seenQmlPrefix = true; |
| 121 | } |
| 122 | if (name == qmlForeignName) { |
| 123 | processedRelatedNames.insert(value: obj.value(key: valueKey).toString()); |
| 124 | break; |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | auto addType = [&](const QString &typeName) { |
| 130 | if (processedRelatedNames.contains(value: typeName)) |
| 131 | return; |
| 132 | processedRelatedNames.insert(value: typeName); |
| 133 | if (const QJsonObject *other = QmlTypesClassDescription::findType(types: foreignTypes, name: typeName)) { |
| 134 | relatedTypes.append(t: *other); |
| 135 | typeQueue.enqueue(t: *other); |
| 136 | } |
| 137 | }; |
| 138 | |
| 139 | // Then recursively iterate the super types and attached types, marking the |
| 140 | // ones we are interested in as related. |
| 141 | while (!typeQueue.isEmpty()) { |
| 142 | const QJsonObject classDef = typeQueue.dequeue(); |
| 143 | |
| 144 | const auto classInfos = classDef.value(key: classInfosKey).toArray(); |
| 145 | for (const QJsonValue &classInfo : classInfos) { |
| 146 | const QJsonObject obj = classInfo.toObject(); |
| 147 | if (obj.value(key: nameKey).toString() == qmlAttachedName) { |
| 148 | addType(obj.value(key: valueKey).toString()); |
| 149 | } else if (obj.value(key: nameKey).toString() == qmlForeignName) { |
| 150 | const QString foreignClassName = obj.value(key: valueKey).toString(); |
| 151 | if (const QJsonObject *other = QmlTypesClassDescription::findType( |
| 152 | types: foreignTypes, name: foreignClassName)) { |
| 153 | const auto otherSupers = other->value(key: superClassesKey).toArray(); |
| 154 | if (!otherSupers.isEmpty()) { |
| 155 | const QJsonObject otherSuperObject = otherSupers.first().toObject(); |
| 156 | if (otherSuperObject.value(key: accessKey).toString() == publicAccess) |
| 157 | addType(otherSuperObject.value(key: nameKey).toString()); |
| 158 | } |
| 159 | |
| 160 | const auto otherClassInfos = other->value(key: classInfosKey).toArray(); |
| 161 | for (const QJsonValue &otherClassInfo : otherClassInfos) { |
| 162 | const QJsonObject obj = otherClassInfo.toObject(); |
| 163 | if (obj.value(key: nameKey).toString() == qmlAttachedName) { |
| 164 | addType(obj.value(key: valueKey).toString()); |
| 165 | break; |
| 166 | } |
| 167 | // No, you cannot chain QML_FOREIGN declarations. Sorry. |
| 168 | } |
| 169 | break; |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | const auto supers = classDef.value(key: superClassesKey).toArray(); |
| 175 | if (!supers.isEmpty()) { |
| 176 | const QJsonObject superObject = supers.first().toObject(); |
| 177 | if (superObject.value(key: accessKey).toString() == publicAccess) |
| 178 | addType(superObject.value(key: nameKey).toString()); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | return relatedTypes; |
| 183 | } |
| 184 | |
| 185 | int main(int argc, char **argv) |
| 186 | { |
| 187 | // Produce reliably the same output for the same input by disabling QHash's random seeding. |
| 188 | qSetGlobalQHashSeed(newSeed: 0); |
| 189 | |
| 190 | QCoreApplication app(argc, argv); |
| 191 | QCoreApplication::setApplicationName(QStringLiteral("qmltyperegistrar" )); |
| 192 | QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); |
| 193 | |
| 194 | QCommandLineParser parser; |
| 195 | parser.addHelpOption(); |
| 196 | parser.addVersionOption(); |
| 197 | |
| 198 | QCommandLineOption outputOption(QStringLiteral("o" )); |
| 199 | outputOption.setDescription(QStringLiteral("Write output to specified file." )); |
| 200 | outputOption.setValueName(QStringLiteral("file" )); |
| 201 | outputOption.setFlags(QCommandLineOption::ShortOptionStyle); |
| 202 | parser.addOption(commandLineOption: outputOption); |
| 203 | |
| 204 | QCommandLineOption privateIncludesOption( |
| 205 | QStringLiteral("private-includes" ), |
| 206 | QStringLiteral("Include headers ending in \"_p.h\" using \"#include <private/foo_p.h>\"" |
| 207 | "rather than \"#include <foo_p.h>\"." )); |
| 208 | parser.addOption(commandLineOption: privateIncludesOption); |
| 209 | |
| 210 | QCommandLineOption importNameOption(QStringLiteral("import-name" )); |
| 211 | importNameOption.setDescription(QStringLiteral("Name of the module to use for type and module " |
| 212 | "registrations." )); |
| 213 | importNameOption.setValueName(QStringLiteral("module name" )); |
| 214 | parser.addOption(commandLineOption: importNameOption); |
| 215 | |
| 216 | QCommandLineOption majorVersionOption(QStringLiteral("major-version" )); |
| 217 | majorVersionOption.setDescription(QStringLiteral("Major version to use for type and module " |
| 218 | "registrations." )); |
| 219 | majorVersionOption.setValueName(QStringLiteral("major version" )); |
| 220 | parser.addOption(commandLineOption: majorVersionOption); |
| 221 | |
| 222 | QCommandLineOption minorVersionOption(QStringLiteral("minor-version" )); |
| 223 | minorVersionOption.setDescription(QStringLiteral("Minor version to use for module " |
| 224 | "registration." )); |
| 225 | minorVersionOption.setValueName(QStringLiteral("minor version" )); |
| 226 | parser.addOption(commandLineOption: minorVersionOption); |
| 227 | |
| 228 | QCommandLineOption pluginTypesOption(QStringLiteral("generate-qmltypes" )); |
| 229 | pluginTypesOption.setDescription(QStringLiteral("Generate qmltypes into specified file." )); |
| 230 | pluginTypesOption.setValueName(QStringLiteral("qmltypes file" )); |
| 231 | parser.addOption(commandLineOption: pluginTypesOption); |
| 232 | |
| 233 | QCommandLineOption foreignTypesOption(QStringLiteral("foreign-types" )); |
| 234 | foreignTypesOption.setDescription(QStringLiteral( |
| 235 | "Comma separated list of other modules' metatypes files " |
| 236 | "to consult for foreign types when generating " |
| 237 | "qmltypes file." )); |
| 238 | foreignTypesOption.setValueName(QStringLiteral("foreign types" )); |
| 239 | parser.addOption(commandLineOption: foreignTypesOption); |
| 240 | |
| 241 | QCommandLineOption dependenciesOption(QStringLiteral("dependencies" )); |
| 242 | dependenciesOption.setDescription(QStringLiteral("JSON file with dependencies to be stated in " |
| 243 | "qmltypes file." )); |
| 244 | dependenciesOption.setValueName(QStringLiteral("dependencies.json" )); |
| 245 | parser.addOption(commandLineOption: dependenciesOption); |
| 246 | |
| 247 | parser.addPositionalArgument(QStringLiteral("[MOC generated json file]" ), |
| 248 | QStringLiteral("MOC generated json output." )); |
| 249 | |
| 250 | parser.process(app); |
| 251 | |
| 252 | FILE *output = stdout; |
| 253 | QScopedPointer<FILE, ScopedPointerFileCloser> outputFile; |
| 254 | |
| 255 | if (parser.isSet(option: outputOption)) { |
| 256 | QString outputName = parser.value(option: outputOption); |
| 257 | #if defined(_MSC_VER) |
| 258 | if (_wfopen_s(&output, reinterpret_cast<const wchar_t *>(outputName.utf16()), L"w" ) != 0) { |
| 259 | #else |
| 260 | output = fopen(filename: QFile::encodeName(fileName: outputName).constData(), modes: "w" ); // create output file |
| 261 | if (!output) { |
| 262 | #endif |
| 263 | fprintf(stderr, format: "Error: Cannot open %s for writing\n" , qPrintable(outputName)); |
| 264 | return EXIT_FAILURE; |
| 265 | } |
| 266 | outputFile.reset(other: output); |
| 267 | } |
| 268 | |
| 269 | fprintf(stream: output, |
| 270 | format: "/****************************************************************************\n" |
| 271 | "** Generated QML type registration code\n**\n" ); |
| 272 | fprintf(stream: output, |
| 273 | format: "** WARNING! All changes made in this file will be lost!\n" |
| 274 | "*****************************************************************************/\n\n" ); |
| 275 | fprintf(stream: output, |
| 276 | format: "#include <QtQml/qqml.h>\n" |
| 277 | "#include <QtQml/qqmlmoduleregistration.h>\n" ); |
| 278 | |
| 279 | QStringList includes; |
| 280 | QVector<QJsonObject> types; |
| 281 | QVector<QJsonObject> foreignTypes; |
| 282 | |
| 283 | const QString module = parser.value(option: importNameOption); |
| 284 | const QStringList files = parser.positionalArguments(); |
| 285 | for (const QString &source: files) { |
| 286 | QJsonDocument metaObjects; |
| 287 | { |
| 288 | QFile f(source); |
| 289 | if (!f.open(flags: QIODevice::ReadOnly)) { |
| 290 | fprintf(stderr, format: "Error opening %s for reading\n" , qPrintable(source)); |
| 291 | return EXIT_FAILURE; |
| 292 | } |
| 293 | QJsonParseError error = {.offset: 0, .error: QJsonParseError::NoError}; |
| 294 | metaObjects = QJsonDocument::fromJson(json: f.readAll(), error: &error); |
| 295 | if (error.error != QJsonParseError::NoError) { |
| 296 | fprintf(stderr, format: "Error parsing %s\n" , qPrintable(source)); |
| 297 | return EXIT_FAILURE; |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | const bool privateIncludes = parser.isSet(option: privateIncludesOption); |
| 302 | auto resolvedInclude = [&](const QString &include) { |
| 303 | return (privateIncludes && include.endsWith(s: QLatin1String("_p.h" ))) |
| 304 | ? QLatin1String("private/" ) + include |
| 305 | : include; |
| 306 | }; |
| 307 | |
| 308 | auto processMetaObject = [&](const QJsonObject &metaObject) { |
| 309 | const QString include = resolvedInclude(metaObject[QLatin1String("inputFile" )].toString()); |
| 310 | const QJsonArray classes = metaObject[QLatin1String("classes" )].toArray(); |
| 311 | for (const auto &cls : classes) { |
| 312 | QJsonObject classDef = cls.toObject(); |
| 313 | classDef.insert(key: QLatin1String("inputFile" ), value: include); |
| 314 | |
| 315 | switch (qmlTypeRegistrationMode(classDef)) { |
| 316 | case NamespaceRegistration: |
| 317 | case GadgetRegistration: |
| 318 | case ObjectRegistration: { |
| 319 | if (!include.endsWith(s: QLatin1String(".h" )) |
| 320 | && !include.endsWith(s: QLatin1String(".hpp" )) |
| 321 | && !include.endsWith(s: QLatin1String(".hxx" )) |
| 322 | && include.contains(c: QLatin1Char('.'))) { |
| 323 | fprintf(stderr, |
| 324 | format: "Class %s is declared in %s, which appears not to be a header.\n" |
| 325 | "The compilation of its registration to QML may fail.\n" , |
| 326 | qPrintable(classDef.value(QLatin1String("qualifiedClassName" )) |
| 327 | .toString()), |
| 328 | qPrintable(include)); |
| 329 | } |
| 330 | includes.append(t: include); |
| 331 | classDef.insert(key: QLatin1String("registerable" ), value: true); |
| 332 | |
| 333 | types.append(t: classDef); |
| 334 | break; |
| 335 | } |
| 336 | case NoRegistration: |
| 337 | foreignTypes.append(t: classDef); |
| 338 | break; |
| 339 | } |
| 340 | } |
| 341 | }; |
| 342 | |
| 343 | if (metaObjects.isArray()) { |
| 344 | const QJsonArray metaObjectsArray = metaObjects.array(); |
| 345 | for (const auto &metaObject : metaObjectsArray) { |
| 346 | if (!metaObject.isObject()) { |
| 347 | fprintf(stderr, format: "Error parsing %s: JSON is not an object\n" , |
| 348 | qPrintable(source)); |
| 349 | return EXIT_FAILURE; |
| 350 | } |
| 351 | |
| 352 | processMetaObject(metaObject.toObject()); |
| 353 | } |
| 354 | } else if (metaObjects.isObject()) { |
| 355 | processMetaObject(metaObjects.object()); |
| 356 | } else { |
| 357 | fprintf(stderr, format: "Error parsing %s: JSON is not an object or an array\n" , |
| 358 | qPrintable(source)); |
| 359 | return EXIT_FAILURE; |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | const QLatin1String qualifiedClassNameKey("qualifiedClassName" ); |
| 364 | auto sortTypes = [&](QVector<QJsonObject> &types) { |
| 365 | std::sort(first: types.begin(), last: types.end(), comp: [&](const QJsonObject &a, const QJsonObject &b) { |
| 366 | return a.value(key: qualifiedClassNameKey).toString() < |
| 367 | b.value(key: qualifiedClassNameKey).toString(); |
| 368 | }); |
| 369 | }; |
| 370 | |
| 371 | sortTypes(types); |
| 372 | |
| 373 | std::sort(first: includes.begin(), last: includes.end()); |
| 374 | const auto newEnd = std::unique(first: includes.begin(), last: includes.end()); |
| 375 | includes.erase(afirst: newEnd, alast: includes.end()); |
| 376 | |
| 377 | for (const QString &include : qAsConst(t&: includes)) |
| 378 | fprintf(stream: output, format: "\n#include <%s>" , qPrintable(include)); |
| 379 | |
| 380 | fprintf(stream: output, format: "\n\n" ); |
| 381 | |
| 382 | QString moduleAsSymbol = module; |
| 383 | moduleAsSymbol.replace(before: QLatin1Char('.'), after: QLatin1Char('_')); |
| 384 | |
| 385 | const QString functionName = QStringLiteral("qml_register_types_" ) + moduleAsSymbol; |
| 386 | |
| 387 | fprintf(stream: output, format: "void %s()\n{" , qPrintable(functionName)); |
| 388 | const auto majorVersion = parser.value(option: majorVersionOption); |
| 389 | |
| 390 | for (const QJsonObject &classDef : qAsConst(t&: types)) { |
| 391 | if (!classDef.value(key: QLatin1String("registerable" )).toBool()) |
| 392 | continue; |
| 393 | |
| 394 | const QString className = classDef[QLatin1String("qualifiedClassName" )].toString(); |
| 395 | |
| 396 | if (classDef.value(key: QLatin1String("namespace" )).toBool()) { |
| 397 | fprintf(stream: output, format: "\n qmlRegisterNamespaceAndRevisions(&%s::staticMetaObject, \"%s\", %s);" , |
| 398 | qPrintable(className), qPrintable(module), qPrintable(majorVersion)); |
| 399 | } else { |
| 400 | fprintf(stream: output, format: "\n qmlRegisterTypesAndRevisions<%s>(\"%s\", %s);" , |
| 401 | qPrintable(className), qPrintable(module), qPrintable(majorVersion)); |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | fprintf(stream: output, format: "\n qmlRegisterModule(\"%s\", %s, %s);" , |
| 406 | qPrintable(module), qPrintable(majorVersion), |
| 407 | qPrintable(parser.value(minorVersionOption))); |
| 408 | fprintf(stream: output, format: "\n}\n" ); |
| 409 | fprintf(stream: output, format: "\nstatic const QQmlModuleRegistration registration(\"%s\", %s, %s);\n" , |
| 410 | qPrintable(module), qPrintable(majorVersion), qPrintable(functionName)); |
| 411 | |
| 412 | if (!parser.isSet(option: pluginTypesOption)) |
| 413 | return EXIT_SUCCESS; |
| 414 | |
| 415 | if (parser.isSet(option: foreignTypesOption)) { |
| 416 | const QStringList foreignTypesFiles = parser.value(option: foreignTypesOption) |
| 417 | .split(sep: QLatin1Char(',')); |
| 418 | for (const QString &types : foreignTypesFiles) { |
| 419 | QFile typesFile(types); |
| 420 | if (!typesFile.open(flags: QIODevice::ReadOnly)) { |
| 421 | fprintf(stderr, format: "Cannot open foreign types file %s\n" , qPrintable(types)); |
| 422 | continue; |
| 423 | } |
| 424 | |
| 425 | QJsonParseError error = {.offset: 0, .error: QJsonParseError::NoError}; |
| 426 | QJsonDocument foreignMetaObjects = QJsonDocument::fromJson(json: typesFile.readAll(), error: &error); |
| 427 | if (error.error != QJsonParseError::NoError) { |
| 428 | fprintf(stderr, format: "Error parsing %s\n" , qPrintable(types)); |
| 429 | continue; |
| 430 | } |
| 431 | |
| 432 | const QJsonArray foreignObjectsArray = foreignMetaObjects.array(); |
| 433 | for (const auto &metaObject : foreignObjectsArray) { |
| 434 | if (!metaObject.isObject()) { |
| 435 | fprintf(stderr, format: "Error parsing %s: JSON is not an object\n" , |
| 436 | qPrintable(types)); |
| 437 | continue; |
| 438 | } |
| 439 | |
| 440 | const QString include = metaObject[QLatin1String("inputFile" )].toString(); |
| 441 | const QJsonArray classes = metaObject[QLatin1String("classes" )].toArray(); |
| 442 | for (const auto &cls : classes) { |
| 443 | QJsonObject classDef = cls.toObject(); |
| 444 | classDef.insert(key: QLatin1String("inputFile" ), value: include); |
| 445 | foreignTypes.append(t: classDef); |
| 446 | } |
| 447 | } |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | sortTypes(foreignTypes); |
| 452 | types += foreignRelatedTypes(types, foreignTypes); |
| 453 | sortTypes(types); |
| 454 | |
| 455 | QmlTypesCreator creator; |
| 456 | creator.setOwnTypes(std::move(types)); |
| 457 | creator.setForeignTypes(std::move(foreignTypes)); |
| 458 | creator.setModule(module); |
| 459 | creator.setMajorVersion(parser.value(option: majorVersionOption).toInt()); |
| 460 | |
| 461 | creator.generate(outFileName: parser.value(option: pluginTypesOption), dependenciesFileName: parser.value(option: dependenciesOption)); |
| 462 | return EXIT_SUCCESS; |
| 463 | } |
| 464 | |