/* * This file is part of the PySide project. * * Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). * * Contact: PySide team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include "generatorrunnerconfig.h" #include "generator.h" #ifdef _WINDOWS #define PATH_SPLITTER ";" #else #define PATH_SPLITTER ":" #endif namespace { class ArgsHandler { public: explicit ArgsHandler(const QMap& other); virtual ~ArgsHandler(); inline QMap& args() const { return *m_args; } inline bool argExists(const QString& s) const { return m_args->contains(s); } QString removeArg(const QString& s); bool argExistsRemove(const QString& s); inline QString argValue(const QString& s) const { return m_args->value(s); } inline bool noArgs() const { return m_args->isEmpty(); } private: QMap* m_args; }; ArgsHandler::ArgsHandler(const QMap& other) : m_args(new QMap(other)) { } ArgsHandler::~ArgsHandler() { delete m_args; } QString ArgsHandler::removeArg(const QString& s) { QString retval; if (argExists(s)) { retval = argValue(s); m_args->remove(s); } return retval; } bool ArgsHandler::argExistsRemove(const QString& s) { bool retval = false; if (argExists(s)) { retval = true; m_args->remove(s); } return retval; } } static void printOptions(QTextStream& s, const QMap& options) { QMap::const_iterator it = options.constBegin(); s.setFieldAlignment(QTextStream::AlignLeft); for (; it != options.constEnd(); ++it) { s << " --"; s.setFieldWidth(38); s << it.key() << it.value(); s.setFieldWidth(0); s << endl; } } typedef void (*getGeneratorsFunc)(QLinkedList*); static bool processProjectFile(QFile& projectFile, QMap& args) { QByteArray line = projectFile.readLine().trimmed(); if (line.isEmpty() || line != "[generator-project]") return false; QStringList includePaths; QStringList typesystemPaths; QStringList apiVersions; while (!projectFile.atEnd()) { line = projectFile.readLine().trimmed(); if (line.isEmpty()) continue; int split = line.indexOf("="); QString key; QString value; if (split > 0) { key = line.left(split - 1).trimmed(); value = line.mid(split + 1).trimmed(); } else { key = line; } if (key == "include-path") includePaths << QDir::toNativeSeparators(value); else if (key == "typesystem-path") typesystemPaths << QDir::toNativeSeparators(value); else if (key == "api-version") apiVersions << value; else if (key == "header-file") args["arg-1"] = value; else if (key == "typesystem-file") args["arg-2"] = value; else args[key] = value; } if (!includePaths.isEmpty()) args["include-paths"] = includePaths.join(PATH_SPLITTER); if (!typesystemPaths.isEmpty()) args["typesystem-paths"] = typesystemPaths.join(PATH_SPLITTER); if (!apiVersions.isEmpty()) args["api-version"] = apiVersions.join("|"); return true; } static QMap getInitializedArguments() { QMap args; QStringList arguments = QCoreApplication::arguments(); QString appName = arguments.first(); arguments.removeFirst(); QString projectFileName; foreach (const QString& arg, arguments) { if (arg.startsWith("--project-file")) { int split = arg.indexOf("="); if (split > 0) projectFileName = arg.mid(split + 1).trimmed(); break; } } if (projectFileName.isNull()) return args; if (!QFile::exists(projectFileName)) { std::cerr << qPrintable(appName) << ": Project file \""; std::cerr << qPrintable(projectFileName) << "\" not found."; std::cerr << std::endl; return args; } QFile projectFile(projectFileName); if (!projectFile.open(QIODevice::ReadOnly)) return args; if (!processProjectFile(projectFile, args)) { std::cerr << qPrintable(appName) << ": first line of project file \""; std::cerr << qPrintable(projectFileName) << "\" must be the string \"[generator-project]\""; std::cerr << std::endl; return args; } return args; } static QMap getCommandLineArgs() { QMap args = getInitializedArguments(); QStringList arguments = QCoreApplication::arguments(); arguments.removeFirst(); int argNum = 0; foreach (QString arg, arguments) { arg = arg.trimmed(); if (arg.startsWith("--")) { int split = arg.indexOf("="); if (split > 0) args[arg.mid(2).left(split-2)] = arg.mid(split + 1).trimmed(); else args[arg.mid(2)] = QString(); } else if (arg.startsWith("-")) { args[arg.mid(1)] = QString(); } else { argNum++; args[QString("arg-%1").arg(argNum)] = arg; } } return args; } void printUsage(const GeneratorList& generators, const QString& generatorName) { QTextStream s(stdout); s << "Usage:\n " << (generatorName.isEmpty() ? "generator" : generatorName) << " [options] header-file typesystem-file\n\n" << "General options:\n"; QMap generalOptions; generalOptions.insert("project-file=", "text file containing a description of the binding project. Replaces and overrides command line arguments"); generalOptions.insert("debug-level=[sparse|medium|full]", "Set the debug level"); generalOptions.insert("silent", "Avoid printing any message"); generalOptions.insert("help", "Display this help and exit"); generalOptions.insert("no-suppress-warnings", "Show all warnings"); generalOptions.insert("output-directory=", "The directory where the generated files will be written"); generalOptions.insert("include-paths=[" PATH_SPLITTER "" PATH_SPLITTER "...]", "Include paths used by the C++ parser"); generalOptions.insert("typesystem-paths=[" PATH_SPLITTER "" PATH_SPLITTER "...]", "Paths used when searching for typesystems"); generalOptions.insert("documentation-only", "Do not generates any code, just the documentation"); generalOptions.insert("license-file=", "File used for copyright headers of generated files"); generalOptions.insert("version", "Output version information and exit"); generalOptions.insert("generator-set=<\"generator module\">", "generator-set to be used. e.g. qtdoc"); generalOptions.insert("api-version=<\"package mask\">,<\"version\">", "Specify the supported api version used to generate the bindings"); generalOptions.insert("drop-type-entries=\"[;TypeEntry1;...]\"", "Semicolon separated list of type system entries (classes, namespaces, global functions and enums) to be dropped from generation."); printOptions(s, generalOptions); foreach (Generator* generator, generators) { QMap options = generator->options(); if (!options.isEmpty()) { s << endl << generator->name() << " options:\n"; printOptions(s, generator->options()); } } } static inline void printVerAndBanner() { std::cout << "generatorrunner v" GENERATORRUNNER_VERSION << std::endl; std::cout << "Copyright (C) 2009-2012 Nokia Corporation and/or its subsidiary(-ies)" << std::endl; } static inline void errorPrint(const QString& s, const bool& verAndBanner = false) { if (verAndBanner) printVerAndBanner(); std::cerr << s.toAscii().constData() << std::endl; } int main(int argc, char *argv[]) { // needed by qxmlpatterns QCoreApplication app(argc, argv); // Store command arguments in a map QMap args = getCommandLineArgs(); ArgsHandler argsHandler(args); GeneratorList generators; /* Get alias name (e.g.: shiboken) */ const QString aliasName = argsHandler.removeArg("alias-name"); if (argsHandler.argExistsRemove("version")) { printVerAndBanner(); return EXIT_SUCCESS; } // Try to load a generator QString generatorSet = argsHandler.removeArg("generator-set"); // Also check "generatorSet" command line argument for backward compatibility. if (generatorSet.isEmpty()) generatorSet = argsHandler.removeArg("generatorSet"); if (!generatorSet.isEmpty()) { QFileInfo generatorFile(generatorSet); if (!generatorFile.exists()) { QString generatorSetName(generatorSet + "_generator" + MODULE_EXTENSION); // More library paths may be added via the QT_PLUGIN_PATH environment variable. QCoreApplication::addLibraryPath(GENERATORRUNNER_PLUGIN_DIR); foreach (const QString& path, QCoreApplication::libraryPaths()) { generatorFile.setFile(QDir(path), generatorSetName); if (generatorFile.exists()) break; } } if (!generatorFile.exists()) { errorPrint(QString("%1: Error loading generator-set plugin: %2 module not found."). arg(aliasName.isEmpty() ? argv[0] : aliasName). arg(qPrintable(generatorFile.baseName())), true); return EXIT_FAILURE; } QLibrary plugin(generatorFile.filePath()); getGeneratorsFunc getGenerators = (getGeneratorsFunc)plugin.resolve("getGenerators"); if (getGenerators) { getGenerators(&generators); } else { errorPrint(QString("%1: Error loading generator-set plugin: %2"). arg(aliasName.isEmpty() ? argv[0] : aliasName). arg(qPrintable(plugin.errorString())), true); return EXIT_FAILURE; } } else if (!argsHandler.argExists("help")) { errorPrint(QString("%1: You need to specify a generator with --generator-set=GENERATOR_NAME"). arg(aliasName.isEmpty() ? argv[0] : aliasName), true); return EXIT_FAILURE; } /* We need alias-name argument for the usage's message being * printed properly when running Generatorrunner from a Shiboken's * process. Don't worry, this "new method of doing IPC between two * processes" is only used here as a workaround, and not our way * out to do IPC. :-) */ if (argsHandler.argExistsRemove("help")) { printUsage(generators, aliasName); return EXIT_SUCCESS; } QString licenseComment; QString licenseFileName = argsHandler.removeArg("license-file"); if (!licenseFileName.isEmpty()) { if (QFile::exists(licenseFileName)) { QFile licenseFile(licenseFileName); if (licenseFile.open(QIODevice::ReadOnly)) licenseComment = licenseFile.readAll(); } else { errorPrint(QString("Couldn't find the file containing the license heading: %1"). arg(qPrintable(licenseFileName))); return EXIT_FAILURE; } } QString outputDirectory = argsHandler.removeArg("output-directory"); if (outputDirectory.isEmpty()) outputDirectory = "out"; if (!QDir(outputDirectory).exists()) { if (!QDir().mkpath(outputDirectory)) { ReportHandler::warning("Can't create output directory: "+outputDirectory); return EXIT_FAILURE; } } // Create and set-up API Extractor ApiExtractor extractor; extractor.setLogDirectory(outputDirectory); if (argsHandler.argExistsRemove("silent")) { extractor.setSilent(true); } else { QString level = argsHandler.removeArg("debug-level"); if (!level.isEmpty()) { if (level == "sparse") extractor.setDebugLevel(ReportHandler::SparseDebug); else if (level == "medium") extractor.setDebugLevel(ReportHandler::MediumDebug); else if (level == "full") extractor.setDebugLevel(ReportHandler::FullDebug); } } if (argsHandler.argExistsRemove("no-suppress-warnings")) extractor.setSuppressWarnings(false); if (argsHandler.argExists("api-version")) { QStringList versions = argsHandler.removeArg("api-version").split("|"); foreach (QString fullVersion, versions) { QStringList parts = fullVersion.split(","); QString package; QString version; package = parts.count() == 1 ? "*" : parts.first(); version = parts.last(); extractor.setApiVersion(package, version.toAscii()); } } if (argsHandler.argExists("drop-type-entries")) extractor.setDropTypeEntries(argsHandler.removeArg("drop-type-entries")); QString path = argsHandler.removeArg("typesystem-paths"); if (!path.isEmpty()) extractor.addTypesystemSearchPath(path.split(PATH_SPLITTER)); path = argsHandler.removeArg("include-paths"); if (!path.isEmpty()) extractor.addIncludePath(path.split(PATH_SPLITTER)); QString cppFileName = argsHandler.removeArg("arg-1"); QString typeSystemFileName = argsHandler.removeArg("arg-2"); /* Make sure to remove the project file's arguments (if any) and * --project-file, also the arguments of each generator before * checking if there isn't any existing arguments in argsHandler. */ argsHandler.removeArg("project-file"); QMap projectFileArgs = getInitializedArguments(); if (!projectFileArgs.isEmpty()) { QMap::const_iterator it = projectFileArgs.constBegin(); for ( ; it != projectFileArgs.constEnd(); ++it) argsHandler.removeArg(it.key()); } foreach (Generator* generator, generators) { QMap options = generator->options(); if (!options.isEmpty()) { QMap::const_iterator it = options.constBegin(); for ( ; it != options.constEnd(); ++it) argsHandler.removeArg(it.key()); } } if (!argsHandler.noArgs()) { errorPrint(QString("%1: Called with wrong arguments."). arg(aliasName.isEmpty() ? argv[0] : aliasName), true); std::cout << "Note: use --help option for more information." << std::endl; return EXIT_FAILURE; } extractor.setCppFileName(cppFileName); extractor.setTypeSystem(typeSystemFileName); if (!extractor.run()) return EXIT_FAILURE; if (!extractor.classCount()) ReportHandler::warning("No C++ classes found!"); foreach (Generator* g, generators) { g->setOutputDirectory(outputDirectory); g->setLicenseComment(licenseComment); if (g->setup(extractor, args)) g->generate(); } qDeleteAll(generators); ReportHandler::flush(); std::cout << "Done, " << ReportHandler::warningCount(); std::cout << " warnings (" << ReportHandler::suppressedCount() << " known issues)"; std::cout << std::endl; }