Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,18 +257,16 @@ 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') }}
uses: actions/upload-artifact@v4
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:
Expand Down
257 changes: 190 additions & 67 deletions generator/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include <QDir>
#include <QFileInfo>
#include <QFile>
#include <QLibraryInfo>
#include <QTextStream>
#include <QRegularExpression>

Expand All @@ -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");
Comment on lines +123 to +127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike a little bit the fact that now we can't generate the bindings for a different Qt version than the version with which the generator was built. Sure, this is a little bit of a corner case, but I know that I have used this at some time during development.

Could we get another override mechanism for the Qt include directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very sensible point. This is an oversight & I will revisit.

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 <qtInclude>/<Module>
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 <lib>/<Module>.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__");
Expand Down Expand Up @@ -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))
{
Expand All @@ -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;
}
};
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -420,6 +540,9 @@ void displayHelp(GeneratorSet* generatorSet) {
" --no-suppress-warnings \n"
" --output-directory=[dir] \n"
" --include-paths=<path>[%c<path>%c...] \n"
#ifdef Q_OS_MACOS
" --framework-paths=<path>[%c<path>%c...] \n"
#endif
" --qt-version=x.y.z \n"
" --print-stdout \n",
path_splitter, path_splitter);
Expand Down
7 changes: 6 additions & 1 deletion generator/simplecpp/README.txt
Original file line number Diff line number Diff line change
@@ -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).

Expand Down
Loading
Loading