aboutsummaryrefslogtreecommitdiff
path: root/automoc
diff options
context:
space:
mode:
authorMatthias Kretz <kretz@kde.org>2007-07-09 15:27:27 +0000
committerMatthias Kretz <kretz@kde.org>2007-07-09 15:27:27 +0000
commitc0980c740022e025fb6c089092f03992ac8a90ba (patch)
treee3cf833e605e44f9dd8324185f63c975e05d8dbf /automoc
parent789a43a8e400806a43b043860ca65d2ff538f8ac (diff)
downloadextra-cmake-modules-c0980c740022e025fb6c089092f03992ac8a90ba.tar.gz
extra-cmake-modules-c0980c740022e025fb6c089092f03992ac8a90ba.tar.bz2
As posted to kde-buildsystem:
I replaced kde4automoc.cmake with a C++/QtCore based program that can run more efficient. Instead of creating a <targetname>.automoc file that is added to the target I create a <targetname>_automoc.cpp file now that is compiled and linked into the target. This file #includes all moc files that are not included by other source files. This way the automoc can, at make-time, decide what mocs need to be compiled explicitly and linked into the target. E.g. the following is possible now: foo.h: class A : public QObject { Q_OBJECT ... }; foo.cpp does not #include "foo.moc" run make - everything compiles and links fine (without mentioning the header in KDE4_MOC_HEADERS either since the new automoc looks at all corresponding header files from the .cpp files by itself) now change foo.cpp to #include "foo.moc" running make now will just work, even with the /fast target. Next change I did was to create a <targetname>_automoc.cpp.files file to pass the moc includes and the source files that belong to the target to the automoc. I could have kept it on the command line but I got a report that the command line was already too long for Windows' cmd.exe. Implementation details: - The messages of the automoc are written using cmake -E cmake_echo_color, so the automoc correctly colorizes its messages now. - The moc QProcesses are started in parallel (up to 10). svn path=/trunk/KDE/kdelibs/; revision=685719
Diffstat (limited to 'automoc')
-rw-r--r--automoc/CMakeLists.txt5
-rw-r--r--automoc/kde4automoc.cpp270
2 files changed, 275 insertions, 0 deletions
diff --git a/automoc/CMakeLists.txt b/automoc/CMakeLists.txt
new file mode 100644
index 00000000..553aa1af
--- /dev/null
+++ b/automoc/CMakeLists.txt
@@ -0,0 +1,5 @@
+include_directories(${QT_INCLUDE_DIR})
+add_executable(kde4automoc kde4automoc.cpp)
+kde4_handle_rpath_for_executable(kde4automoc "RUN_UNINSTALLED")
+target_link_libraries(kde4automoc ${QT_QTCORE_LIBRARY})
+install(TARGETS kde4automoc DESTINATION ${BIN_INSTALL_DIR})
diff --git a/automoc/kde4automoc.cpp b/automoc/kde4automoc.cpp
new file mode 100644
index 00000000..8023a3a9
--- /dev/null
+++ b/automoc/kde4automoc.cpp
@@ -0,0 +1,270 @@
+/* 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 version 2
+ as published by the Free Software Foundation.
+
+ 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();
+ ~AutoMoc();
+ void run();
+
+ private:
+ void generateMoc(const QString &sourceFile, const QString &mocFileName);
+ void usage(const QString &);
+ void echoColor(const QString &msg)
+ {
+ QProcess cmakeEcho;
+ cmakeEcho.setProcessChannelMode(QProcess::ForwardedChannels);
+ QStringList args(cmakeEchoColorArgs);
+ args << msg;
+ cmakeEcho.startDetached("cmake", args);
+ }
+
+ QString bindir;
+ QString mocExe;
+ QStringList mocIncludes;
+ QStringList cmakeEchoColorArgs;
+ const bool verbose;
+ QTextStream cerr;
+ QTextStream cout;
+ QQueue<QProcess *> mocProcesses;
+};
+
+void AutoMoc::usage(const QString &path)
+{
+ cout << "usage: " << path << " <outfile> <srcdir> <bindir> <moc executable>" << endl;
+ ::exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ AutoMoc().run();
+ return 0;
+}
+
+AutoMoc::AutoMoc()
+ : verbose(!QByteArray(getenv("VERBOSE")).isEmpty()), cerr(stderr), cout(stdout)
+{
+ const QByteArray colorEnv = getenv("COLOR");
+ cmakeEchoColorArgs << "-E" << "cmake_echo_color" << QString("--switch=") + colorEnv << "--blue"
+ << "--bold";
+}
+
+void 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 += '/';
+ }
+ bindir = args[3];
+ if (!bindir.endsWith('/')) {
+ bindir += '/';
+ }
+ 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) {
+ 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("[\n]\\s*#\\s*include\\s+[\"<](moc_[^ \">]+\\.cpp|[^ \">]+\\.moc)[\">]");
+ QRegExp qObjectRegExp("[\n]\\s*Q_OBJECT\\b");
+ foreach (const QString &absFilename, sourceFiles) {
+ //qDebug() << absFilename;
+ const QFileInfo absFilenameInfo(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 = absFilenameInfo.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 = absFilenameInfo.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);
+ }
+ }
+ } 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);
+ }
+ const QString sourceFilePath = absPath + basename + ".h";
+ if (!QFile::exists(sourceFilePath)) {
+ cerr << "kde4automoc: The file \"" << absFilename <<
+ "\" includes the moc file \"" << currentMoc << "\", but \"" <<
+ sourceFilePath << "\" does not exist." << endl;
+ ::exit(EXIT_FAILURE);
+ }
+ includedMocs.insert(sourceFilePath, currentMoc);
+ notIncludedMocs.remove(sourceFilePath);
+ } 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_" + absFilenameInfo.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());
+ }
+
+ // source file that includes all remaining moc files
+ outfile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
+ QTextStream outStream(&outfile);
+ outStream << "/* This file is autogenerated, do not edit */\n";
+
+ // 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";
+ }
+ outfile.close();
+}
+
+AutoMoc::~AutoMoc()
+{
+ // let all remaining moc processes finish
+ while (!mocProcesses.isEmpty()) {
+ QProcess *mocProc = mocProcesses.dequeue();
+ if (!mocProc->waitForFinished()) {
+ cerr << "kde4automoc: moc failed: " << mocProc->errorString() << endl;
+ }
+ delete mocProc;
+ }
+}
+
+void AutoMoc::generateMoc(const QString &sourceFile, const QString &mocFileName)
+{
+ //qDebug() << Q_FUNC_INFO << sourceFile << mocFileName;
+ const QString mocFilePath = bindir + mocFileName;
+ if (QFileInfo(mocFilePath).lastModified() < QFileInfo(sourceFile).lastModified()) {
+ if (verbose) {
+ echoColor("Generating " + mocFileName);
+ } else {
+ echoColor("Generating " + mocFilePath + " from " + sourceFile);
+ }
+
+ // we don't want too many child processes
+ if (mocProcesses.size() > 10) {
+ while (!mocProcesses.isEmpty()) {
+ QProcess *mocProc = mocProcesses.dequeue();
+ if (!mocProc->waitForFinished()) {
+ cerr << "kde4automoc: moc failed: " << mocProc->errorString() << endl;
+ }
+ delete mocProc;
+ }
+ }
+
+ QProcess *mocProc = new QProcess;
+ mocProc->setProcessChannelMode(QProcess::ForwardedChannels);
+ QStringList args(mocIncludes);
+ args << "-o" << mocFilePath << sourceFile;
+ //qDebug() << "executing: " << mocExe << args;
+ mocProc->start(mocExe, args, QIODevice::NotOpen);
+ mocProcesses.enqueue(mocProc);
+ }
+}