cmQtAutoGenerators.cxx 67.4 KB
Newer Older
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
Brad King's avatar
Brad King committed
3
#include "cmQtAutoGenerators.h"
4
#include "cmQtAutoGeneratorCommon.h"
Brad King's avatar
Brad King committed
5

6
7
8
#include "cmConfigure.h"
#include "cmsys/FStream.hxx"
#include "cmsys/Terminal.h"
9
#include <algorithm>
10
#include <assert.h>
11
#include <list>
12
13
#include <sstream>
#include <stdlib.h>
14
#include <string.h>
15
16
#include <utility>

17
#include "cmAlgorithms.h"
18
#include "cmCryptoHash.h"
19
#include "cmFilePathChecksum.h"
20
21
22
23
24
25
26
27
28
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmOutputConverter.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
#include "cmSystemTools.h"
#include "cm_auto_ptr.hxx"
#include "cmake.h"

29
30
31
#if defined(__APPLE__)
#include <unistd.h>
#endif
32

33
34
// -- Static variables

35
36
37
static const char* SettingsKeyMoc = "AM_MOC_SETTINGS_HASH";
static const char* SettingsKeyUic = "AM_UIC_SETTINGS_HASH";
static const char* SettingsKeyRcc = "AM_RCC_SETTINGS_HASH";
38

39
40
// -- Static functions

41
inline static std::string Quoted(const std::string& text)
42
{
43
  return cmQtAutoGeneratorCommon::Quoted(text);
44
45
}

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static std::string QuotedCommand(const std::vector<std::string>& command)
{
  std::string res;
  for (std::vector<std::string>::const_iterator cit = command.begin();
       cit != command.end(); ++cit) {
    if (!res.empty()) {
      res.push_back(' ');
    }
    const std::string cesc = Quoted(*cit);
    if (cit->empty() || (cesc.size() > (cit->size() + 2)) ||
        (cesc.find(' ') != std::string::npos)) {
      res += cesc;
    } else {
      res += *cit;
    }
  }
  return res;
}

65
static void InfoGet(cmMakefile* makefile, const char* key, std::string& value)
66
{
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  value = makefile->GetSafeDefinition(key);
}

static void InfoGet(cmMakefile* makefile, const char* key, bool& value)
{
  value = makefile->IsOn(key);
}

static void InfoGet(cmMakefile* makefile, const char* key,
                    std::vector<std::string>& list)
{
  cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
}

81
82
static void InfoGetConfig(cmMakefile* makefile, const char* key,
                          const std::string& config, std::string& value)
83
84
85
86
87
88
89
90
91
{
  const char* valueConf = CM_NULLPTR;
  {
    std::string keyConf = key;
    if (!config.empty()) {
      keyConf += "_";
      keyConf += config;
    }
    valueConf = makefile->GetDefinition(keyConf);
92
  }
93
94
  if (valueConf == CM_NULLPTR) {
    valueConf = makefile->GetSafeDefinition(key);
95
  }
96
97
98
99
100
101
102
103
104
105
  value = valueConf;
}

static void InfoGetConfig(cmMakefile* makefile, const char* key,
                          const std::string& config,
                          std::vector<std::string>& list)
{
  std::string value;
  InfoGetConfig(makefile, key, config, value);
  cmSystemTools::ExpandListArgument(value, list);
106
107
}

108
109
inline static bool SettingsMatch(cmMakefile* makefile, const char* key,
                                 const std::string& value)
110
{
111
  return (value == makefile->GetSafeDefinition(key));
112
113
}

114
115
static void SettingAppend(std::string& str, const char* key,
                          const std::string& value)
116
{
117
  if (!value.empty()) {
118
119
120
121
122
    str += "set(";
    str += key;
    str += " ";
    str += cmOutputConverter::EscapeForCMake(value);
    str += ")\n";
123
  }
124
125
}

126
static std::string SubDirPrefix(const std::string& fileName)
127
128
129
130
131
132
133
134
{
  std::string res(cmsys::SystemTools::GetFilenamePath(fileName));
  if (!res.empty()) {
    res += '/';
  }
  return res;
}

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
static bool FileNameIsUnique(const std::string& filePath,
                             const std::map<std::string, std::string>& fileMap)
{
  size_t count(0);
  const std::string fileName = cmsys::SystemTools::GetFilenameName(filePath);
  for (std::map<std::string, std::string>::const_iterator si = fileMap.begin();
       si != fileMap.end(); ++si) {
    if (cmsys::SystemTools::GetFilenameName(si->first) == fileName) {
      ++count;
      if (count > 1) {
        return false;
      }
    }
  }
  return true;
}

152
static bool ReadAll(std::string& content, const std::string& filename)
153
{
154
155
156
157
158
159
160
161
162
163
164
  bool success = false;
  {
    cmsys::ifstream ifs(filename.c_str());
    if (ifs) {
      std::ostringstream osst;
      osst << ifs.rdbuf();
      content = osst.str();
      success = true;
    }
  }
  return success;
165
166
}

167
168
169
170
171
172
173
174
175
176
177
178
179
/**
 * @brief Tests if buildFile doesn't exist or is older than sourceFile
 * @return True if buildFile doesn't exist or is older than sourceFile
 */
