diff --git a/cmake/automoc/kde4automoc.cpp b/cmake/automoc/kde4automoc.cpp index da0d545777..4985d74be2 100644 --- a/cmake/automoc/kde4automoc.cpp +++ b/cmake/automoc/kde4automoc.cpp @@ -1,344 +1,344 @@ /* This file is part of the KDE project Copyright (C) 2007 Matthias Kretz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include class AutoMoc { public: AutoMoc(); bool run(); private: void generateMoc(const QString &sourceFile, const QString &mocFileName); void waitForProcesses(); void usage(const QString &); void echoColor(const QString &msg) { QProcess *cmakeEcho = new QProcess; cmakeEcho->setProcessChannelMode(QProcess::ForwardedChannels); QStringList args(cmakeEchoColorArgs); args << msg; cmakeEcho->start("cmake", args, QIODevice::NotOpen); processes.enqueue(Process(cmakeEcho, QString())); } QString builddir; QString mocExe; QStringList mocIncludes; QStringList cmakeEchoColorArgs; const bool verbose; QTextStream cerr; QTextStream cout; struct Process { Process(QProcess *a, const QString &b) : qproc(a), mocFilePath(b) {} QProcess *qproc; QString mocFilePath; }; QQueue processes; bool failed; }; void AutoMoc::usage(const QString &path) { cout << "usage: " << path << " " << endl; ::exit(EXIT_FAILURE); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); if (!AutoMoc().run()) { return EXIT_FAILURE; } return 0; } AutoMoc::AutoMoc() : verbose(!qgetenv("VERBOSE").isEmpty()), cerr(stderr), cout(stdout), failed(false) { const QByteArray colorEnv = qgetenv("COLOR"); cmakeEchoColorArgs << "-E" << "cmake_echo_color" << QString("--switch=") + colorEnv << "--blue" << "--bold"; } bool AutoMoc::run() { const QStringList args = QCoreApplication::arguments(); Q_ASSERT(args.size() > 0); if (args.size() < 4) { usage(args[0]); } QFile outfile(args[1]); const QFileInfo outfileInfo(outfile); QString srcdir(args[2]); if (!srcdir.endsWith('/')) { srcdir += '/'; } builddir = args[3]; if (!builddir.endsWith('/')) { builddir += '/'; } mocExe = args[4]; QFile dotFiles(args[1] + ".files"); dotFiles.open(QIODevice::ReadOnly | QIODevice::Text); QByteArray line = dotFiles.readLine(); Q_ASSERT(line == "MOC_INCLUDES:\n"); line = dotFiles.readLine().trimmed(); const QStringList incPaths = QString::fromUtf8(line).split(';'); foreach (const QString &path, incPaths) { if (!path.isEmpty()) { mocIncludes << "-I" + path; } } line = dotFiles.readLine(); Q_ASSERT(line == "SOURCES:\n"); line = dotFiles.readLine().trimmed(); dotFiles.close(); const QStringList sourceFiles = QString::fromUtf8(line).split(';'); // the program goes through all .cpp files to see which moc files are included. It is not really // interesting how the moc file is named, but what file the moc is created from. Once a moc is // included the same moc may not be included in the _automoc.cpp file anymore. OTOH if there's a // header containing Q_OBJECT where no corresponding moc file is included anywhere a // moc_.cpp file is created and included in the _automoc.cpp file. QHash includedMocs; // key = moc source filepath, value = moc output filepath QHash notIncludedMocs; // key = moc source filepath, value = moc output filename QRegExp mocIncludeRegExp("[\n]\\s*#\\s*include\\s+[\"<](moc_[^ \">]+\\.cpp|[^ \">]+\\.moc)[\">]"); QRegExp qObjectRegExp("[\n]\\s*Q_OBJECT\\b"); QStringList headerExtensions; headerExtensions << ".h" << ".hpp" << ".hxx" << ".H"; foreach (const QString &absFilename, sourceFiles) { //qDebug() << absFilename; const QFileInfo sourceFileInfo(absFilename); if (absFilename.endsWith(".cpp") || absFilename.endsWith(".cc") || absFilename.endsWith(".cxx") || absFilename.endsWith(".C")) { //qDebug() << "check .cpp file"; QFile sourceFile(absFilename); sourceFile.open(QIODevice::ReadOnly); const QByteArray contents = sourceFile.readAll(); if (contents.isEmpty()) { cerr << "kde4automoc: empty source file: " << absFilename << endl; continue; } const QString contentsString = QString::fromUtf8(contents); const QString absPath = sourceFileInfo.absolutePath() + '/'; Q_ASSERT(absPath.endsWith('/')); int matchOffset = mocIncludeRegExp.indexIn(contentsString); if (matchOffset < 0) { // no moc #include, look whether we need to create a moc from the .h nevertheless //qDebug() << "no moc #include in the .cpp file"; const QString basename = sourceFileInfo.completeBaseName(); const QString headername = absPath + basename + ".h"; if (QFile::exists(headername) && !includedMocs.contains(headername) && !notIncludedMocs.contains(headername)) { const QString currentMoc = "moc_" + basename + ".cpp"; QFile header(headername); header.open(QIODevice::ReadOnly); const QByteArray contents = header.readAll(); if (qObjectRegExp.indexIn(QString::fromUtf8(contents)) >= 0) { //qDebug() << "header contains Q_OBJECT macro"; notIncludedMocs.insert(headername, currentMoc); } } const QString privateHeaderName = absPath + basename + "_p.h"; if (QFile::exists(privateHeaderName) && !includedMocs.contains(privateHeaderName) && !notIncludedMocs.contains(privateHeaderName)) { const QString currentMoc = "moc_" + basename + "_p.cpp"; QFile header(privateHeaderName); header.open(QIODevice::ReadOnly); const QByteArray contents = header.readAll(); if (qObjectRegExp.indexIn(QString::fromUtf8(contents)) >= 0) { //qDebug() << "header contains Q_OBJECT macro"; notIncludedMocs.insert(privateHeaderName, currentMoc); } } } else { do { // call this for every moc include in the file const QString currentMoc = mocIncludeRegExp.cap(1); //qDebug() << "found moc include: " << currentMoc << " at offset " << matchOffset; QString basename = QFileInfo(currentMoc).completeBaseName(); const bool moc_style = currentMoc.startsWith("moc_"); if (moc_style || qObjectRegExp.indexIn(contentsString) < 0) { if (moc_style) { basename = basename.right(basename.length() - 4); } bool headerFound = false; foreach (QString ext, headerExtensions) { QString sourceFilePath = absPath + basename + ext; if (QFile::exists(sourceFilePath)) { headerFound = true; includedMocs.insert(sourceFilePath, currentMoc); notIncludedMocs.remove(sourceFilePath); break; } } if (!headerFound) { cerr << "kde4automoc: The file \"" << absFilename << "\" includes the moc file \"" << currentMoc << "\", but \"" << absPath + basename + "{" + headerExtensions.join(",") + "}" << "\" do not exist." << endl; ::exit(EXIT_FAILURE); } } else { includedMocs.insert(absFilename, currentMoc); notIncludedMocs.remove(absFilename); } matchOffset = mocIncludeRegExp.indexIn(contentsString, matchOffset + currentMoc.length()); } while(matchOffset >= 0); } } else if (absFilename.endsWith(".h") || absFilename.endsWith(".hpp") || absFilename.endsWith(".hxx") || absFilename.endsWith(".H")) { if (!includedMocs.contains(absFilename) && !notIncludedMocs.contains(absFilename)) { // if this header is not getting processed yet and is explicitly mentioned for the // automoc the moc is run unconditionally on the header and the resulting file is // included in the _automoc.cpp file (unless there's a .cpp file later on that // includes the moc from this header) const QString currentMoc = "moc_" + sourceFileInfo.completeBaseName() + ".cpp"; notIncludedMocs.insert(absFilename, currentMoc); } } else { if (verbose) { cout << "kde4automoc: ignoring file '" << absFilename << "' with unknown suffix" << endl; } } } // run moc on all the moc's that are #included in source files QHash::ConstIterator end = includedMocs.constEnd(); QHash::ConstIterator it = includedMocs.constBegin(); for (; it != end; ++it) { generateMoc(it.key(), it.value()); } QByteArray automocSource; QTextStream outStream(&automocSource, QIODevice::WriteOnly); outStream << "/* This file is autogenerated, do not edit */\n"; if (notIncludedMocs.isEmpty()) { outStream << "enum some_compilers { need_more_than_nothing };\n"; } else { // run moc on the remaining headers and include them in the _automoc.cpp file end = notIncludedMocs.constEnd(); it = notIncludedMocs.constBegin(); for (; it != end; ++it) { generateMoc(it.key(), it.value()); outStream << "#include \"" << it.value() << "\"\n"; } } // let all remaining moc processes finish waitForProcesses(); if (failed) { // if any moc process failed we don't want to touch the _automoc.cpp file so that // kde4automoc is rerun until the issue is fixed cerr << "returning failed.."<< endl; return false; } outStream.flush(); // source file that includes all remaining moc files outfile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate); outfile.write(automocSource); outfile.close(); return true; } void AutoMoc::waitForProcesses() { while (!processes.isEmpty()) { Process proc = processes.dequeue(); bool result = proc.qproc->waitForFinished(-1); //ignore errors from the cmake echo process if (!proc.mocFilePath.isEmpty()) { if (!result || proc.qproc->exitCode()) { cerr << "kde4automoc: process for " << proc.mocFilePath << " failed: " << proc.qproc->errorString() << endl; cerr << "pid to wait for: " << proc.qproc->pid() << endl; cerr << "processes in queue: " << processes.size() << endl; failed = true; QFile::remove(proc.mocFilePath); } } delete proc.qproc; } } void AutoMoc::generateMoc(const QString &sourceFile, const QString &mocFileName) { //qDebug() << Q_FUNC_INFO << sourceFile << mocFileName; const QString mocFilePath = builddir + mocFileName; if (QFileInfo(mocFilePath).lastModified() < QFileInfo(sourceFile).lastModified()) { if (verbose) { echoColor("Generating " + mocFilePath + " from " + sourceFile); } else { echoColor("Generating " + mocFileName); } // we don't want too many child processes #ifdef Q_OS_FREEBSD - static const int max_processes = 1; + static const int max_processes = 0; #else static const int max_processes = 10; #endif if (processes.size() > max_processes) { waitForProcesses(); } QProcess *mocProc = new QProcess; mocProc->setProcessChannelMode(QProcess::ForwardedChannels); QStringList args(mocIncludes); #ifdef Q_OS_WIN args << "-DWIN32"; #endif args << "-o" << mocFilePath << sourceFile; //qDebug() << "executing: " << mocExe << args; mocProc->start(mocExe, args, QIODevice::NotOpen); if (mocProc->waitForStarted()) processes.enqueue(Process(mocProc, mocFilePath)); else { cerr << "kde4automoc: process for " << mocFilePath << "failed to start: " << mocProc->errorString() << endl; failed = true; delete mocProc; } } }