aboutsummaryrefslogtreecommitdiff
path: root/automoc/kde4automoc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'automoc/kde4automoc.cpp')
-rw-r--r--automoc/kde4automoc.cpp345
1 files changed, 345 insertions, 0 deletions
diff --git a/automoc/kde4automoc.cpp b/automoc/kde4automoc.cpp
new file mode 100644
index 00000000..68ccdbdb
--- /dev/null
+++ b/automoc/kde4automoc.cpp
@@ -0,0 +1,345 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 Matthias Kretz <kretz@kde.org>
+
+ 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 <QtCore/QCoreApplication>
+#include <QtCore/QDateTime>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QHash>
+#include <QtCore/QProcess>
+#include <QtCore/QQueue>
+#include <QtCore/QRegExp>
+#include <QtCore/QStringList>
+#include <QtCore/QTextStream>
+#include <QtCore/QtDebug>
+#include <cstdlib>
+
+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(QLatin1String("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<Process> processes;
+ bool failed;
+};
+
+void AutoMoc::usage(const QString &path)
+{
+ cout << "usage: " << path << " <outfile> <srcdir> <builddir> <moc executable>" << 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 << QLatin1String("-E") << QLatin1String("cmake_echo_color")
+ << QLatin1String("--switch=") + colorEnv << QLatin1String("--blue")
+ << QLatin1String("--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_<filename>.cpp file is created and included in the _automoc.cpp file.
+ QHash<QString, QString> includedMocs; // key = moc source filepath, value = moc output filepath
+ QHash<QString, QString> notIncludedMocs; // key = moc source filepath, value = moc output filename
+
+ QRegExp mocIncludeRegExp(QLatin1String("[\n]\\s*#\\s*include\\s+[\"<](moc_[^ \">]+\\.cpp|[^ \">]+\\.moc)[\">]"));
+ QRegExp qObjectRegExp(QLatin1String("[\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 (const 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<QString, QString>::ConstIterator end = includedMocs.constEnd();
+ QHash<QString, QString>::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 = 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 << QLatin1String("-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;
+ }
+ }
+}