static bool FileAbsentOrOlder(const std::string& buildFile,
                              const std::string& sourceFile)
{
  int result = 0;
  bool success =
    cmsys::SystemTools::FileTimeCompare(buildFile, sourceFile, &result);
  return (!success || (result <= 0));
}

180
181
182
183
184
185
static bool ListContains(const std::vector<std::string>& list,
                         const std::string& entry)
{
  return (std::find(list.begin(), list.end(), entry) != list.end());
}

186
187
188
189
190
191
192
static std::string JoinOptionsList(const std::vector<std::string>& opts)
{
  return cmOutputConverter::EscapeForCMake(cmJoin(opts, ";"));
}

static std::string JoinOptionsMap(
  const std::map<std::string, std::string>& opts)
193
194
195
196
197
{
  std::string result;
  for (std::map<std::string, std::string>::const_iterator it = opts.begin();
       it != opts.end(); ++it) {
    if (it != opts.begin()) {
198
      result += cmQtAutoGeneratorCommon::listSep;
199
200
201
202
203
204
205
206
    }
    result += it->first;
    result += "===";
    result += it->second;
  }
  return result;
}

207
208
209
static std::string JoinExts(const std::vector<std::string>& lst)
{
  std::string result;
210
211
212
213
214
215
216
217
218
  if (!lst.empty()) {
    const std::string separator = ",";
    for (std::vector<std::string>::const_iterator it = lst.begin();
         it != lst.end(); ++it) {
      if (it != lst.begin()) {
        result += separator;
      }
      result += '.';
      result += *it;
219
220
221
222
223
    }
  }
  return result;
}

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
static void UicMergeOptions(std::vector<std::string>& opts,
                            const std::vector<std::string>& fileOpts,
                            bool isQt5)
{
  static const char* valueOptions[] = { "tr",      "translate",
                                        "postfix", "generator",
                                        "include", // Since Qt 5.3
                                        "g" };
  std::vector<std::string> extraOpts;
  for (std::vector<std::string>::const_iterator it = fileOpts.begin();
       it != fileOpts.end(); ++it) {
    std::vector<std::string>::iterator existingIt =
      std::find(opts.begin(), opts.end(), *it);
    if (existingIt != opts.end()) {
      const char* o = it->c_str();
      if (*o == '-') {
        ++o;
      }
      if (isQt5 && *o == '-') {
        ++o;
      }
      if (std::find_if(cmArrayBegin(valueOptions), cmArrayEnd(valueOptions),
                       cmStrCmp(*it)) != cmArrayEnd(valueOptions)) {
        assert(existingIt + 1 != opts.end());
        *(existingIt + 1) = *(it + 1);
        ++it;
      }
    } else {
      extraOpts.push_back(*it);
    }
  }
  opts.insert(opts.end(), extraOpts.begin(), extraOpts.end());
}

258
259
// -- Class methods

260
cmQtAutoGenerators::cmQtAutoGenerators()
261
  : Verbose(cmsys::SystemTools::HasEnv("VERBOSE"))
262
  , ColorOutput(true)
263
  , MocSettingsChanged(false)
264
  , MocPredefsChanged(false)
265
266
267
268
269
  , MocRunFailed(false)
  , UicSettingsChanged(false)
  , UicRunFailed(false)
  , RccSettingsChanged(false)
  , RccRunFailed(false)
