1 | /* |
2 | Copyright (C) 2007 Matthias Kretz <kretz@kde.org> |
3 | |
4 | Redistribution and use in source and binary forms, with or without |
5 | modification, are permitted provided that the following conditions |
6 | are met: |
7 | |
8 | 1. Redistributions of source code must retain the above copyright |
9 | notice, this list of conditions and the following disclaimer. |
10 | 2. Redistributions in binary form must reproduce the above copyright |
11 | notice, this list of conditions and the following disclaimer in the |
12 | documentation and/or other materials provided with the distribution. |
13 | |
14 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
15 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
16 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
17 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
19 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include <QtCore/QCoreApplication> |
27 | #include <QtCore/QDateTime> |
28 | #include <QtCore/QDir> |
29 | #include <QtCore/QFile> |
30 | #include <QtCore/QFileInfo> |
31 | #include <QtCore/QHash> |
32 | #include <QtCore/QProcess> |
33 | #include <QtCore/QQueue> |
34 | #include <QtCore/QRegExp> |
35 | #include <QtCore/QStringList> |
36 | #include <QtCore/QTextStream> |
37 | #include <QtCore/QtDebug> |
38 | #include <cstdlib> |
39 | #include <sys/types.h> |
40 | #include <time.h> |
41 | #include <errno.h> |
42 | |
43 | #ifdef Q_OS_WIN |
44 | #include <windows.h> |
45 | #include <sys/utime.h> |
46 | #else |
47 | #include <utime.h> |
48 | #endif |
49 | |
50 | #if defined(Q_OS_DARWIN) || defined(Q_OS_MAC) |
51 | #include <unistd.h> |
52 | #endif |
53 | |
54 | // currently this is only used for the version number, Alex |
55 | #include "automoc4_config.h" |
56 | |
57 | class AutoMoc |
58 | { |
59 | public: |
60 | AutoMoc(); |
61 | bool run(); |
62 | |
63 | private: |
64 | void dotFilesCheck(bool); |
65 | void lazyInitMocDefinitions(); |
66 | void lazyInit(); |
67 | bool touch(const QString &filename); |
68 | bool generateMoc(const QString &sourceFile, const QString &mocFileName); |
69 | void printUsage(const QString &); |
70 | void printVersion(); |
71 | void echoColor(const QString &msg) |
72 | { |
73 | QProcess cmakeEcho; |
74 | cmakeEcho.setProcessChannelMode(QProcess::ForwardedChannels); |
75 | QStringList args(cmakeEchoColorArgs); |
76 | args << msg; |
77 | cmakeEcho.start(cmakeExecutable, args, QIODevice::NotOpen); |
78 | cmakeEcho.waitForFinished(-1); |
79 | } |
80 | |
81 | QString builddir; |
82 | QString mocExe; |
83 | QStringList mocIncludes; |
84 | QStringList mocDefinitions; |
85 | QStringList cmakeEchoColorArgs; |
86 | QString cmakeExecutable; |
87 | QFile dotFiles; |
88 | const bool verbose; |
89 | QTextStream cerr; |
90 | QTextStream cout; |
91 | bool failed; |
92 | bool automocCppChanged; |
93 | bool generateAll; |
94 | bool doTouch; |
95 | }; |
96 | |
97 | void AutoMoc::printUsage(const QString &path) |
98 | { |
99 | cout << "Usage: " << path << " <outfile> <srcdir> <builddir> <moc executable> <cmake executable> [--touch]" << endl; |
100 | } |
101 | |
102 | void AutoMoc::printVersion() |
103 | { |
104 | cout << "automoc4 " << AUTOMOC4_VERSION << endl; |
105 | } |
106 | |
107 | void AutoMoc::dotFilesCheck(bool x) |
108 | { |
109 | if (!x) { |
110 | cerr << "Error: syntax error in " << dotFiles.fileName() << endl; |
111 | ::exit(EXIT_FAILURE); |
112 | } |
113 | } |
114 | |
115 | int main(int argc, char **argv) |
116 | { |
117 | QCoreApplication app(argc, argv); |
118 | if (!AutoMoc().run()) { |
119 | return EXIT_FAILURE; |
120 | } |
121 | return 0; |
122 | } |
123 | |
124 | AutoMoc::AutoMoc() |
125 | : verbose(!qgetenv("VERBOSE" ).isEmpty()), cerr(stderr), cout(stdout), failed(false), |
126 | automocCppChanged(false), generateAll(false), doTouch(false) |
127 | { |
128 | const QByteArray colorEnv = qgetenv("COLOR" ); |
129 | cmakeEchoColorArgs << QLatin1String("-E" ) << QLatin1String("cmake_echo_color" ) |
130 | << QLatin1String("--switch=" ) + colorEnv << QLatin1String("--blue" ) |
131 | << QLatin1String("--bold" ); |
132 | } |
133 | |
134 | void AutoMoc::lazyInitMocDefinitions() |
135 | { |
136 | static bool done = false; |
137 | if (done) { |
138 | return; |
139 | } |
140 | done = true; |
141 | QByteArray line = dotFiles.readLine(); |
142 | dotFilesCheck(line == "MOC_COMPILE_DEFINITIONS:\n" ); |
143 | line = dotFiles.readLine().trimmed(); |
144 | const QStringList &cdefList = QString::fromUtf8(line).split(';', QString::SkipEmptyParts); |
145 | line = dotFiles.readLine(); |
146 | dotFilesCheck(line == "MOC_DEFINITIONS:\n" ); |
147 | line = dotFiles.readLine().trimmed(); |
148 | if (!cdefList.isEmpty()) { |
149 | foreach (const QString &def, cdefList) { |
150 | Q_ASSERT(!def.isEmpty()); |
151 | mocDefinitions << QLatin1String("-D" ) + def; |
152 | } |
153 | } else { |
154 | const QStringList &defList = QString::fromUtf8(line).split(' ', QString::SkipEmptyParts); |
155 | foreach (const QString &def, defList) { |
156 | Q_ASSERT(!def.isEmpty()); |
157 | if (def.startsWith(QLatin1String("-D" ))) { |
158 | mocDefinitions << def; |
159 | } |
160 | } |
161 | } |
162 | } |
163 | |
164 | void AutoMoc::lazyInit() |
165 | { |
166 | const QStringList &args = QCoreApplication::arguments(); |
167 | |
168 | mocExe = args[4]; |
169 | cmakeExecutable = args[5]; |
170 | |
171 | if (args.size() > 6) { |
172 | if (args[6] == QLatin1String("--touch" )) { |
173 | doTouch = true; |
174 | } |
175 | } |
176 | |
177 | lazyInitMocDefinitions(); |
178 | |
179 | QByteArray line = dotFiles.readLine(); |
180 | dotFilesCheck(line == "MOC_INCLUDES:\n" ); |
181 | line = dotFiles.readLine().trimmed(); |
182 | const QStringList &incPaths = QString::fromUtf8(line).split(';', QString::SkipEmptyParts); |
183 | QSet<QString> frameworkPaths; |
184 | foreach (const QString &path, incPaths) { |
185 | Q_ASSERT(!path.isEmpty()); |
186 | mocIncludes << "-I" + path; |
187 | if (path.endsWith(QLatin1String(".framework/Headers" ))) { |
188 | QDir framework(path); |
189 | // Go up twice to get to the framework root |
190 | framework.cdUp(); |
191 | framework.cdUp(); |
192 | frameworkPaths << framework.path(); |
193 | } |
194 | } |
195 | |
196 | foreach (const QString &path, frameworkPaths) { |
197 | mocIncludes << "-F" << path; |
198 | } |
199 | |
200 | line = dotFiles.readLine(); |
201 | dotFilesCheck(line == "CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE:\n" ); |
202 | line = dotFiles.readLine(); |
203 | if (line == "ON\n" ) { |
204 | line = dotFiles.readLine(); |
205 | dotFilesCheck(line == "CMAKE_BINARY_DIR:\n" ); |
206 | const QString &binDir = QLatin1String("-I" ) + QString::fromUtf8(dotFiles.readLine().trimmed()); |
207 | |
208 | line = dotFiles.readLine(); |
209 | dotFilesCheck(line == "CMAKE_SOURCE_DIR:\n" ); |
210 | const QString &srcDir = QLatin1String("-I" ) + QString::fromUtf8(dotFiles.readLine().trimmed()); |
211 | |
212 | QStringList sortedMocIncludes; |
213 | QMutableListIterator<QString> it(mocIncludes); |
214 | while (it.hasNext()) { |
215 | if (it.next().startsWith(binDir)) { |
216 | sortedMocIncludes << it.value(); |
217 | it.remove(); |
218 | } |
219 | } |
220 | it.toFront(); |
221 | while (it.hasNext()) { |
222 | if (it.next().startsWith(srcDir)) { |
223 | sortedMocIncludes << it.value(); |
224 | it.remove(); |
225 | } |
226 | } |
227 | sortedMocIncludes += mocIncludes; |
228 | mocIncludes = sortedMocIncludes; |
229 | } |
230 | } |
231 | |
232 | bool AutoMoc::run() |
233 | { |
234 | const QStringList &args = QCoreApplication::arguments(); |
235 | Q_ASSERT(args.size() > 0); |
236 | if (args.size() == 2) { |
237 | if ((args[1]=="--help" ) || (args[1]=="-h" )) { |
238 | printUsage(args[0]); |
239 | ::exit(0); |
240 | } |
241 | else if (args[1]=="--version" ) { |
242 | printVersion(); |
243 | ::exit(0); |
244 | } |
245 | else { |
246 | printUsage(args[0]); |
247 | ::exit(EXIT_FAILURE); |
248 | } |
249 | } |
250 | else if (args.size() < 6) { |
251 | printUsage(args[0]); |
252 | ::exit(EXIT_FAILURE); |
253 | } |
254 | QFile outfile(args[1]); |
255 | const QFileInfo outfileInfo(outfile); |
256 | |
257 | QString srcdir(args[2]); |
258 | if (!srcdir.endsWith('/')) { |
259 | srcdir += '/'; |
260 | } |
261 | builddir = args[3]; |
262 | if (!builddir.endsWith('/')) { |
263 | builddir += '/'; |
264 | } |
265 | |
266 | dotFiles.setFileName(args[1] + QLatin1String(".files" )); |
267 | dotFiles.open(QIODevice::ReadOnly | QIODevice::Text); |
268 | |
269 | const QByteArray &line = dotFiles.readLine(); |
270 | dotFilesCheck(line == "SOURCES:\n" ); |
271 | const QStringList &sourceFiles = QString::fromUtf8(dotFiles.readLine().trimmed()).split(';', QString::SkipEmptyParts); |
272 | |
273 | if (outfile.exists()) { |
274 | // set generateAll = true if MOC_COMPILE_DEFINITIONS changed |
275 | outfile.open(QIODevice::ReadOnly | QIODevice::Text); |
276 | QByteArray buf = outfile.readLine(); |
277 | // the second line contains the joined mocDefinitions |
278 | buf = outfile.readLine(); |
279 | buf.chop(1); // remove trailing \n |
280 | lazyInitMocDefinitions(); |
281 | generateAll = (buf != mocDefinitions.join(QString(QLatin1Char(' '))).toUtf8()); |
282 | outfile.close(); |
283 | } else { |
284 | generateAll = true; |
285 | } |
286 | |
287 | // the program goes through all .cpp files to see which moc files are included. It is not really |
288 | // interesting how the moc file is named, but what file the moc is created from. Once a moc is |
289 | // included the same moc may not be included in the _automoc.cpp file anymore. OTOH if there's a |
290 | // header containing Q_OBJECT where no corresponding moc file is included anywhere a |
291 | // moc_<filename>.cpp file is created and included in the _automoc.cpp file. |
292 | QHash<QString, QString> includedMocs; // key = moc source filepath, value = moc output filepath |
293 | QHash<QString, QString> notIncludedMocs; // key = moc source filepath, value = moc output filename |
294 | |
295 | QRegExp mocIncludeRegExp(QLatin1String("[\n]\\s*#\\s*include\\s+[\"<]((?:[^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]" )); |
296 | QRegExp qObjectRegExp(QLatin1String("[\n]\\s*Q_OBJECT\\b" )); |
297 | QStringList ; |
298 | #if defined(Q_OS_WIN) |
299 | // not case sensitive |
300 | headerExtensions << ".h" << ".hpp" << ".hxx" ; |
301 | #elif defined(Q_OS_DARWIN) || defined(Q_OS_MAC) |
302 | headerExtensions << ".h" << ".hpp" << ".hxx" ; |
303 | |
304 | // detect case-sensitive filesystem |
305 | long caseSensitive = pathconf(srcdir.toLocal8Bit(), _PC_CASE_SENSITIVE); |
306 | if (caseSensitive == 1) { |
307 | headerExtensions << ".H" ; |
308 | } |
309 | #else |
310 | headerExtensions << ".h" << ".hpp" << ".hxx" << ".H" ; |
311 | #endif |
312 | /* not safe: if a moc file is missing it's hard to get it generated if this check is "active" |
313 | const QDateTime &lastRun = QFileInfo(dotFiles).lastModified(); |
314 | if (!generateAll) { |
315 | bool dirty = false; |
316 | foreach (const QString &absFilename, sourceFiles) { |
317 | const QFileInfo sourceFileInfo(absFilename); |
318 | if (sourceFileInfo.lastModified() >= lastRun) { |
319 | dirty = true; |
320 | break; |
321 | } |
322 | const QString &absPathBaseName = sourceFileInfo.absolutePath() + QLatin1Char('/') + sourceFileInfo.completeBaseName(); |
323 | foreach (const QString &ext, headerExtensions) { |
324 | const QFileInfo header(absPathBaseName + ext); |
325 | if (header.exists() && header.lastModified() >= lastRun) { |
326 | dirty = true; |
327 | break; |
328 | } |
329 | const QFileInfo pheader(absPathBaseName + QLatin1String("_p") + ext); |
330 | if (pheader.exists() && pheader.lastModified() >= lastRun) { |
331 | dirty = true; |
332 | break; |
333 | } |
334 | } |
335 | if (dirty) { |
336 | break; |
337 | } |
338 | } |
339 | if (!dirty) { |
340 | return true; |
341 | } |
342 | } |
343 | */ |
344 | |
345 | foreach (const QString &absFilename, sourceFiles) { |
346 | //qDebug() << absFilename; |
347 | const QFileInfo sourceFileInfo(absFilename); |
348 | if (absFilename.endsWith(QLatin1String(".cpp" )) || absFilename.endsWith(QLatin1String(".cc" )) || |
349 | absFilename.endsWith(QLatin1String(".mm" )) || absFilename.endsWith(QLatin1String(".cxx" )) || |
350 | absFilename.endsWith(QLatin1String(".C" ))) { |
351 | //qDebug() << "check .cpp file"; |
352 | QFile sourceFile(absFilename); |
353 | sourceFile.open(QIODevice::ReadOnly); |
354 | const QByteArray contents = sourceFile.readAll(); |
355 | if (contents.isEmpty()) { |
356 | cerr << "automoc4: empty source file: " << absFilename << endl; |
357 | continue; |
358 | } |
359 | const QString contentsString = QString::fromUtf8(contents); |
360 | const QString absPath = sourceFileInfo.absolutePath() + '/'; |
361 | Q_ASSERT(absPath.endsWith('/')); |
362 | int matchOffset = mocIncludeRegExp.indexIn(contentsString); |
363 | if (matchOffset < 0) { |
364 | // no moc #include, look whether we need to create a moc from the .h nevertheless |
365 | //qDebug() << "no moc #include in the .cpp file"; |
366 | const QString basename = sourceFileInfo.completeBaseName(); |
367 | foreach (const QString &ext, headerExtensions) { |
368 | const QString = absPath + basename + ext; |
369 | if (QFile::exists(headername) && !includedMocs.contains(headername) && |
370 | !notIncludedMocs.contains(headername)) { |
371 | const QString currentMoc = "moc_" + basename + ".cpp" ; |
372 | QFile (headername); |
373 | header.open(QIODevice::ReadOnly); |
374 | const QByteArray contents = header.readAll(); |
375 | if (qObjectRegExp.indexIn(QString::fromUtf8(contents)) >= 0) { |
376 | //qDebug() << "header contains Q_OBJECT macro"; |
377 | notIncludedMocs.insert(headername, currentMoc); |
378 | } |
379 | break; |
380 | } |
381 | } |
382 | foreach (const QString &ext, headerExtensions) { |
383 | const QString = absPath + basename + "_p" + ext; |
384 | if (QFile::exists(privateHeaderName) && !includedMocs.contains(privateHeaderName) && |
385 | !notIncludedMocs.contains(privateHeaderName)) { |
386 | const QString currentMoc = "moc_" + basename + "_p.cpp" ; |
387 | QFile (privateHeaderName); |
388 | header.open(QIODevice::ReadOnly); |
389 | const QByteArray contents = header.readAll(); |
390 | if (qObjectRegExp.indexIn(QString::fromUtf8(contents)) >= 0) { |
391 | //qDebug() << "header contains Q_OBJECT macro"; |
392 | notIncludedMocs.insert(privateHeaderName, currentMoc); |
393 | } |
394 | break; |
395 | } |
396 | } |
397 | } else { |
398 | do { // call this for every moc include in the file |
399 | const QString currentMoc = mocIncludeRegExp.cap(1); |
400 | //qDebug() << "found moc include: " << currentMoc << " at offset " << matchOffset; |
401 | const QFileInfo currentMocInfo(currentMoc); |
402 | QString basename = currentMocInfo.completeBaseName(); |
403 | const bool moc_style = basename.startsWith(QLatin1String("moc_" )); |
404 | |
405 | // If the moc include is of the moc_foo.cpp style we expect the Q_OBJECT class |
406 | // declaration in a header file. |
407 | // If the moc include is of the foo.moc style we need to look for a Q_OBJECT |
408 | // macro in the current source file, if it contains the macro we generate the |
409 | // moc file from the source file, else from the header. |
410 | // |
411 | // TODO: currently any .moc file name will be used if the source contains |
412 | // Q_OBJECT |
413 | if (moc_style || qObjectRegExp.indexIn(contentsString) < 0) { |
414 | if (moc_style) { |
415 | // basename should be the part of the moc filename used for finding the |
416 | // correct header, so we need to remove the moc_ part |
417 | basename = basename.right(basename.length() - 4); |
418 | } |
419 | |
420 | bool = false; |
421 | foreach (const QString &ext, headerExtensions) { |
422 | const QString &sourceFilePath = absPath + basename + ext; |
423 | if (QFile::exists(sourceFilePath)) { |
424 | headerFound = true; |
425 | includedMocs.insert(sourceFilePath, currentMoc); |
426 | notIncludedMocs.remove(sourceFilePath); |
427 | break; |
428 | } |
429 | } |
430 | if (!headerFound) { |
431 | // the moc file is in a subdir => look for the header in the same subdir |
432 | if (currentMoc.indexOf('/') != -1) { |
433 | const QString &filepath = absPath + currentMocInfo.path() + QLatin1Char('/') + basename; |
434 | |
435 | foreach (const QString &ext, headerExtensions) { |
436 | const QString &sourceFilePath = filepath + ext; |
437 | if (QFile::exists(sourceFilePath)) { |
438 | headerFound = true; |
439 | includedMocs.insert(sourceFilePath, currentMoc); |
440 | notIncludedMocs.remove(sourceFilePath); |
441 | break; |
442 | } |
443 | } |
444 | if (!headerFound) { |
445 | cerr << "automoc4: The file \"" << absFilename << |
446 | "\" includes the moc file \"" << currentMoc << "\", but neither \"" << |
447 | absPath + basename + '{' + headerExtensions.join("," ) + "}\" nor \"" << |
448 | filepath + '{' + headerExtensions.join("," ) + '}' << |
449 | "\" exist." << endl; |
450 | ::exit(EXIT_FAILURE); |
451 | } |
452 | } else { |
453 | cerr << "automoc4: The file \"" << absFilename << |
454 | "\" includes the moc file \"" << currentMoc << "\", but \"" << |
455 | absPath + basename + '{' + headerExtensions.join("," ) + '}' << |
456 | "\" does not exist." << endl; |
457 | ::exit(EXIT_FAILURE); |
458 | } |
459 | } |
460 | } else { |
461 | includedMocs.insert(absFilename, currentMoc); |
462 | notIncludedMocs.remove(absFilename); |
463 | } |
464 | |
465 | matchOffset = mocIncludeRegExp.indexIn(contentsString, |
466 | matchOffset + currentMoc.length()); |
467 | } while(matchOffset >= 0); |
468 | } |
469 | } else if (absFilename.endsWith(QLatin1String(".h" )) || absFilename.endsWith(QLatin1String(".hpp" )) || |
470 | absFilename.endsWith(QLatin1String(".hxx" )) || absFilename.endsWith(QLatin1String(".H" ))) { |
471 | if (!includedMocs.contains(absFilename) && !notIncludedMocs.contains(absFilename)) { |
472 | // if this header is not getting processed yet and is explicitly mentioned for the |
473 | // automoc the moc is run unconditionally on the header and the resulting file is |
474 | // included in the _automoc.cpp file (unless there's a .cpp file later on that |
475 | // includes the moc from this header) |
476 | const QString currentMoc = "moc_" + sourceFileInfo.completeBaseName() + ".cpp" ; |
477 | notIncludedMocs.insert(absFilename, currentMoc); |
478 | } |
479 | } else { |
480 | if (verbose) { |
481 | cout << "automoc4: ignoring file '" << absFilename << "' with unknown suffix" << endl; |
482 | } |
483 | } |
484 | } |
485 | |
486 | // run moc on all the moc's that are #included in source files |
487 | QHash<QString, QString>::ConstIterator end = includedMocs.constEnd(); |
488 | QHash<QString, QString>::ConstIterator it = includedMocs.constBegin(); |
489 | for (; it != end; ++it) { |
490 | generateMoc(it.key(), it.value()); |
491 | } |
492 | |
493 | QByteArray automocSource; |
494 | QTextStream outStream(&automocSource, QIODevice::WriteOnly); |
495 | outStream << "/* This file is autogenerated, do not edit\n" |
496 | << mocDefinitions.join(QString(QLatin1Char(' '))) << "\n*/\n" ; |
497 | |
498 | if (notIncludedMocs.isEmpty()) { |
499 | outStream << "enum some_compilers { need_more_than_nothing };\n" ; |
500 | } else { |
501 | // run moc on the remaining headers and include them in the _automoc.cpp file |
502 | end = notIncludedMocs.constEnd(); |
503 | it = notIncludedMocs.constBegin(); |
504 | for (; it != end; ++it) { |
505 | if (generateMoc(it.key(), it.value())) { |
506 | automocCppChanged = true; |
507 | } |
508 | outStream << "#include \"" << it.value() << "\"\n" ; |
509 | } |
510 | } |
511 | |
512 | if (failed) { |
513 | // if any moc process failed we don't want to touch the _automoc.cpp file so that |
514 | // automoc4 is rerun until the issue is fixed |
515 | cerr << "returning failed.." << endl; |
516 | return false; |
517 | } |
518 | outStream.flush(); |
519 | |
520 | if (!automocCppChanged) { |
521 | // compare contents of the _automoc.cpp file |
522 | outfile.open(QIODevice::ReadOnly | QIODevice::Text); |
523 | const QByteArray oldContents = outfile.readAll(); |
524 | outfile.close(); |
525 | if (oldContents == automocSource) { |
526 | // nothing changed: don't touch the _automoc.cpp file |
527 | return true; |
528 | } |
529 | } |
530 | // either the contents of the _automoc.cpp file or one of the mocs included by it have changed |
531 | |
532 | // source file that includes all remaining moc files (_automoc.cpp file) |
533 | outfile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate); |
534 | outfile.write(automocSource); |
535 | outfile.close(); |
536 | |
537 | // update the timestamp on the _automoc.cpp.files file to make sure we get called again |
538 | dotFiles.close(); |
539 | if (doTouch && !touch(dotFiles.fileName())) { |
540 | return false; |
541 | } |
542 | |
543 | return true; |
544 | } |
545 | |
546 | bool AutoMoc::touch(const QString &_filename) |
547 | { |
548 | // sleep for 1s in order to make the modification time greater than the modification time of |
549 | // the files written before. Equal modification time is not good enough. Just using utime with |
550 | // time(NULL) + 1 is also not a good solution as then make will complain about clock skew. |
551 | #ifdef Q_OS_WIN |
552 | Sleep(1000); |
553 | _wutime(reinterpret_cast<const wchar_t *>(_filename.utf16()), 0); |
554 | #else |
555 | const QByteArray &filename = QFile::encodeName(_filename); |
556 | const struct timespec sleepDuration = { 1, 0 }; |
557 | nanosleep(&sleepDuration, NULL); |
558 | |
559 | int err = utime(filename.constData(), NULL); |
560 | if (err == -1) { |
561 | err = errno; |
562 | cerr << strerror(err) << "\n" ; |
563 | return false; |
564 | } |
565 | #endif |
566 | return true; |
567 | } |
568 | |
569 | bool AutoMoc::generateMoc(const QString &sourceFile, const QString &mocFileName) |
570 | { |
571 | //qDebug() << Q_FUNC_INFO << sourceFile << mocFileName; |
572 | const QString mocFilePath = builddir + mocFileName; |
573 | QFileInfo mocInfo(mocFilePath); |
574 | if (generateAll || mocInfo.lastModified() <= QFileInfo(sourceFile).lastModified()) { |
575 | QDir mocDir = mocInfo.dir(); |
576 | // make sure the directory for the resulting moc file exists |
577 | if (!mocDir.exists()) { |
578 | mocDir.mkpath(mocDir.path()); |
579 | } |
580 | |
581 | static bool initialized = false; |
582 | if (!initialized) { |
583 | initialized = true; |
584 | lazyInit(); |
585 | } |
586 | if (verbose) { |
587 | echoColor("Generating " + mocFilePath + " from " + sourceFile); |
588 | } else { |
589 | echoColor("Generating " + mocFileName); |
590 | } |
591 | |
592 | QProcess mocProc; |
593 | mocProc.setProcessChannelMode(QProcess::ForwardedChannels); |
594 | QStringList args(mocIncludes + mocDefinitions); |
595 | #ifdef Q_OS_WIN |
596 | args << "-DWIN32" ; |
597 | #endif |
598 | args << QLatin1String("-o" ) << mocFilePath << sourceFile; |
599 | //qDebug() << "executing: " << mocExe << args; |
600 | if (verbose) { |
601 | cout << mocExe << " " << args.join(QLatin1String(" " )) << endl; |
602 | } |
603 | mocProc.start(mocExe, args, QIODevice::NotOpen); |
604 | if (mocProc.waitForStarted()) { |
605 | const bool result = mocProc.waitForFinished(-1); |
606 | if (!result || mocProc.exitCode()) { |
607 | cerr << "automoc4: process for " << mocFilePath |
608 | << " failed: " << mocProc.errorString() << endl; |
609 | cerr << "pid to wait for: " << mocProc.pid() << endl; |
610 | failed = true; |
611 | QFile::remove(mocFilePath); |
612 | } |
613 | return true; |
614 | } else { |
615 | cerr << "automoc4: process for " << mocFilePath << "failed to start: " |
616 | << mocProc.errorString() << endl; |
617 | failed = true; |
618 | } |
619 | } |
620 | return false; |
621 | } |
622 | |