/**************************************************************************** ** ** Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the Qt Script Generator project on Qt Labs. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include "asttoxml.h" #include "reporthandler.h" #include "typesystem.h" #include "generatorset.h" #include "fileout.h" #include "control.h" // Enable simplecpp’s legacy raw-pointer TokenList API so that the PythonQt // generator can keep using its existing integration. // Reference: https://github.com/danmar/simplecpp/pull/496 #define SIMPLECPP_TOKENLIST_ALLOW_PTR #include "simplecpp.h" #include #include #include #include #include #include #include void displayHelp(GeneratorSet* generatorSet); namespace { static const QStringList candidatesQtModules { QStringLiteral("QtCore"), QStringLiteral("QtGui"), QStringLiteral("QtNetwork"), QStringLiteral("QtOpenGL"), QStringLiteral("QtXml"), }; static bool isDir(const QString& p) { return QFileInfo(p).isDir(); } static QString joinPath(const QString& base, const QString& child) { return QDir(base).filePath(child); } #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QString qEnvironmentVariable(const char* varName) { return QString::fromLocal8Bit(qgetenv(varName)); } #endif QStringList getIncludeDirectories(const QString& commandLineIncludes) { QStringList includes; includes << QStringLiteral("."); const QChar pathSplitter = QDir::listSeparator(); // From env var PYTHONQT_INCLUDE const QString envInclude = qEnvironmentVariable("PYTHONQT_INCLUDE"); if (!envInclude.isEmpty()) { QStringList envIncludes = envInclude.split(pathSplitter, Qt::SkipEmptyParts); for (const QString& include : qAsConst(envIncludes)) { if (isDir(include)) { includes << include; } else { qWarning() << "Include path" << include << "does not exist, ignoring."; } } } // CLI-provided include paths if (!commandLineIncludes.isEmpty()) { const QStringList cliIncludes = commandLineIncludes.split(QDir::listSeparator(), Qt::SkipEmptyParts); for (const QString& include : cliIncludes) { if (isDir(include)) { includes << QDir::cleanPath(include); } else { qWarning() << "Include path" << include << "does not exist, ignoring."; } } } // Prefer QTDIR first (allows targeting a different Qt). // If QTDIR/include exists use it, otherwise treat QTDIR as the include root QString qtInclude; const QString qtDir = qEnvironmentVariable("QTDIR"); if (!qtDir.isEmpty() && isDir(qtDir)) { const QString qtIncludeUnder = joinPath(qtDir, QStringLiteral("include")); qtInclude = isDir(qtIncludeUnder) ? qtIncludeUnder : qtDir; } else { const QString reason = QStringLiteral("The QTDIR environment variable ") + (qtDir.isEmpty() ? "is not set." : "points to a non-existing directory."); qWarning().noquote() << reason << "Falling back to QLibraryInfo includes path."; } // Fallback to QLibraryInfo (works without QTDIR or when QTDIR unusable) if (qtInclude.isEmpty()) { qtInclude = QLibraryInfo::location(QLibraryInfo::HeadersPath); } if (!qtInclude.isEmpty() && isDir(qtInclude)) { qInfo() << "Using Qt headers at:" << qtInclude; // Check for / for (const QString& qtModule : qAsConst(candidatesQtModules)) { const QString qtModuleInclude = joinPath(qtInclude, qtModule); if (isDir(qtModuleInclude)) { includes << qtModuleInclude; } } includes << qtInclude; } return includes; } QStringList getFrameworkDirectories(const QString& commandLineFrameworks) { QStringList frameworks; frameworks << QStringLiteral("."); const QChar pathSplitter = QDir::listSeparator(); // From env var PYTHONQT_FRAMEWORK const QString envFramework = qEnvironmentVariable("PYTHONQT_FRAMEWORK"); if (!envFramework.isEmpty()) { QStringList envFrameworks = envFramework.split(pathSplitter, Qt::SkipEmptyParts); for (const QString& framework : qAsConst(envFrameworks)) { if (isDir(framework)) { frameworks << framework; } else { qWarning() << "Framework path" << framework << "does not exist, ignoring."; } } } // CLI-provided framework paths if (!commandLineFrameworks.isEmpty()) { const QStringList cliFrameworks = commandLineFrameworks.split(QDir::listSeparator(), Qt::SkipEmptyParts); for (const QString& framework : cliFrameworks) { if (isDir(framework)) { frameworks << QDir::cleanPath(framework); } else { qWarning() << "Framework path" << framework << "does not exist, ignoring."; } } } // Prefer QTDIR/lib first (allows targeting a different Qt) QString qtLib; const QString qtDir = qEnvironmentVariable("QTDIR"); if (!qtDir.isEmpty() && isDir(qtDir)) { qtLib = joinPath(qtDir, QStringLiteral("lib")); } else { const QString reason = QStringLiteral("The QTDIR environment variable ") + (qtDir.isEmpty() ? "is not set." : "points to a non-existing directory."); qWarning().noquote() << reason << "Falling back to QLibraryInfo libraries path."; } // Fallback to QLibraryInfo (works without QTDIR or when QTDIR unusable) if (qtLib.isEmpty()) { qtLib = QLibraryInfo::location(QLibraryInfo::LibrariesPath); } if (!qtLib.isEmpty() && isDir(qtLib)) { qInfo() << "Using Qt frameworks at:" << qtLib; // Check for /.framework bundles for (const QString& qtModule : qAsConst(candidatesQtModules)) { const QString qtModuleFramework = QDir(qtLib).filePath(qtModule + ".framework"); if (QDir(qtModuleFramework).exists()) { frameworks << qtModuleFramework; } } frameworks << qtLib; } return frameworks; } bool preprocess(const QString& sourceFile, const QString& targetFile, const QStringList& includePaths, const QStringList& frameworkPaths) { simplecpp::DUI dui; // settings for (const QString& include : includePaths) { dui.addIncludePath(QDir::toNativeSeparators(include).toStdString()); } for (const QString& framework : frameworkPaths) { dui.addFrameworkPath(QDir::toNativeSeparators(framework).toStdString()); } dui.defines.push_back("__cplusplus=1"); dui.defines.push_back("__STDC__"); #ifdef Q_PROCESSOR_X86_64 dui.defines.push_back("__x86_64__"); #endif dui.std = "c++20"; dui.removeComments = true; QFile file(sourceFile); if (!file.open(QFile::ReadOnly)) { std::cerr << "Main file not found:" << sourceFile.toUtf8().constData() << std::endl; return false; } QByteArray ba = file.readAll(); file.close(); // Perform preprocessing (code originally taken from simplecpp/main.cpp and modified) simplecpp::OutputList outputList; std::vector files; std::unique_ptr rawtokens( new simplecpp::TokenList(ba.constData(), ba.size(), files, {}, &outputList)); rawtokens->removeComments(); simplecpp::TokenList outputTokens(files); simplecpp::FileDataCache filedata; simplecpp::preprocess(outputTokens, *rawtokens, files, filedata, dui, &outputList); simplecpp::cleanup(filedata); rawtokens.reset(); for (const simplecpp::Output& output : outputList) { if (output.type == simplecpp::Output::MISSING_HEADER) { // do not print these messages for now, we are not interested continue; } std::cerr << output.location.file() << ':' << output.location.line << ": "; switch (output.type) { case simplecpp::Output::ERROR: std::cerr << "#error: "; break; case simplecpp::Output::WARNING: std::cerr << "#warning: "; break; case simplecpp::Output::MISSING_HEADER: std::cerr << "missing header: "; break; case simplecpp::Output::INCLUDE_NESTED_TOO_DEEPLY: std::cerr << "include nested too deeply: "; break; case simplecpp::Output::SYNTAX_ERROR: std::cerr << "syntax error: "; break; case simplecpp::Output::PORTABILITY_BACKSLASH: std::cerr << "portability: "; break; case simplecpp::Output::UNHANDLED_CHAR_ERROR: std::cerr << "unhandled char error: "; break; case simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND: std::cerr << "explicit include not found: "; break; case simplecpp::Output::FILE_NOT_FOUND: std::cerr << "file not found: "; break; case simplecpp::Output::DUI_ERROR: std::cerr << "dui error: "; break; } std::cerr << output.msg << std::endl; } QFile f(targetFile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { fprintf(stderr, "Failed to write preprocessed file: %s\n", qPrintable(targetFile)); } std::string result = outputTokens.stringify(); f.write(result.c_str(), result.length()); return true; } unsigned int getQtVersion(const QStringList& paths) { QRegularExpression re("#define\\s+QTCORE_VERSION\\s+0x([0-9a-f]+)", QRegularExpression::CaseInsensitiveOption); // Iterate through provided paths to find the qtcoreversion.h header file for (const QString& path : paths) { #ifdef Q_OS_MACOS // Define potential locations for the header file on macOS const QStringList candidates = { joinPath(path, "Versions/A/Headers/qtcoreversion.h"), joinPath(path, "Versions/5/Headers/qtcoreversion.h"), joinPath(path, "Headers/qtcoreversion.h"), // top-level Headers (often a symlink into Versions) }; #else // Define the location for other platforms const QStringList candidates = {joinPath(path, "qtcoreversion.h")}; #endif // Pick the first existing candidate QString qtCoreVersionHeader; for (const QString& candidate : candidates) { if (QFile::exists(candidate)) { qtCoreVersionHeader = candidate; break; } } if (QFile::exists(qtCoreVersionHeader)) { QString filePath = QFileInfo(qtCoreVersionHeader).absoluteFilePath(); QFile f(filePath); if (f.open(QIODevice::ReadOnly)) { QTextStream ts(&f); QString content = ts.readAll(); f.close(); auto match = re.match(content); if (match.isValid()) { unsigned int result; bool ok; result = match.captured(1).toUInt(&ok, 16); if (!ok) { printf("Could not parse Qt version in file [%s] (looked for #define QTCORE_VERSION)\n", qPrintable(filePath)); } return result; } } } } printf("Error: Could not find Qt version (looked for qtcoreversion.h in %s)\n", qPrintable(paths.join(QDir::listSeparator()))); return 0; } }; int main(int argc, char* argv[]) { ReportHandler::setContext("Arguments"); QScopedPointer gs(GeneratorSet::getInstance()); QString default_file = ":/trolltech/generator/qtscript_masterinclude.h"; QString default_system = ":/trolltech/generator/build_all.txt"; QString fileName; QString typesystemFileName; QString pp_file = ".preprocessed.tmp"; QMap args; unsigned int qtVersion {}; int argNum = 0; for (int i = 1; i < argc; ++i) { QString arg(argv[i]); 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; } } if (args.contains("no-suppress-warnings")) { TypeDatabase* db = TypeDatabase::instance(); db->setSuppressWarnings(false); } if (args.contains("debug-level")) { QString level = args.value("debug-level"); if (level == "sparse") ReportHandler::setDebugLevel(ReportHandler::SparseDebug); else if (level == "medium") ReportHandler::setDebugLevel(ReportHandler::MediumDebug); else if (level == "full") ReportHandler::setDebugLevel(ReportHandler::FullDebug); } if (args.contains("print-parser-errors")) { Control::setPrintErrors(true); } if (args.contains("dummy")) { FileOut::dummy = true; } if (args.contains("diff")) { FileOut::diff = true; } if (args.contains("license")) FileOut::license = true; if (args.contains("rebuild-only")) { QStringList classes = args.value("rebuild-only").split(",", Qt::SkipEmptyParts); TypeDatabase::instance()->setRebuildClasses(classes); } if (args.contains("qt-version")) { bool ok; qtVersion = TypeSystem::qtVersionFromString(args.value("qt-version"), ok); if (!ok || qtVersion < 0x050000) { printf("Invalid Qt version specified, will look into header files for version...\n"); qtVersion = 0; } } fileName = args.value("arg-1"); typesystemFileName = args.value("arg-2"); if (args.contains("arg-3")) displayHelp(&*gs); if (fileName.isEmpty()) fileName = default_file; if (typesystemFileName.isEmpty()) typesystemFileName = default_system; if (fileName.isEmpty() || typesystemFileName.isEmpty()) displayHelp(&*gs); if (!gs->readParameters(args)) displayHelp(&*gs); printf("Please wait while source files are being generated...\n"); QStringList includePaths = getIncludeDirectories(args.value("include-paths")); if (!qtVersion && !includePaths.empty()) { printf("Trying to determine Qt version...\n"); qtVersion = getQtVersion(includePaths); } #ifdef Q_OS_MACOS QStringList frameworkPaths = getFrameworkDirectories(args.value("framework-paths")); if (!qtVersion && !frameworkPaths.empty()) { printf("Trying to determine Qt version...\n"); qtVersion = getQtVersion(frameworkPaths); } if (!qtVersion) { fprintf(stderr, "Aborting. Could not determine Qt version from include or framework paths.\n"); return 1; } #else Q_UNUSED(getFrameworkDirectories); QStringList frameworkPaths; if (!qtVersion) { fprintf(stderr, "Aborting. Could not determine Qt version from include paths.\n"); return 1; } #endif printf("Determined Qt version is %d.%d.%d\n", qtVersion >> 16, (qtVersion >> 8) & 0xFF, qtVersion & 0xFF); printf("Parsing typesystem file [%s]\n", qPrintable(typesystemFileName)); fflush(stdout); ReportHandler::setContext("Typesystem"); if (TypeDatabase::instance()->parseFile(typesystemFileName, qtVersion)) { TypeDatabase::instance()->finalSetup(); } else { qFatal("Cannot parse file: '%s'", qPrintable(typesystemFileName)); } printf("PreProcessing - Generate [%s] using [%s] and include-paths [%s]\n", qPrintable(pp_file), qPrintable(fileName), qPrintable(args.value("include-paths"))); ReportHandler::setContext("Preprocess"); if (!preprocess(fileName, pp_file, includePaths, frameworkPaths)) { fprintf(stderr, "Preprocessor failed on file: '%s'\n", qPrintable(fileName)); return 1; } if (args.contains("ast-to-xml")) { printf("Running ast-to-xml on file [%s] using pp_file [%s] and include-paths [%s]\n", qPrintable(fileName), qPrintable(pp_file), qPrintable(args.value("include-paths"))); ReportHandler::setContext(QString("AST-to-XML")); astToXML(pp_file); return 0; } printf("Building model using [%s]\n", qPrintable(pp_file)); ReportHandler::setContext("Build"); gs->setIncludePaths(includePaths); gs->buildModel(pp_file); if (args.contains("dump-object-tree")) { gs->dumpObjectTree(); return 0; } ReportHandler::setContext("Generate"); printf("%s\n", qPrintable(gs->generate())); printf("Done, %d warnings (%d known issues)\n", ReportHandler::warningCount(), ReportHandler::suppressedCount()); } void displayHelp(GeneratorSet* generatorSet) { const auto path_splitter = QDir::listSeparator().toLatin1(); printf("Usage:\n generator [options] header-file typesystem-file\n\n"); printf("Available options:\n\n"); printf("General:\n"); printf(" --debug-level=[sparse|medium|full] \n" " --print-parser-errors \n" " --dump-object-tree \n" " --help, -h or -? \n" " --no-suppress-warnings \n" " --output-directory=[dir] \n" " --include-paths=[%c%c...] \n" #ifdef Q_OS_MACOS " --framework-paths=[%c%c...] \n" #endif " --qt-version=x.y.z \n" " --print-stdout \n", path_splitter, path_splitter); printf("%s", qPrintable(generatorSet->usage())); exit(0); }