270
{
271

272
  std::string colorEnv;
273
  cmsys::SystemTools::GetEnv("COLOR", colorEnv);
274
275
  if (!colorEnv.empty()) {
    if (cmSystemTools::IsOn(colorEnv.c_str())) {
276
      this->ColorOutput = true;
277
    } else {
278
279
      this->ColorOutput = false;
    }
280
  }
281

282
  // Moc macro filters
283
284
285
286
  this->MocMacroFilters[0].first = "Q_OBJECT";
  this->MocMacroFilters[0].second.compile("[\n][ \t]*Q_OBJECT[^a-zA-Z0-9_]");
  this->MocMacroFilters[1].first = "Q_GADGET";
  this->MocMacroFilters[1].second.compile("[\n][ \t]*Q_GADGET[^a-zA-Z0-9_]");
287

288
  // Precompile regular expressions
289
  this->MocRegExpInclude.compile(
290
291
    "[\n][ \t]*#[ \t]*include[ \t]+"
    "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
292
  this->UicRegExpInclude.compile("[\n][ \t]*#[ \t]*include[ \t]+"
293
                                 "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
294
295
}

296
297
bool cmQtAutoGenerators::Run(const std::string& targetDirectory,
                             const std::string& config)
298
{
299
  cmake cm(cmake::RoleScript);
300
301
  cm.SetHomeOutputDirectory(targetDirectory);
  cm.SetHomeDirectory(targetDirectory);
302
  cm.GetCurrentSnapshot().SetDefaultDefinitions();
303
  cmGlobalGenerator gg(&cm);
304

305
  cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
306
307
308
  snapshot.GetDirectory().SetCurrentBinary(targetDirectory);
  snapshot.GetDirectory().SetCurrentSource(targetDirectory);

309
  CM_AUTO_PTR<cmMakefile> mf(new cmMakefile(&gg, snapshot));
310
  gg.SetCurrentMakefile(mf.get());
311

312
313
314
  bool success = false;
  if (this->ReadAutogenInfoFile(mf.get(), targetDirectory, config)) {
    // Read old settings
315
    this->SettingsFileRead(mf.get());
316
317
318
319
    // Init and run
    this->Init(mf.get());
    if (this->RunAutogen()) {
      // Write current settings
320
      if (this->SettingsFileWrite()) {
321
322
        success = true;
      }
323
    }
324
  }
325
  return success;
326
327
}

328
329
330
331
332
333
334
335
336
337
338
339
340
bool cmQtAutoGenerators::MocDependFilterPush(const std::string& key,
                                             const std::string& regExp)
{
  bool success = false;
  if (!key.empty()) {
    if (!regExp.empty()) {
      MocDependFilter filter;
      filter.key = key;
      if (filter.regExp.compile(regExp)) {
        this->MocDependFilters.push_back(filter);
        success = true;
      } else {
        this->LogError("AutoMoc: Error in AUTOMOC_DEPEND_FILTERS: Compiling "
341
342
                       "regular expression failed.\nKey:  " +
                       Quoted(key) + "\nExp.: " + Quoted(regExp));
343
344
345
346
347
348
349
350
351
352
353
      }
    } else {
      this->LogError("AutoMoc: Error in AUTOMOC_DEPEND_FILTERS: Regular "
                     "expression is empty");
    }
  } else {
    this->LogError("AutoMoc: Error in AUTOMOC_DEPEND_FILTERS: Key is empty");
  }
  return success;
}

354
355
356
bool cmQtAutoGenerators::ReadAutogenInfoFile(
  cmMakefile* makefile, const std::string& targetDirectory,
  const std::string& config)
357
{
358
  std::string filename(cmSystemTools::CollapseFullPath(targetDirectory));
359
  cmSystemTools::ConvertToUnixSlashes(filename);
360
  filename += "/AutogenInfo.cmake";
361

362
  if (!makefile->ReadListFile(filename.c_str())) {
363
    this->LogError("AutoGen: Error processing file: " + filename);
364
    return false;
365
  }
366

367
368
369
370
  // - Old settings file
  {
    this->SettingsFile = cmSystemTools::CollapseFullPath(targetDirectory);
    cmSystemTools::ConvertToUnixSlashes(this->SettingsFile);
371
372
373
    this->SettingsFile += "/AutogenOldSettings";
    this->SettingsFile += this->ConfigSuffix;
    this->SettingsFile += ".cmake";
374
375
  }

376
377
  // -- Meta
  InfoGetConfig(makefile, "AM_CONFIG_SUFFIX", config, this->ConfigSuffix);
378

379
  // - Files and directories
380
381
382
383
384
385
  InfoGet(makefile, "AM_CMAKE_SOURCE_DIR", this->ProjectSourceDir);
  InfoGet(makefile, "AM_CMAKE_BINARY_DIR", this->ProjectBinaryDir);
  InfoGet(makefile, "AM_CMAKE_CURRENT_SOURCE_DIR", this->CurrentSourceDir);
  InfoGet(makefile, "AM_CMAKE_CURRENT_BINARY_DIR", this->CurrentBinaryDir);
  InfoGet(makefile, "AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE",
          this->IncludeProjectDirsBefore);
386
  InfoGet(makefile, "AM_BUILD_DIR", this->AutogenBuildDir);
387
388
389
390
  if (this->AutogenBuildDir.empty()) {
    this->LogError("AutoGen: Error: Missing autogen build directory ");
    return false;
  }
391
392
  InfoGet(makefile, "AM_SOURCES", this->Sources);
  InfoGet(makefile, "AM_HEADERS", this->Headers);
393

394
  // - Qt environment
395
396
397
  InfoGet(makefile, "AM_QT_VERSION_MAJOR", this->QtMajorVersion);
  if (this->QtMajorVersion.empty()) {
    InfoGet(makefile, "AM_Qt5Core_VERSION_MAJOR", this->QtMajorVersion);
398
  }
399
400
401
  InfoGet(makefile, "AM_QT_MOC_EXECUTABLE", this->MocExecutable);
  InfoGet(makefile, "AM_QT_UIC_EXECUTABLE", this->UicExecutable);
  InfoGet(makefile, "AM_QT_RCC_EXECUTABLE", this->RccExecutable);
402
403

  InfoGet(makefile, "AM_MOC_PREDEFS_CMD", this->MocPredefsCmd);
404
405
  // Check Qt version
  if ((this->QtMajorVersion != "4") && (this->QtMajorVersion != "5")) {
406
407
    this->LogError("AutoGen: Error: Unsupported Qt version: " +
                   Quoted(this->QtMajorVersion));
408
409
    return false;
  }
410
411

  // - Moc
412
  if (this->MocEnabled()) {
413
    InfoGet(makefile, "AM_MOC_SKIP", this->MocSkipList);
414
415
    InfoGetConfig(makefile, "AM_MOC_DEFINITIONS", config,
                  this->MocDefinitions);
416
417
418
419
420
421
422
423
#ifdef _WIN32
    {
      const std::string win32("WIN32");
      if (!ListContains(this->MocDefinitions, win32)) {
        this->MocDefinitions.push_back(win32);
      }
    }
#endif
424
    InfoGetConfig(makefile, "AM_MOC_INCLUDES", config, this->MocIncludePaths);
425
426
    InfoGet(makefile, "AM_MOC_OPTIONS", this->MocOptions);
    InfoGet(makefile, "AM_MOC_RELAXED_MODE", this->MocRelaxedMode);
427
428
    {
      std::vector<std::string> mocDependFilters;
429
      InfoGet(makefile, "AM_MOC_DEPEND_FILTERS", mocDependFilters);
430
431
432
433
434
435
436
437
438
439
440
441
442
443
      // Insert Q_PLUGIN_METADATA dependency filter
      if (this->QtMajorVersion != "4") {
        this->MocDependFilterPush("Q_PLUGIN_METADATA",
                                  "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
                                  "[^\\)]*FILE[ \t]*\"([^\"]+)\"");
      }
      // Insert user defined dependency filters
      if ((mocDependFilters.size() % 2) == 0) {
        for (std::vector<std::string>::const_iterator dit =
               mocDependFilters.begin();
             dit != mocDependFilters.end(); dit += 2) {
          if (!this->MocDependFilterPush(*dit, *(dit + 1))) {
            return false;
          }
444
        }
445
446
447
      } else {
        this->LogError(
          "AutoMoc: Error: AUTOMOC_DEPEND_FILTERS list size is not "
448
449
          "a multiple of 2 in:\n" +
          Quoted(filename));
450
        return false;
451
452
453
      }
    }
  }
454

455
  // - Uic
456
  if (this->UicEnabled()) {
457
458
    InfoGet(makefile, "AM_UIC_SKIP", this->UicSkipList);
    InfoGet(makefile, "AM_UIC_SEARCH_PATHS", this->UicSearchPaths);
459
460
    InfoGetConfig(makefile, "AM_UIC_TARGET_OPTIONS", config,
                  this->UicTargetOptions);
461
462
463
    {
      std::vector<std::string> uicFilesVec;
      std::vector<std::string> uicOptionsVec;
464
465
      InfoGet(makefile, "AM_UIC_OPTIONS_FILES", uicFilesVec);
      InfoGet(makefile, "AM_UIC_OPTIONS_OPTIONS", uicOptionsVec);
466
467
468
469
470
471
      // Compare list sizes
      if (uicFilesVec.size() == uicOptionsVec.size()) {
        for (std::vector<std::string>::iterator
               fileIt = uicFilesVec.begin(),
               optionIt = uicOptionsVec.begin();
             fileIt != uicFilesVec.end(); ++fileIt, ++optionIt) {
472
473
          cmSystemTools::ReplaceString(*optionIt,
                                       cmQtAutoGeneratorCommon::listSep, ";");
474
475
476
477
          this->UicOptions[*fileIt] = *optionIt;
        }
      } else {
        this->LogError(
478
479
          "AutoGen: Error: Uic files/options lists size missmatch in:\n" +
          Quoted(filename));
480
        return false;
481
      }
482
483
    }
  }
484
485

  // - Rcc
486
  if (this->RccEnabled()) {
487
    InfoGet(makefile, "AM_RCC_SOURCES", this->RccSources);
488
    // File options
489
490
491
    {
      std::vector<std::string> rccFilesVec;
      std::vector<std::string> rccOptionsVec;
492
493
      InfoGet(makefile, "AM_RCC_OPTIONS_FILES", rccFilesVec);
      InfoGet(makefile, "AM_RCC_OPTIONS_OPTIONS", rccOptionsVec);
494
      if (rccFilesVec.size() == rccOptionsVec.size()) {
495
496
497
498
499
500
501
502
503
504
        for (std::vector<std::string>::iterator
               fileIt = rccFilesVec.begin(),
               optionIt = rccOptionsVec.begin();
             fileIt != rccFilesVec.end(); ++fileIt, ++optionIt) {
          // Replace item separator
          cmSystemTools::ReplaceString(*optionIt,
                                       cmQtAutoGeneratorCommon::listSep, ";");
          this->RccOptions[*fileIt] = *optionIt;
        }
      } else {
505
        this->LogError(
506
507
          "AutoGen: Error: RCC files/options lists size missmatch in:\n" +
          Quoted(filename));
508
509
        return false;
      }
510
    }
511
    // File lists
512
513
    {
      std::vector<std::string> rccInputLists;
514
      InfoGet(makefile, "AM_RCC_INPUTS", rccInputLists);
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
      if (this->RccSources.size() == rccInputLists.size()) {
        for (std::vector<std::string>::iterator
               fileIt = this->RccSources.begin(),
               inputIt = rccInputLists.begin();
             fileIt != this->RccSources.end(); ++fileIt, ++inputIt) {
          // Remove braces
          *inputIt = inputIt->substr(1, inputIt->size() - 2);
          // Replace item separator
          cmSystemTools::ReplaceString(*inputIt,
                                       cmQtAutoGeneratorCommon::listSep, ";");
          std::vector<std::string> rccInputFiles;
          cmSystemTools::ExpandListArgument(*inputIt, rccInputFiles);
          this->RccInputs[*fileIt] = rccInputFiles;
        }
      } else {
530
        this->LogError(
531
532
          "AutoGen: Error: RCC sources/inputs lists size missmatch in:\n" +
          Quoted(filename));
533
534
        return false;
      }
535
    }
536
  }
537

538
539
540
  return true;
}

541
void cmQtAutoGenerators::SettingsFileRead(cmMakefile* makefile)
542
{
543
  // Compose current settings strings
544
545
  {
    cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
546
    const std::string sep(" ~~~ ");
547
548
    if (this->MocEnabled()) {
      std::string str;
549
550
      str += this->MocExecutable;
      str += sep;
551
      str += JoinOptionsList(this->MocDefinitions);
552
      str += sep;
553
      str += JoinOptionsList(this->MocIncludePaths);
554
      str += sep;
555
      str += JoinOptionsList(this->MocOptions);
556
      str += sep;
557
      str += this->IncludeProjectDirsBefore ? "TRUE" : "FALSE";
558
      str += sep;
559
      str += JoinOptionsList(this->MocPredefsCmd);
560
      str += sep;
561
562
563
564
      this->SettingsStringMoc = crypt.HashString(str);
    }
    if (this->UicEnabled()) {
      std::string str;
565
566
      str += this->UicExecutable;
      str += sep;
567
      str += JoinOptionsList(this->UicTargetOptions);
568
      str += sep;
569
      str += JoinOptionsMap(this->UicOptions);
570
      str += sep;
571
572
573
574
      this->SettingsStringUic = crypt.HashString(str);
    }
    if (this->RccEnabled()) {
      std::string str;
575
576
      str += this->RccExecutable;
      str += sep;
577
      str += JoinOptionsMap(this->RccOptions);
578
      str += sep;
579
580
      this->SettingsStringRcc = crypt.HashString(str);
    }
581
  }
582

583
  // Read old settings
584
  if (makefile->ReadListFile(this->SettingsFile.c_str())) {
585
    if (!SettingsMatch(makefile, SettingsKeyMoc, this->SettingsStringMoc)) {
586
      this->MocSettingsChanged = true;
587
    }
588
    if (!SettingsMatch(makefile, SettingsKeyUic, this->SettingsStringUic)) {
589
      this->UicSettingsChanged = true;
590
    }
591
    if (!SettingsMatch(makefile, SettingsKeyRcc, this->SettingsStringRcc)) {
592
      this->RccSettingsChanged = true;
593
    }
594
595
596
    // In case any setting changed remove the old settings file.
    // This triggers a full rebuild on the next run if the current
    // build is aborted before writing the current settings in the end.
597
    if (this->AnySettingsChanged()) {
598
      cmSystemTools::RemoveFile(this->SettingsFile);
599
600
601
    }
  } else {
    // If the file could not be read re-generate everythiung.
602
603
604
    this->MocSettingsChanged = true;
    this->UicSettingsChanged = true;
    this->RccSettingsChanged = true;
605
  }
606
607
}

608
bool cmQtAutoGenerators::SettingsFileWrite()
609
{
610
  bool success = true;
611
  // Only write if any setting changed
612
  if (this->AnySettingsChanged()) {
613
    if (this->Verbose) {
614
615
      this->LogInfo("AutoGen: Writing settings file " +
                    Quoted(this->SettingsFile));
616
    }
617
618
619
620
621
622
623
624
625
626
    // Compose settings file content
    std::string settings;
    SettingAppend(settings, SettingsKeyMoc, this->SettingsStringMoc);
    SettingAppend(settings, SettingsKeyUic, this->SettingsStringUic);
    SettingAppend(settings, SettingsKeyRcc, this->SettingsStringRcc);
    // Write settings file
    if (!this->FileWrite("AutoGen", this->SettingsFile, settings)) {
      this->LogError("AutoGen: Error: Could not write old settings file " +
                     Quoted(this->SettingsFile));
      // Remove old settings file to trigger a full rebuild on the next run
627
      cmSystemTools::RemoveFile(this->SettingsFile);
628
      success = false;
629
630
631
    }
  }
  return success;
632
633
}

634
void cmQtAutoGenerators::Init(cmMakefile* makefile)
635
{
636
637
638
639
640
641
642
643
644
645
646
  // Mocs compilation file
  this->MocCompFileRel = "mocs_compilation";
  this->MocCompFileRel += this->ConfigSuffix;
  this->MocCompFileRel += ".cpp";
  this->MocCompFileAbs = cmSystemTools::CollapseCombinedPath(
    this->AutogenBuildDir, this->MocCompFileRel);

  // Mocs include directory
  this->AutogenIncludeDir = "include";
  this->AutogenIncludeDir += this->ConfigSuffix;
  this->AutogenIncludeDir += "/";
647

648
649
  // Moc predefs file
  if (!this->MocPredefsCmd.empty()) {
650
651
652
    this->MocPredefsFileRel = "moc_predefs.h";
    this->MocPredefsFileAbs = cmSystemTools::CollapseCombinedPath(
      this->AutogenBuildDir, this->MocPredefsFileRel);
653
654
  }

655
  // Init file path checksum generator
656
  FPathChecksum.setupParentDirs(this->CurrentSourceDir, this->CurrentBinaryDir,
657
658
659
                                this->ProjectSourceDir,
                                this->ProjectBinaryDir);

660
661
  // Acquire header extensions
  this->HeaderExtensions = makefile->GetCMakeInstance()->GetHeaderExtensions();
662

663
  // Sort include directories on demand
664
  if (this->IncludeProjectDirsBefore) {
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
    // Move strings to temporary list
    std::list<std::string> includes;
    includes.insert(includes.end(), this->MocIncludePaths.begin(),
                    this->MocIncludePaths.end());
    this->MocIncludePaths.clear();
    this->MocIncludePaths.reserve(includes.size());
    // Append project directories only
    {
      const char* movePaths[2] = { this->ProjectBinaryDir.c_str(),
                                   this->ProjectSourceDir.c_str() };
      for (const char* const* mpit = cmArrayBegin(movePaths);
           mpit != cmArrayEnd(movePaths); ++mpit) {
        std::list<std::string>::iterator it = includes.begin();
        while (it != includes.end()) {
          const std::string& path = *it;
          if (cmsys::SystemTools::StringStartsWith(path, *mpit)) {
            this->MocIncludePaths.push_back(path);
            it = includes.erase(it);
          } else {
            ++it;
          }
        }
687
      }
688
    }
689
690
691
692
    // Append remaining directories
    this->MocIncludePaths.insert(this->MocIncludePaths.end(), includes.begin(),
                                 includes.end());
  }
693
694
695
  // Compose moc includes list
  {
    std::set<std::string> frameworkPaths;
696
697
698
699
    for (std::vector<std::string>::const_iterator it =
           this->MocIncludePaths.begin();
         it != this->MocIncludePaths.end(); ++it) {
      const std::string& path = *it;
700
      this->MocIncludes.push_back("-I" + path);
701
702
703
704
705
706
707
708
      // Extract framework path
      if (cmHasLiteralSuffix(path, ".framework/Headers")) {
        // Go up twice to get to the framework root
        std::vector<std::string> pathComponents;
        cmsys::SystemTools::SplitPath(path, pathComponents);
        std::string frameworkPath = cmsys::SystemTools::JoinPath(
          pathComponents.begin(), pathComponents.end() - 2);
        frameworkPaths.insert(frameworkPath);
709
710
      }
    }
711
712
713
    // Append framework includes
    for (std::set<std::string>::const_iterator it = frameworkPaths.begin();
         it != frameworkPaths.end(); ++it) {
714
715
      this->MocIncludes.push_back("-F");
      this->MocIncludes.push_back(*it);
716
717
    }
  }
718
719
}

720
bool cmQtAutoGenerators::RunAutogen()
721
{
Alexander Neundorf's avatar
Alexander Neundorf committed
722
723
724
  // 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
725
726
727
728
  // may not be included in the mocs_compilation_$<CONFIG>.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 mocs_compilation_$<CONFIG>.cpp file.
Alexander Neundorf's avatar
Alexander Neundorf committed
729

730
731
732
733
734
735
736
737
738
739
740
  // Create AUTOGEN include directory
  {
    const std::string incDirAbs = cmSystemTools::CollapseCombinedPath(
      this->AutogenBuildDir, this->AutogenIncludeDir);
    if (!cmsys::SystemTools::MakeDirectory(incDirAbs)) {
      this->LogError("AutoGen: Error: Could not create include directory " +
                     Quoted(incDirAbs));
      return false;
    }
  }

Alexander Neundorf's avatar
Alexander Neundorf committed
741
  // key = moc source filepath, value = moc output filepath
742
743
  std::map<std::string, std::string> mocsIncluded;
  std::map<std::string, std::string> mocsNotIncluded;
744
  std::map<std::string, std::set<std::string> > mocDepends;
745
  std::map<std::string, std::vector<std::string> > uisIncluded;
746
  // collects all headers which may need to be mocced
747
748
  std::set<std::string> mocHeaderFiles;
  std::set<std::string> uicHeaderFiles;
749

750
  // Parse sources
751
752
  for (std::vector<std::string>::const_iterator it = this->Sources.begin();
       it != this->Sources.end(); ++it) {
753
    const std::string& absFilename = cmsys::SystemTools::GetRealPath(*it);
754
    // Parse source file for MOC/UIC
755
756
    if (!this->ParseSourceFile(absFilename, mocsIncluded, mocDepends,
                               uisIncluded, this->MocRelaxedMode)) {
757
      return false;
758
    }
759
760
761
    // Find additional headers
    this->SearchHeadersForSourceFile(absFilename, mocHeaderFiles,
                                     uicHeaderFiles);
762
763
  }

764
  // Parse headers
765
766
  for (std::vector<std::string>::const_iterator it = this->Headers.begin();
       it != this->Headers.end(); ++it) {
767
    const std::string& headerName = cmsys::SystemTools::GetRealPath(*it);
768
    if (!this->MocSkip(headerName)) {
769
      mocHeaderFiles.insert(headerName);
770
    }
771
    if (!this->UicSkip(headerName)) {
772
      uicHeaderFiles.insert(headerName);
773
774
    }
  }
775
776
777
778
  if (!this->ParseHeaders(mocHeaderFiles, uicHeaderFiles, mocsIncluded,
                          mocsNotIncluded, mocDepends, uisIncluded)) {
    return false;
  };
779

780
  // Generate files
781
  if (!this->MocGenerateAll(mocsIncluded, mocsNotIncluded, mocDepends)) {
782
    return false;
783
  }
784
  if (!this->UicGenerateAll(uisIncluded)) {
785
    return false;
786
  }
787
  if (!this->RccGenerateAll()) {
788
    return false;
789
  }
790

791
792
793
  return true;
}

794
795
796
797
/**
 * @brief Tests if the C++ content requires moc processing
 * @return True if moc is required
 */
798
bool cmQtAutoGenerators::MocRequired(const std::string& contentText,
799
                                     std::string* macroName)
800
{
801
802
  for (unsigned int ii = 0; ii != cmArraySize(this->MocMacroFilters); ++ii) {
    MocMacroFilter& filter = this->MocMacroFilters[ii];
803
804
805
806
    // Run a simple find string operation before the expensive
    // regular expression check
    if (contentText.find(filter.first) != std::string::npos) {
      if (filter.second.find(contentText)) {
807
808
        // Return macro name on demand
        if (macroName != CM_NULLPTR) {
809
          *macroName = filter.first;
810
811
812
        }
        return true;
      }
813
814
815
816
817
    }
  }
  return false;
}

818
819
820
821
822
823
824
825
826
827
828
829
void cmQtAutoGenerators::MocFindDepends(
  const std::string& absFilename, const std::string& contentText,
  std::map<std::string, std::set<std::string> >& mocDepends)
{
  for (std::vector<MocDependFilter>::iterator fit =
         this->MocDependFilters.begin();
       fit != this->MocDependFilters.end(); ++fit) {
    MocDependFilter& filter = *fit;
    // Run a simple find string operation before the expensive
    // regular expression check
    if (contentText.find(filter.key) != std::string::npos) {
      // Run regular expression check loop
830
      const std::string sourcePath = SubDirPrefix(absFilename);
831
832
833
834
835
836
      const char* contentChars = contentText.c_str();
      while (filter.regExp.find(contentChars)) {
        // Evaluate match
        const std::string match = filter.regExp.match(1);
        if (!match.empty()) {
          // Find the dependency file
837
          std::string incFile;
838
          if (this->MocFindIncludedFile(incFile, sourcePath, match)) {
839
840
            mocDepends[absFilename].insert(incFile);
            if (this->Verbose) {
841
842
              this->LogInfo("AutoMoc: Found dependency:\n  " +
                            Quoted(absFilename) + "\n  " + Quoted(incFile));
843
844
            }
          } else {
845
846
847
            this->LogWarning("AutoMoc: Warning: " + Quoted(absFilename) +
                             "\n" + "Could not find dependency file " +
                             Quoted(match));
848
849
850
851
          }
        }
        contentChars += filter.regExp.end();
      }
852
853
854
855
856
857
858
859
    }
  }
}

/**
 * @brief Tests if the file should be ignored for moc scanning
 * @return True if the file should be ignored
 */
860
bool cmQtAutoGenerators::MocSkip(const std::string& absFilename) const
861
{
862
  if (this->MocEnabled()) {
863
    // Test if the file name is on the skip list
864
    if (!ListContains(this->MocSkipList, absFilename)) {
865
866
867
868
869
870
871
872
873
      return false;
    }
  }
  return true;
}

/**
 * @brief Tests if the file name is in the skip list
 */
874
bool cmQtAutoGenerators::UicSkip(const std::string& absFilename) const
875
{
876
  if (this->UicEnabled()) {
877
    // Test if the file name is on the skip list
878
    if (!ListContains(this->UicSkipList, absFilename)) {
879
880
881
882
883
884
      return false;
    }
  }
  return true;
}

885
886
887
888
889
/**
 * @return True on success
 */
bool cmQtAutoGenerators::ParseSourceFile(
  const std::string& absFilename,
890
  std::map<std::string, std::string>& mocsIncluded,
891
  std::map<std::string, std::set<std::string> >& mocDepends,
892
  std::map<std::string, std::vector<std::string> >& uisIncluded, bool relaxed)
893
{
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
  std::string contentText;
  bool success = ReadAll(contentText, absFilename);
  if (success) {
    if (!contentText.empty()) {
      // Parse source contents for MOC
      if (success && !this->MocSkip(absFilename)) {
        success = this->MocParseSourceContent(
          absFilename, contentText, mocsIncluded, mocDepends, relaxed);
      }
      // Parse source contents for UIC
      if (success && !this->UicSkip(absFilename)) {
        this->UicParseContent(absFilename, contentText, uisIncluded);
      }
    } else {
      std::ostringstream ost;
      ost << "AutoGen: Warning: The file is empty:\n"
          << Quoted(absFilename) << "\n";
      this->LogWarning(ost.str());
912
    }
913
914
915
916
  } else {
    std::ostringstream ost;
    ost << "AutoGen: Error: Could not read file:\n" << Quoted(absFilename);
    this->LogError(ost.str());
917
918
919
920
  }
  return success;
}

921
922
void cmQtAutoGenerators::UicParseContent(
  const std::string& absFilename, const std::string& contentText,
923
  std::map<std::string, std::vector<std::string> >& uisIncluded)
924
{
925
  if (this->Verbose) {
926
    this->LogInfo("AutoUic: Checking " + absFilename);
927
  }
928

929
  const char* contentChars = contentText.c_str();
930
  if (strstr(contentChars, "ui_") != CM_NULLPTR) {
931
932
933
    while (this->UicRegExpInclude.find(contentChars)) {
      uisIncluded[absFilename].push_back(this->UicRegExpInclude.match(1));
      contentChars += this->UicRegExpInclude.end();
934
935
936
937
938
939
940
    }
  }
}

/**
 * @return True on success
 */
941
942
943
944
bool cmQtAutoGenerators::MocParseSourceContent(
  const std::string& absFilename, const std::string& contentText,
  std::map<std::string, std::string>& mocsIncluded,
  std::map<std::string, std::set<std::string> >& mocDepends, bool relaxed)
945
{
946
  if (this->Verbose) {
947
    this->LogInfo("AutoMoc: Checking " + absFilename);
948
949
  }

950
  const std::string scannedFileAbsPath = SubDirPrefix(absFilename);
951
952
953
954
  const std::string scannedFileBasename =
    cmsys::SystemTools::GetFilenameWithoutLastExtension(absFilename);

  std::string macroName;
955
  const bool requiresMoc = this->MocRequired(contentText, &macroName);
956
  bool ownDotMocIncluded = false;
957
958
  std::string ownMocUnderscoreInclude;
  std::string ownMocUnderscoreHeader;
959
960
961
962

  // first a simple string check for "moc" is *much* faster than the regexp,
  // and if the string search already fails, we don't have to try the
  // expensive regexp
963
  const char* contentChars = contentText.c_str();
964
  if (strstr(contentChars, "moc") != CM_NULLPTR) {
965
    // Iterate over all included moc files
966
967
    while (this->MocRegExpInclude.find(contentChars)) {
      const std::string incString = this->MocRegExpInclude.match(1);
968
      // Basename of the moc include
969
      const std::string incSubDir(SubDirPrefix(incString));
970
971
      const std::string incBasename =
        cmsys::SystemTools::GetFilenameWithoutLastExtension(incString);
972
973
974
975
976
977

      // If the moc include is of the moc_foo.cpp style we expect
      // the Q_OBJECT class declaration in a header file.
      // If the moc include is of the foo.moc style we need to look for
      // a Q_OBJECT macro in the current source file, if it contains the
      // macro we generate the moc file from the source file.
978
      if (cmHasLiteralPrefix(incBasename, "moc_")) {
979
        // Include: moc_FOO.cxx
980
981
        // Remove the moc_ part
        const std::string incRealBasename = incBasename.substr(4);
982
        const std::string headerToMoc =
983
          this->MocFindHeader(scannedFileAbsPath, incSubDir + incRealBasename);
984
        if (!headerToMoc.empty()) {
985
986
987
988
989
990
991
992
993
          if (!this->MocSkip(headerToMoc)) {
            // Register moc job
            mocsIncluded[headerToMoc] = incString;
            this->MocFindDepends(headerToMoc, contentText, mocDepends);
            // Store meta information for relaxed mode
            if (relaxed && (incRealBasename == scannedFileBasename)) {
              ownMocUnderscoreInclude = incString;
              ownMocUnderscoreHeader = headerToMoc;
            }
994
995
          }
        } else {
996
          std::ostringstream ost;
997
          ost << "AutoMoc: Error: " << Quoted(absFilename) << "\n"
998
999
1000
1001
1002
              << "The file includes the moc file " << Quoted(incString)
              << ", but could not find header "
              << Quoted(incRealBasename + "{" +
                        JoinExts(this->HeaderExtensions) + "}");
          ;
1003
          this->LogError(ost.str());
1004
1005
1006
1007
1008
1009
1010
          return false;
        }
      } else {
        // Include: FOO.moc
        std::string fileToMoc;
        if (relaxed) {
          // Mode: Relaxed
1011
1012
1013
1014
1015
1016
          if (requiresMoc && (incBasename == scannedFileBasename)) {
            // Include self
            fileToMoc = absFilename;
            ownDotMocIncluded = true;
          } else {
            // In relaxed mode try to find a header instead but issue a warning
1017
            const std::string headerToMoc =
1018
              this->MocFindHeader(scannedFileAbsPath, incSubDir + incBasename);