diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10dc3d84e..008924802 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -257,9 +257,8 @@ jobs: if: ${{ contains(matrix.configuration, 'release') }} run: | cd generator - # workaround to allow to find the Qt include dirs for installed standard qt packages UBSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="detect_leaks=0:detect_stack_use_after_return=1:fast_unwind_on_malloc=0" \ - QTDIR=-UNDEFINED- ./pythonqt_generator --qt-version=${{ steps.versions.outputs.QT_VERSION_FULL }} --include-paths=$QT_ROOT_DIR/lib + ./pythonqt_generator - name: Upload Wrappers if: ${{ contains(matrix.configuration, 'release') }} @@ -267,8 +266,7 @@ jobs: with: name: wrappers_macos${{ steps.versions.outputs.MACOS_VERSION_SHORT }}_qt${{ steps.versions.outputs.QT_VERSION_SHORT }} path: generated_cpp - # comment in after #276 is fixed - # if-no-files-found: error + if-no-files-found: error windows: strategy: diff --git a/generator/main.cpp b/generator/main.cpp index 8d9f0f4e8..6665db751 100644 --- a/generator/main.cpp +++ b/generator/main.cpp @@ -58,6 +58,7 @@ #include #include #include +#include #include #include @@ -67,77 +68,162 @@ 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 << QString("."); - - QChar pathSplitter = QDir::listSeparator(); + includes << QStringLiteral("."); - // Environment PYTHONQT_INCLUDE - QString includePath = getenv("PYTHONQT_INCLUDE"); - if (!includePath.isEmpty()) - includes += includePath.split(pathSplitter, Qt::SkipEmptyParts); + const QChar pathSplitter = QDir::listSeparator(); - // Includes from the command line - if (!commandLineIncludes.isEmpty()) - includes += commandLineIncludes.split(pathSplitter, Qt::SkipEmptyParts); - for (auto it = includes.begin(); it != includes.end();) - { - if (!QDir(*it).exists()) - { - qWarning() << "Include path " << it->toUtf8() << " does not exist, ignoring it."; - it = includes.erase(it); + // 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."; + } } - else - { - ++it; + } + + // 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."; + } } } - // Include Qt - QString qtdir = getenv("QTDIR"); - if (qtdir.isEmpty() || !QDir(qtdir).exists(qtdir)) - { - QString reason = "The QTDIR environment variable " + qtdir.isEmpty() ? - "is not set. " : "points to a non-existing directory. "; -#if defined(Q_OS_MAC) - qWarning() << reason << "Assuming standard binary install using frameworks."; - QString frameworkDir = "/Library/Frameworks"; - includes << (frameworkDir + "/QtXml.framework/Headers"); - includes << (frameworkDir + "/QtNetwork.framework/Headers"); - includes << (frameworkDir + "/QtCore.framework/Headers"); - includes << (frameworkDir + "/QtGui.framework/Headers"); - includes << (frameworkDir + "/QtOpenGL.framework/Headers"); - includes << frameworkDir; -#else - qWarning() << reason << "This may cause problems with finding the necessary include files."; -#endif + // Prefer QLibraryInfo (works without QTDIR) + QString qtInclude = QLibraryInfo::location(QLibraryInfo::HeadersPath); + if (!isDir(qtInclude)) { + // Fallback to QTDIR/include + const QString qtDir = qEnvironmentVariable("QTDIR"); + if (qtDir.isEmpty() || !isDir(qtDir)) { + const QString reason = QStringLiteral("The QTDIR environment variable ") + + (qtDir.isEmpty() + ? "is not set." + : "points to a non-existing directory."); + qWarning() << reason << "This may cause problems with finding the necessary include files."; + } else { + qtInclude = joinPath(qtDir, QStringLiteral("include")); + } } - else - { - std::cout << "-------------------------------------------------------------" << std::endl; - std::cout << "Using QT at: " << qtdir.toLocal8Bit().constData() << std::endl; - std::cout << "-------------------------------------------------------------" << std::endl; - qtdir += "/include"; - includes << (qtdir + "/QtXml"); - includes << (qtdir + "/QtNetwork"); - includes << (qtdir + "/QtCore"); - includes << (qtdir + "/QtGui"); - includes << (qtdir + "/QtOpenGL"); - includes << qtdir; + 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 QLibraryInfo (works without QTDIR) + QString qtLib = QLibraryInfo::location(QLibraryInfo::LibrariesPath); + if (!isDir(qtLib)) { + // Fallback to QTDIR/include + const QString qtDir = qEnvironmentVariable("QTDIR"); + if (qtDir.isEmpty() || !isDir(qtDir)) { + const QString reason = QStringLiteral("The QTDIR environment variable ") + + (qtDir.isEmpty() + ? "is not set." + : "points to a non-existing directory."); + qWarning() << reason << "This may cause problems with finding the necessary framework files."; + } else { + qtLib = joinPath(qtDir, QStringLiteral("lib")); + } + } + 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) + preprocess(const QString& sourceFile, const QString& targetFile, const QStringList& includePaths, const QStringList& frameworkPaths) { simplecpp::DUI dui; // settings - for(QString include : includePaths) { - dui.includePaths.push_back(QDir::toNativeSeparators(include).toStdString()); + 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__"); @@ -217,15 +303,36 @@ namespace return true; } - unsigned int getQtVersion(const QStringList& includePaths) + unsigned int getQtVersion(const QStringList& paths) { QRegularExpression re("#define\\s+QTCORE_VERSION\\s+0x([0-9a-f]+)", QRegularExpression::CaseInsensitiveOption); - for (const QString &includeDir: includePaths) + + // Iterate through provided paths to find the qtcoreversion.h header file + for (const QString &path: paths) { - QFileInfo fi(QDir(includeDir), "qtcoreversion.h"); - if (fi.exists()) +#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 = fi.absoluteFilePath(); + QString filePath = QFileInfo(qtCoreVersionHeader).absoluteFilePath(); QFile f(filePath); if (f.open(QIODevice::ReadOnly)) { @@ -249,7 +356,7 @@ namespace } } printf("Error: Could not find Qt version (looked for qtcoreversion.h in %s)\n", - qPrintable(includePaths.join(QDir::listSeparator()))); + qPrintable(paths.join(QDir::listSeparator()))); return 0; } }; @@ -354,16 +461,29 @@ int main(int argc, char *argv[]) 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) { - printf("Trying to determine Qt version...\n"); - qtVersion = getQtVersion(includePaths); - if (!qtVersion) - { - fprintf(stderr, "Aborting\n"); // the error message was printed by getQtVersion - return 1; - } - printf("Determined Qt version is %d.%d.%d\n", qtVersion >> 16, (qtVersion >> 8) & 0xFF, qtVersion & 0xFF); + 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); @@ -379,7 +499,7 @@ int main(int argc, char *argv[]) qPrintable(pp_file), qPrintable(fileName), qPrintable(args.value("include-paths"))); ReportHandler::setContext("Preprocess"); - if (!preprocess(fileName, pp_file, includePaths)) { + if (!preprocess(fileName, pp_file, includePaths, frameworkPaths)) { fprintf(stderr, "Preprocessor failed on file: '%s'\n", qPrintable(fileName)); return 1; } @@ -420,6 +540,9 @@ void displayHelp(GeneratorSet* generatorSet) { " --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); diff --git a/generator/simplecpp/README.txt b/generator/simplecpp/README.txt index c0f265f37..7cd864f4c 100644 --- a/generator/simplecpp/README.txt +++ b/generator/simplecpp/README.txt @@ -1,4 +1,9 @@ -This code is taken from https://github.com/danmar/simplecpp, version post-1.5.2 (37fa4f4) +This code is taken from https://github.com/commontk/simplecpp, version post-1.5.2 (bfc53ad) + +Origin: + +* commontk/simplecpp is a fork of https://github.com/danmar/simplecpp +* It corresponds to upstream commit 37fa4f4, with PR #511 (https://github.com/danmar/simplecpp/pull/511) backported. The code was released under the 0BSD license (see LICENSE file). diff --git a/generator/simplecpp/simplecpp.cpp b/generator/simplecpp/simplecpp.cpp index 88cedd4e1..da838133a 100644 --- a/generator/simplecpp/simplecpp.cpp +++ b/generator/simplecpp/simplecpp.cpp @@ -13,6 +13,7 @@ #include "simplecpp.h" #include +#include #include #include #include @@ -2429,6 +2430,27 @@ static bool isAbsolutePath(const std::string &path) } #endif +namespace { + // "" -> "" (and PrivateHeaders variant). + // Returns candidates in priority order (Headers, then PrivateHeaders). + inline std::array + toAppleFrameworkRelatives(const std::string& header) + { + const std::size_t slash = header.find('/'); + if (slash == std::string::npos) + return { header, header }; // no transformation applicable + const std::string pkg = header.substr(0, slash); + const std::string tail = header.substr(slash); // includes '/' + return { pkg + ".framework/Headers" + tail, + pkg + ".framework/PrivateHeaders" + tail }; + } + + inline void push_unique(std::vector &v, std::string p) { + if (!p.empty() && (v.empty() || v.back() != p)) + v.push_back(std::move(p)); + } +} + namespace simplecpp { /** * perform path simplifications for . and .. @@ -2999,12 +3021,61 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const } } - // search the header on the include paths (provided by the flags "-I...") - for (const auto &includePath : dui.includePaths) { - std::string path = openHeaderDirect(f, simplecpp::simplifyPath(includePath + "/" + header)); - if (!path.empty()) - return path; + // Build an ordered, typed path list: + // - Prefer DUI::searchPaths when provided (interleaved -I/-F/-iframework). + // - Otherwise mirror legacy includePaths into Include entries (back-compat). + std::vector searchPaths; + if (!dui.searchPaths.empty()) { + searchPaths = dui.searchPaths; + } else { + searchPaths.reserve(dui.includePaths.size()); + for (const auto &includePath : dui.includePaths) + searchPaths.push_back({includePath, simplecpp::DUI::PathKind::Include}); } + + // Interleave -I and -F in CLI order + for (const auto &searchPath : searchPaths) { + if (searchPath.kind == simplecpp::DUI::PathKind::Include) { + const std::string path = openHeaderDirect(f, simplecpp::simplifyPath(searchPath.path + "/" + header)); + if (!path.empty()) + return path; + } else if (searchPath.kind == simplecpp::DUI::PathKind::Framework) { + // try Headers then PrivateHeaders + const auto relatives = toAppleFrameworkRelatives(header); + if (relatives[0] != header) { // Skip if no framework rewrite was applied. + for (const auto &rel : relatives) { + const std::string frameworkPath = openHeaderDirect(f, simplecpp::simplifyPath(searchPath.path + "/" + rel)); + if (!frameworkPath.empty()) + return frameworkPath; + } + } + } + } + + // -isystem + for (const auto &searchPath : searchPaths) { + if (searchPath.kind == simplecpp::DUI::PathKind::SystemInclude) { + std::string path = openHeaderDirect(f, simplecpp::simplifyPath(searchPath.path + "/" + header)); + if (!path.empty()) + return path; + } + } + + // -iframework + for (const auto &searchPath : searchPaths) { + if (searchPath.kind == simplecpp::DUI::PathKind::SystemFramework) { + const auto relatives = toAppleFrameworkRelatives(header); + if (relatives[0] != header) { // Skip if no framework rewrite was applied. + // Try Headers then PrivateHeaders + for (const auto &rel : relatives) { + const std::string frameworkPath = openHeaderDirect(f, simplecpp::simplifyPath(searchPath.path + "/" + rel)); + if (!frameworkPath.empty()) + return frameworkPath; + } + } + } + } + return ""; } @@ -3036,6 +3107,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat std::pair simplecpp::FileDataCache::get(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) { + // Absolute path: load directly if (isAbsolutePath(header)) { auto ins = mNameMap.emplace(simplecpp::simplifyPath(header), nullptr); @@ -3051,32 +3123,78 @@ std::pair simplecpp::FileDataCache::get(const std:: return {nullptr, false}; } + // Build ordered candidates. + std::vector candidates; + + // Prefer first to search the header relatively to source file if found, when not a system header if (!systemheader) { - auto ins = mNameMap.emplace(simplecpp::simplifyPath(dirPath(sourcefile) + header), nullptr); + push_unique(candidates, simplecpp::simplifyPath(dirPath(sourcefile) + header)); + } - if (ins.second) { - const auto ret = tryload(ins.first, dui, filenames, outputList); - if (ret.first != nullptr) { - return ret; + // Build an ordered, typed path list: + // - Prefer DUI::searchPaths when provided (interleaved -I/-F/-iframework). + // - Otherwise mirror legacy includePaths into Include entries (back-compat). + std::vector searchPaths; + if (!dui.searchPaths.empty()) { + searchPaths = dui.searchPaths; + } else { + searchPaths.reserve(dui.includePaths.size()); + for (const auto &p : dui.includePaths) + searchPaths.push_back({p, simplecpp::DUI::PathKind::Include}); + } + + // Interleave -I and -F in CLI order + for (const auto &searchPath : searchPaths) { + if (searchPath.kind == simplecpp::DUI::PathKind::Include) { + push_unique(candidates, simplecpp::simplifyPath(searchPath.path + "/" + header)); + } else if (searchPath.kind == simplecpp::DUI::PathKind::Framework) { + // Try Headers then PrivateHeaders + const auto relatives = toAppleFrameworkRelatives(header); + if (relatives[0] != header) { // Skip if no framework rewrite was applied. + push_unique(candidates, simplecpp::simplifyPath(searchPath.path + "/" + relatives[0])); + push_unique(candidates, simplecpp::simplifyPath(searchPath.path + "/" + relatives[1])); } - } else if (ins.first->second != nullptr) { - return {ins.first->second, false}; } } - for (const auto &includePath : dui.includePaths) { - auto ins = mNameMap.emplace(simplecpp::simplifyPath(includePath + "/" + header), nullptr); + // -isystem + for (const auto &searchPath : searchPaths) { + if (searchPath.kind == DUI::PathKind::SystemInclude) { + push_unique(candidates, simplecpp::simplifyPath(searchPath.path + "/" + header)); + } + } - if (ins.second) { - const auto ret = tryload(ins.first, dui, filenames, outputList); - if (ret.first != nullptr) { - return ret; + // -iframework + for (const auto &searchPath : searchPaths) { + if (searchPath.kind == simplecpp::DUI::PathKind::SystemFramework) { + // Try Headers then PrivateHeaders + const auto relatives = toAppleFrameworkRelatives(header); + if (relatives[0] != header) { // Skip if no framework rewrite was applied. + push_unique(candidates, simplecpp::simplifyPath(searchPath.path + "/" + relatives[0])); + push_unique(candidates, simplecpp::simplifyPath(searchPath.path + "/" + relatives[1])); } - } else if (ins.first->second != nullptr) { - return {ins.first->second, false}; } } + // Try loading each candidate path (left-to-right). + for (const std::string &candidate : candidates) { + // Already loaded? + auto it = mNameMap.find(candidate); + if (it != mNameMap.end()) { + return {it->second, false}; + } + + auto ins = mNameMap.emplace(candidate, static_cast(nullptr)); + const auto ret = tryload(ins.first, dui, filenames, outputList); + if (ret.first != nullptr) { + return ret; + } + + // Failed: remove placeholder so we can retry later if needed. + mNameMap.erase(ins.first); + } + + // Not found. return {nullptr, false}; } diff --git a/generator/simplecpp/simplecpp.h b/generator/simplecpp/simplecpp.h index ac367154e..8b45c9c80 100644 --- a/generator/simplecpp/simplecpp.h +++ b/generator/simplecpp/simplecpp.h @@ -395,13 +395,70 @@ namespace simplecpp { /** * Command line preprocessor settings. - * On the command line these are configured by -D, -U, -I, --include, -std + * + * Mirrors typical compiler options: + * -D = Add macro definition + * -U Undefine macro + * -I Add include search directory + * -isystem Add system include search directory + * -F Add framework search directory (Darwin) + * -iframework Add system framework search directory (Darwin) + * --include Force inclusion of a header + * -std= Select language standard (C++17, C23, etc.) + * + * Path search behavior: + * - If searchPaths is non-empty, it is used directly, preserving the + * left-to-right order and distinguishing between Include, Framework, + * and SystemFramework kinds. + * - If searchPaths is empty, legacy includePaths is used instead, and + * each entry is treated as a normal Include path (for backward + * compatibility). */ struct SIMPLECPP_LIB DUI { DUI() : clearIncludeCache(false), removeComments(false) {} + + // Typed search path entry. Mirrors GCC behavior for -I, -isystem, -F, -iframework. + enum class PathKind { Include, SystemInclude, Framework, SystemFramework }; + struct SearchPath { + std::string path; + PathKind kind; + }; + + /** + * Mirrors compiler option -I + * + * If 'legacy' is true, the path is added to the 'includePaths' vector; + * otherwise, it is added to 'searchPaths' with 'PathKind::Include'. + */ + void addIncludePath(const std::string& path, bool legacy=false) { + if (legacy) { + includePaths.push_back(path); + } else { + searchPaths.push_back({path, PathKind::Include}); + } + } + /** Mirrors compiler option -I */ + void addSystemIncludePath(const std::string& path) { + searchPaths.push_back({path, PathKind::SystemInclude}); + } + /** Mirrors compiler option -F */ + void addFrameworkPath(const std::string& path) { + searchPaths.push_back({path, PathKind::Framework}); + } + /** Mirrors compiler option -iframework */ + void addSystemFrameworkPath(const std::string& path) { + searchPaths.push_back({path, PathKind::SystemFramework}); + } + std::list defines; std::set undefined; + + // Back-compat: legacy -I list. If searchPaths is empty at use time, + // consumers should mirror includePaths -> searchPaths as Include. std::list includePaths; + // New: ordered, interleaved search paths with kind. + std::vector searchPaths; + std::list includes; std::string std; bool clearIncludeCache;