cmQtAutoGeneratorMocUic.cxx 60.7 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.  */
3
#include "cmQtAutoGeneratorMocUic.h"
Brad King's avatar
Brad King committed
4

5
#include <algorithm>
6
#include <array>
7
#include <cstddef>
8
#include <list>
9
#include <memory>
10
#include <set>
11 12 13
#include <sstream>
#include <utility>

14
#include "cmAlgorithms.h"
15
#include "cmCryptoHash.h"
16
#include "cmMakefile.h"
17
#include "cmQtAutoGen.h"
18 19 20
#include "cmSystemTools.h"
#include "cmake.h"

21
#if defined(__APPLE__)
22
#  include <unistd.h>
23
#endif
24

25
// -- Class methods
26

27 28 29
std::string cmQtAutoGeneratorMocUic::BaseSettingsT::AbsoluteBuildPath(
  std::string const& relativePath) const
{
30
  return FileSys->CollapseFullPath(relativePath, AutogenBuildDir);
31
}
32

33 34 35 36 37 38 39
/**
 * @brief Tries to find the header file to the given file base path by
 * appending different header extensions
 * @return True on success
 */
bool cmQtAutoGeneratorMocUic::BaseSettingsT::FindHeader(
  std::string& header, std::string const& testBasePath) const
40
{
41 42 43 44 45 46 47 48
  for (std::string const& ext : HeaderExtensions) {
    std::string testFilePath(testBasePath);
    testFilePath.push_back('.');
    testFilePath += ext;
    if (FileSys->FileExists(testFilePath)) {
      header = testFilePath;
      return true;
    }
49
  }
50
  return false;
51 52
}

53 54
bool cmQtAutoGeneratorMocUic::MocSettingsT::skipped(
  std::string const& fileName) const
55
{
56
  return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
57 58
}

59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
/**
 * @brief Returns the first relevant Qt macro name found in the given C++ code
 * @return The name of the Qt macro or an empty string
 */
std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindMacro(
  std::string const& content) const
{
  for (KeyExpT const& filter : MacroFilters) {
    // Run a simple find string operation before the expensive
    // regular expression check
    if (content.find(filter.Key) != std::string::npos) {
      cmsys::RegularExpressionMatch match;
      if (filter.Exp.find(content.c_str(), match)) {
        // Return macro name on demand
        return filter.Key;
      }
    }
  }
  return std::string();
}
79

80
std::string cmQtAutoGeneratorMocUic::MocSettingsT::MacrosString() const
81
{
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  std::string res;
  const auto itB = MacroFilters.cbegin();
  const auto itE = MacroFilters.cend();
  const auto itL = itE - 1;
  auto itC = itB;
  for (; itC != itE; ++itC) {
    // Separator
    if (itC != itB) {
      if (itC != itL) {
        res += ", ";
      } else {
        res += " or ";
      }
    }
    // Key
    res += itC->Key;
  }
  return res;
100 101
}

102 103
std::string cmQtAutoGeneratorMocUic::MocSettingsT::FindIncludedFile(
  std::string const& sourcePath, std::string const& includeString) const
104
{
105 106 107 108 109
  // Search in vicinity of the source
  {
    std::string testPath = sourcePath;
    testPath += includeString;
    if (FileSys->FileExists(testPath)) {
110
      return FileSys->GetRealPath(testPath);
111 112 113 114 115 116 117 118
    }
  }
  // Search in include directories
  for (std::string const& path : IncludePaths) {
    std::string fullPath = path;
    fullPath.push_back('/');
    fullPath += includeString;
    if (FileSys->FileExists(fullPath)) {
119
      return FileSys->GetRealPath(fullPath);
120 121 122 123 124
    }
  }
  // Return empty string
  return std::string();
}
125

126 127 128 129 130 131 132 133 134 135 136
void cmQtAutoGeneratorMocUic::MocSettingsT::FindDependencies(
  std::string const& content, std::set<std::string>& depends) const
{
  if (!DependFilters.empty() && !content.empty()) {
    for (KeyExpT const& filter : DependFilters) {
      // Run a simple find string check
      if (content.find(filter.Key) != std::string::npos) {
        // Run the expensive regular expression check loop
        const char* contentChars = content.c_str();
        cmsys::RegularExpressionMatch match;
        while (filter.Exp.find(contentChars, match)) {
137
          {
138 139 140 141
            std::string dep = match.match(1);
            if (!dep.empty()) {
              depends.emplace(std::move(dep));
            }
142
          }
143
          contentChars += match.end();
144 145 146
        }
      }
    }
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
  }
}

bool cmQtAutoGeneratorMocUic::UicSettingsT::skipped(
  std::string const& fileName) const
{
  return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
}

void cmQtAutoGeneratorMocUic::JobParseT::Process(WorkerT& wrk)
{
  if (AutoMoc && Header) {
    // Don't parse header for moc if the file is included by a source already
    if (wrk.Gen().ParallelMocIncluded(FileName)) {
      AutoMoc = false;
162
    }
163 164 165 166 167 168 169
  }

  if (AutoMoc || AutoUic) {
    std::string error;
    MetaT meta;
    if (wrk.FileSys().FileRead(meta.Content, FileName, &error)) {
      if (!meta.Content.empty()) {
170
        meta.FileDir = wrk.FileSys().SubDirPrefix(FileName);
171
        meta.FileBase =
172
          wrk.FileSys().GetFilenameWithoutLastExtension(FileName);
173 174 175 176 177 178 179 180 181 182 183 184 185

        bool success = true;
        if (AutoMoc) {
          if (Header) {
            success = ParseMocHeader(wrk, meta);
          } else {
            success = ParseMocSource(wrk, meta);
          }
        }
        if (AutoUic && success) {
          ParseUic(wrk, meta);
        }
      } else {
186
        wrk.LogFileWarning(GenT::GEN, FileName, "The source file is empty");
187 188
      }
    } else {
189
      wrk.LogFileError(GenT::GEN, FileName,
190
                       "Could not read the file: " + error);
191
    }
192 193 194 195 196 197 198 199 200 201 202 203
  }
}

bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocSource(WorkerT& wrk,
                                                        MetaT const& meta)
{
  struct JobPre
  {
    bool self;       // source file is self
    bool underscore; // "moc_" style include
    std::string SourceFile;
    std::string IncludeString;
204 205
  };

206 207 208 209 210 211
  struct MocInclude
  {
    std::string Inc;  // full include string
    std::string Dir;  // include string directory
    std::string Base; // include string file base
  };
212

213 214
  // Check if this source file contains a relevant macro
  std::string const ownMacro = wrk.Moc().FindMacro(meta.Content);
215

216 217 218 219 220 221 222 223
  // Extract moc includes from file
  std::deque<MocInclude> mocIncsUsc;
  std::deque<MocInclude> mocIncsDot;
  {
    if (meta.Content.find("moc") != std::string::npos) {
      const char* contentChars = meta.Content.c_str();
      cmsys::RegularExpressionMatch match;
      while (wrk.Moc().RegExpInclude.find(contentChars, match)) {
224
        std::string incString = match.match(2);
225
        std::string incDir(wrk.FileSys().SubDirPrefix(incString));
226
        std::string incBase =
227
          wrk.FileSys().GetFilenameWithoutLastExtension(incString);
228 229 230 231 232 233 234 235 236 237 238 239 240 241
        if (cmHasLiteralPrefix(incBase, "moc_")) {
          // moc_<BASE>.cxx
          // Remove the moc_ part from the base name
          mocIncsUsc.emplace_back(MocInclude{
            std::move(incString), std::move(incDir), incBase.substr(4) });
        } else {
          // <BASE>.moc
          mocIncsDot.emplace_back(MocInclude{
            std::move(incString), std::move(incDir), std::move(incBase) });
        }
        // Forward content pointer
        contentChars += match.end();
      }
    }
242 243
  }

244 245 246
  // Check if there is anything to do
  if (ownMacro.empty() && mocIncsUsc.empty() && mocIncsDot.empty()) {
    return true;
247
  }
248

249 250 251
  bool ownDotMocIncluded = false;
  bool ownMocUscIncluded = false;
  std::deque<JobPre> jobs;
252

253 254 255 256 257 258 259 260
  // Process moc_<BASE>.cxx includes
  for (const MocInclude& mocInc : mocIncsUsc) {
    std::string const header =
      MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
    if (!header.empty()) {
      // Check if header is skipped
      if (wrk.Moc().skipped(header)) {
        continue;
261
      }
262 263 264 265 266 267
      // Register moc job
      const bool ownMoc = (mocInc.Base == meta.FileBase);
      jobs.emplace_back(JobPre{ ownMoc, true, header, mocInc.Inc });
      // Store meta information for relaxed mode
      if (ownMoc) {
        ownMocUscIncluded = true;
268
      }
269 270 271 272 273 274 275
    } else {
      {
        std::string emsg = "The file includes the moc file ";
        emsg += Quoted(mocInc.Inc);
        emsg += ", but the header ";
        emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
        emsg += " could not be found.";
276
        wrk.LogFileError(GenT::MOC, FileName, emsg);
277
      }
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
      return false;
    }
  }

  // Process <BASE>.moc includes
  for (const MocInclude& mocInc : mocIncsDot) {
    const bool ownMoc = (mocInc.Base == meta.FileBase);
    if (wrk.Moc().RelaxedMode) {
      // Relaxed mode
      if (!ownMacro.empty() && ownMoc) {
        // Add self
        jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc });
        ownDotMocIncluded = true;
      } else {
        // In relaxed mode try to find a header instead but issue a warning.
        // This is for KDE4 compatibility
        std::string const header =
          MocFindIncludedHeader(wrk, meta.FileDir, mocInc.Dir + mocInc.Base);
        if (!header.empty()) {
          // Check if header is skipped
          if (wrk.Moc().skipped(header)) {
            continue;
300
          }
301 302 303 304 305 306 307 308 309 310 311 312 313 314
          // Register moc job
          jobs.emplace_back(JobPre{ ownMoc, false, header, mocInc.Inc });
          if (ownMacro.empty()) {
            if (ownMoc) {
              std::string emsg = "The file includes the moc file ";
              emsg += Quoted(mocInc.Inc);
              emsg += ", but does not contain a ";
              emsg += wrk.Moc().MacrosString();
              emsg += " macro.\nRunning moc on\n  ";
              emsg += Quoted(header);
              emsg += "!\nBetter include ";
              emsg += Quoted("moc_" + mocInc.Base + ".cpp");
              emsg += " for a compatibility with strict mode.\n"
                      "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
315
              wrk.LogFileWarning(GenT::MOC, FileName, emsg);
316 317 318 319 320 321 322 323 324 325 326
            } else {
              std::string emsg = "The file includes the moc file ";
              emsg += Quoted(mocInc.Inc);
              emsg += " instead of ";
              emsg += Quoted("moc_" + mocInc.Base + ".cpp");
              emsg += ".\nRunning moc on\n  ";
              emsg += Quoted(header);
              emsg += "!\nBetter include ";
              emsg += Quoted("moc_" + mocInc.Base + ".cpp");
              emsg += " for compatibility with strict mode.\n"
                      "(CMAKE_AUTOMOC_RELAXED_MODE warning)\n";
327
              wrk.LogFileWarning(GenT::MOC, FileName, emsg);
328 329 330 331 332 333 334 335 336 337 338
            }
          }
        } else {
          {
            std::string emsg = "The file includes the moc file ";
            emsg += Quoted(mocInc.Inc);
            emsg += ", which seems to be the moc file from a different "
                    "source file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a "
                    "matching header ";
            emsg += Quoted(MocStringHeaders(wrk, mocInc.Base));
            emsg += " could not be found.";
339
            wrk.LogFileError(GenT::MOC, FileName, emsg);
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
          }
          return false;
        }
      }
    } else {
      // Strict mode
      if (ownMoc) {
        // Include self
        jobs.emplace_back(JobPre{ ownMoc, false, FileName, mocInc.Inc });
        ownDotMocIncluded = true;
        // Accept but issue a warning if moc isn't required
        if (ownMacro.empty()) {
          std::string emsg = "The file includes the moc file ";
          emsg += Quoted(mocInc.Inc);
          emsg += ", but does not contain a ";
          emsg += wrk.Moc().MacrosString();
          emsg += " macro.";
357
          wrk.LogFileWarning(GenT::MOC, FileName, emsg);
358
        }
359
      } else {
360 361 362 363 364 365 366 367
        // Don't allow <BASE>.moc include other than self in strict mode
        {
          std::string emsg = "The file includes the moc file ";
          emsg += Quoted(mocInc.Inc);
          emsg += ", which seems to be the moc file from a different "
                  "source file.\nThis is not supported. Include ";
          emsg += Quoted(meta.FileBase + ".moc");
          emsg += " to run moc on this source file.";
368
          wrk.LogFileError(GenT::MOC, FileName, emsg);
369
        }
370
        return false;
371 372 373
      }
    }
  }
374

375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
  if (!ownMacro.empty() && !ownDotMocIncluded) {
    // In this case, check whether the scanned file itself contains a
    // Q_OBJECT.
    // If this is the case, the moc_foo.cpp should probably be generated from
    // foo.cpp instead of foo.h, because otherwise it won't build.
    // But warn, since this is not how it is supposed to be used.
    // This is for KDE4 compatibility.
    if (wrk.Moc().RelaxedMode && ownMocUscIncluded) {
      JobPre uscJobPre;
      // Remove underscore job request
      {
        auto itC = jobs.begin();
        auto itE = jobs.end();
        for (; itC != itE; ++itC) {
          JobPre& job(*itC);
          if (job.self && job.underscore) {
            uscJobPre = std::move(job);
            jobs.erase(itC);
            break;
          }
        }
396
      }
397 398 399 400 401 402 403 404 405 406 407 408 409 410
      // Issue a warning
      {
        std::string emsg = "The file contains a ";
        emsg += ownMacro;
        emsg += " macro, but does not include ";
        emsg += Quoted(meta.FileBase + ".moc");
        emsg += ". Instead it includes ";
        emsg += Quoted(uscJobPre.IncludeString);
        emsg += ".\nRunning moc on\n  ";
        emsg += Quoted(FileName);
        emsg += "!\nBetter include ";
        emsg += Quoted(meta.FileBase + ".moc");
        emsg += " for compatibility with strict mode.\n"
                "(CMAKE_AUTOMOC_RELAXED_MODE warning)";
411
        wrk.LogFileWarning(GenT::MOC, FileName, emsg);
412
      }
413 414 415 416 417 418 419 420 421 422 423 424 425
      // Add own source job
      jobs.emplace_back(
        JobPre{ true, false, FileName, uscJobPre.IncludeString });
    } else {
      // Otherwise always error out since it will not compile.
      {
        std::string emsg = "The file contains a ";
        emsg += ownMacro;
        emsg += " macro, but does not include ";
        emsg += Quoted(meta.FileBase + ".moc");
        emsg += "!\nConsider to\n - add #include \"";
        emsg += meta.FileBase;
        emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
426
        wrk.LogFileError(GenT::MOC, FileName, emsg);
427 428
      }
      return false;
429 430
    }
  }
431

432 433
  // Convert pre jobs to actual jobs
  for (JobPre& jobPre : jobs) {
434 435
    JobHandleT jobHandle = cm::make_unique<JobMocT>(
      std::move(jobPre.SourceFile), FileName, std::move(jobPre.IncludeString));
436
    if (jobPre.self) {
luz.paz's avatar
luz.paz committed
437
      // Read dependencies from this source
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
      static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
    }
    if (!wrk.Gen().ParallelJobPushMoc(jobHandle)) {
      return false;
    }
  }
  return true;
}

bool cmQtAutoGeneratorMocUic::JobParseT::ParseMocHeader(WorkerT& wrk,
                                                        MetaT const& meta)
{
  bool success = true;
  std::string const macroName = wrk.Moc().FindMacro(meta.Content);
  if (!macroName.empty()) {
453 454
    JobHandleT jobHandle = cm::make_unique<JobMocT>(
      std::string(FileName), std::string(), std::string());
luz.paz's avatar
luz.paz committed
455
    // Read dependencies from this source
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
    static_cast<JobMocT&>(*jobHandle).FindDependencies(wrk, meta.Content);
    success = wrk.Gen().ParallelJobPushMoc(jobHandle);
  }
  return success;
}

std::string cmQtAutoGeneratorMocUic::JobParseT::MocStringHeaders(
  WorkerT& wrk, std::string const& fileBase) const
{
  std::string res = fileBase;
  res += ".{";
  res += cmJoin(wrk.Base().HeaderExtensions, ",");
  res += "}";
  return res;
}

std::string cmQtAutoGeneratorMocUic::JobParseT::MocFindIncludedHeader(
  WorkerT& wrk, std::string const& includerDir, std::string const& includeBase)
{
  std::string header;
  // Search in vicinity of the source
  if (!wrk.Base().FindHeader(header, includerDir + includeBase)) {
    // Search in include directories
    for (std::string const& path : wrk.Moc().IncludePaths) {
      std::string fullPath = path;
      fullPath.push_back('/');
      fullPath += includeBase;
      if (wrk.Base().FindHeader(header, fullPath)) {
        break;
      }
    }
  }
  // Sanitize
  if (!header.empty()) {
490
    header = wrk.FileSys().GetRealPath(header);
491 492 493 494 495 496 497 498 499 500 501 502
  }
  return header;
}

bool cmQtAutoGeneratorMocUic::JobParseT::ParseUic(WorkerT& wrk,
                                                  MetaT const& meta)
{
  bool success = true;
  if (meta.Content.find("ui_") != std::string::npos) {
    const char* contentChars = meta.Content.c_str();
    cmsys::RegularExpressionMatch match;
    while (wrk.Uic().RegExpInclude.find(contentChars, match)) {
503
      if (!ParseUicInclude(wrk, meta, match.match(2))) {
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
        success = false;
        break;
      }
      contentChars += match.end();
    }
  }
  return success;
}

bool cmQtAutoGeneratorMocUic::JobParseT::ParseUicInclude(
  WorkerT& wrk, MetaT const& meta, std::string&& includeString)
{
  bool success = false;
  std::string uiInputFile = UicFindIncludedFile(wrk, meta, includeString);
  if (!uiInputFile.empty()) {
    if (!wrk.Uic().skipped(uiInputFile)) {
520 521
      JobHandleT jobHandle = cm::make_unique<JobUicT>(
        std::move(uiInputFile), FileName, std::move(includeString));
522 523 524 525 526 527 528 529 530 531 532 533 534 535
      success = wrk.Gen().ParallelJobPushUic(jobHandle);
    } else {
      // A skipped file is successful
      success = true;
    }
  }
  return success;
}

std::string cmQtAutoGeneratorMocUic::JobParseT::UicFindIncludedFile(
  WorkerT& wrk, MetaT const& meta, std::string const& includeString)
{
  std::string res;
  std::string searchFile =
536
    wrk.FileSys().GetFilenameWithoutLastExtension(includeString).substr(3);
537 538 539
  searchFile += ".ui";
  // Collect search paths list
  std::deque<std::string> testFiles;
540
  {
541
    std::string const searchPath = wrk.FileSys().SubDirPrefix(includeString);
542

543 544 545 546
    std::string searchFileFull;
    if (!searchPath.empty()) {
      searchFileFull = searchPath;
      searchFileFull += searchFile;
547
    }
548
    // Vicinity of the source
549
    {
550 551 552 553
      std::string const sourcePath = meta.FileDir;
      testFiles.push_back(sourcePath + searchFile);
      if (!searchPath.empty()) {
        testFiles.push_back(sourcePath + searchFileFull);
554
      }
555 556 557 558 559 560 561 562 563 564
    }
    // AUTOUIC search paths
    if (!wrk.Uic().SearchPaths.empty()) {
      for (std::string const& sPath : wrk.Uic().SearchPaths) {
        testFiles.push_back((sPath + "/").append(searchFile));
      }
      if (!searchPath.empty()) {
        for (std::string const& sPath : wrk.Uic().SearchPaths) {
          testFiles.push_back((sPath + "/").append(searchFileFull));
        }
565 566 567 568
      }
    }
  }

569 570 571
  // Search for the .ui file!
  for (std::string const& testFile : testFiles) {
    if (wrk.FileSys().FileExists(testFile)) {
572
      res = wrk.FileSys().GetRealPath(testFile);
573 574
      break;
    }
575
  }
576

577 578 579 580 581 582 583 584 585
  // Log error
  if (res.empty()) {
    std::string emsg = "Could not find ";
    emsg += Quoted(searchFile);
    emsg += " in\n";
    for (std::string const& testFile : testFiles) {
      emsg += "  ";
      emsg += Quoted(testFile);
      emsg += "\n";
586
    }
587
    wrk.LogFileError(GenT::UIC, FileName, emsg);
588
  }
589

590 591 592 593 594 595 596 597 598 599 600 601 602
  return res;
}

void cmQtAutoGeneratorMocUic::JobMocPredefsT::Process(WorkerT& wrk)
{
  // (Re)generate moc_predefs.h on demand
  bool generate(false);
  bool fileExists(wrk.FileSys().FileExists(wrk.Moc().PredefsFileAbs));
  if (!fileExists) {
    if (wrk.Log().Verbose()) {
      std::string reason = "Generating ";
      reason += Quoted(wrk.Moc().PredefsFileRel);
      reason += " because it doesn't exist";
603
      wrk.LogInfo(GenT::MOC, reason);
604 605 606 607 608 609 610
    }
    generate = true;
  } else if (wrk.Moc().SettingsChanged) {
    if (wrk.Log().Verbose()) {
      std::string reason = "Generating ";
      reason += Quoted(wrk.Moc().PredefsFileRel);
      reason += " because the settings changed.";
611
      wrk.LogInfo(GenT::MOC, reason);
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
    }
    generate = true;
  }
  if (generate) {
    ProcessResultT result;
    {
      // Compose command
      std::vector<std::string> cmd = wrk.Moc().PredefsCmd;
      // Add includes
      cmd.insert(cmd.end(), wrk.Moc().Includes.begin(),
                 wrk.Moc().Includes.end());
      // Add definitions
      for (std::string const& def : wrk.Moc().Definitions) {
        cmd.push_back("-D" + def);
      }
      // Execute command
628
      if (!wrk.RunProcess(GenT::MOC, result, cmd)) {
629 630 631 632
        std::string emsg = "The content generation command for ";
        emsg += Quoted(wrk.Moc().PredefsFileRel);
        emsg += " failed.\n";
        emsg += result.ErrorMessage;
633
        wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
634
      }
635 636
    }

637 638 639 640
    // (Re)write predefs file only on demand
    if (!result.error()) {
      if (!fileExists ||
          wrk.FileSys().FileDiffers(wrk.Moc().PredefsFileAbs, result.StdOut)) {
641
        if (wrk.FileSys().FileWrite(GenT::MOC, wrk.Moc().PredefsFileAbs,
642 643 644 645 646 647
                                    result.StdOut)) {
          // Success
        } else {
          std::string emsg = "Writing ";
          emsg += Quoted(wrk.Moc().PredefsFileRel);
          emsg += " failed.";
648
          wrk.LogFileError(GenT::MOC, wrk.Moc().PredefsFileAbs, emsg);
649
        }
650 651 652 653 654 655
      } else {
        // Touch to update the time stamp
        if (wrk.Log().Verbose()) {
          std::string msg = "Touching ";
          msg += Quoted(wrk.Moc().PredefsFileRel);
          msg += ".";
656
          wrk.LogInfo(GenT::MOC, msg);
657 658
        }
        wrk.FileSys().Touch(wrk.Moc().PredefsFileAbs);
659 660
      }
    }
661 662 663 664 665 666 667 668 669 670 671 672 673 674
  }
}

void cmQtAutoGeneratorMocUic::JobMocT::FindDependencies(
  WorkerT& wrk, std::string const& content)
{
  wrk.Moc().FindDependencies(content, Depends);
  DependsValid = true;
}

void cmQtAutoGeneratorMocUic::JobMocT::Process(WorkerT& wrk)
{
  // Compute build file name
  if (!IncludeString.empty()) {
675 676
    BuildFile = wrk.Base().AutogenIncludeDir;
    BuildFile += '/';
677 678
    BuildFile += IncludeString;
  } else {
679 680 681 682 683 684 685 686
    // Relative build path
    std::string relPath = wrk.FileSys().GetFilePathChecksum(SourceFile);
    relPath += "/moc_";
    relPath += wrk.FileSys().GetFilenameWithoutLastExtension(SourceFile);

    // Register relative file path with duplication check
    relPath = wrk.Gen().ParallelMocAutoRegister(relPath);

687 688 689 690
    // Absolute build path
    if (wrk.Base().MultiConfig) {
      BuildFile = wrk.Base().AutogenIncludeDir;
      BuildFile += '/';
691
      BuildFile += relPath;
692
    } else {
693
      BuildFile = wrk.Base().AbsoluteBuildPath(relPath);
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
    }
  }

  if (UpdateRequired(wrk)) {
    GenerateMoc(wrk);
  }
}

bool cmQtAutoGeneratorMocUic::JobMocT::UpdateRequired(WorkerT& wrk)
{
  bool const verbose = wrk.Gen().Log().Verbose();

  // Test if the build file exists
  if (!wrk.FileSys().FileExists(BuildFile)) {
    if (verbose) {
      std::string reason = "Generating ";
      reason += Quoted(BuildFile);
      reason += " from its source file ";
      reason += Quoted(SourceFile);
      reason += " because it doesn't exist";
714
      wrk.LogInfo(GenT::MOC, reason);
715 716 717 718 719 720 721 722 723 724 725 726
    }
    return true;
  }

  // Test if any setting changed
  if (wrk.Moc().SettingsChanged) {
    if (verbose) {
      std::string reason = "Generating ";
      reason += Quoted(BuildFile);
      reason += " from ";
      reason += Quoted(SourceFile);
      reason += " because the MOC settings changed";
727
      wrk.LogInfo(GenT::MOC, reason);
728 729 730 731 732 733 734
    }
    return true;
  }

  // Test if the moc_predefs file is newer
  if (!wrk.Moc().PredefsFileAbs.empty()) {
    bool isOlder = false;
735
    {
736 737 738 739
      std::string error;
      isOlder = wrk.FileSys().FileIsOlderThan(
        BuildFile, wrk.Moc().PredefsFileAbs, &error);
      if (!isOlder && !error.empty()) {
740
        wrk.LogError(GenT::MOC, error);
741
        return false;
742 743
      }
    }
744 745 746 747 748 749
    if (isOlder) {
      if (verbose) {
        std::string reason = "Generating ";
        reason += Quoted(BuildFile);
        reason += " because it's older than: ";
        reason += Quoted(wrk.Moc().PredefsFileAbs);
750
        wrk.LogInfo(GenT::MOC, reason);
751
      }
752
      return true;
753 754 755
    }
  }

756
  // Test if the source file is newer
757
  {
758 759 760 761 762
    bool isOlder = false;
    {
      std::string error;
      isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
      if (!isOlder && !error.empty()) {
763
        wrk.LogError(GenT::MOC, error);
764 765
        return false;
      }
766
    }
767 768 769 770 771 772
    if (isOlder) {
      if (verbose) {
        std::string reason = "Generating ";
        reason += Quoted(BuildFile);
        reason += " because it's older than its source file ";
        reason += Quoted(SourceFile);
773
        wrk.LogInfo(GenT::MOC, reason);
774
      }
775
      return true;
776
    }
777
  }
778

779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
  // Test if a dependency file is newer
  {
    // Read dependencies on demand
    if (!DependsValid) {
      std::string content;
      {
        std::string error;
        if (!wrk.FileSys().FileRead(content, SourceFile, &error)) {
          std::string emsg = "Could not read file\n  ";
          emsg += Quoted(SourceFile);
          emsg += "\nrequired by moc include ";
          emsg += Quoted(IncludeString);
          emsg += " in\n  ";
          emsg += Quoted(IncluderFile);
          emsg += ".\n";
          emsg += error;
795
          wrk.LogError(GenT::MOC, emsg);
796 797
          return false;
        }
798
      }
799
      FindDependencies(wrk, content);
800
    }
801 802
    // Check dependency timestamps
    std::string error;
803
    std::string sourceDir = wrk.FileSys().SubDirPrefix(SourceFile);
804 805 806 807 808 809 810 811 812 813 814 815
    for (std::string const& depFileRel : Depends) {
      std::string depFileAbs =
        wrk.Moc().FindIncludedFile(sourceDir, depFileRel);
      if (!depFileAbs.empty()) {
        if (wrk.FileSys().FileIsOlderThan(BuildFile, depFileAbs, &error)) {
          if (verbose) {
            std::string reason = "Generating ";
            reason += Quoted(BuildFile);
            reason += " from ";
            reason += Quoted(SourceFile);
            reason += " because it is older than it's dependency file ";
            reason += Quoted(depFileAbs);
816
            wrk.LogInfo(GenT::MOC, reason);
817 818 819 820
          }
          return true;
        }
        if (!error.empty()) {
821
          wrk.LogError(GenT::MOC, error);
822 823 824 825 826
          return false;
        }
      } else {
        std::string message = "Could not find dependency file ";
        message += Quoted(depFileRel);
827
        wrk.LogFileWarning(GenT::MOC, SourceFile, message);
828
      }
829
    }
830
  }
831 832

  return false;
833 834
}

835
void cmQtAutoGeneratorMocUic::JobMocT::GenerateMoc(WorkerT& wrk)
836
{
837
  // Make sure the parent directory exists
838
  if (wrk.FileSys().MakeParentDirectory(GenT::MOC, BuildFile)) {
839 840 841 842 843 844 845 846
    // Compose moc command
    std::vector<std::string> cmd;
    cmd.push_back(wrk.Moc().Executable);
    // Add options
    cmd.insert(cmd.end(), wrk.Moc().AllOptions.begin(),
               wrk.Moc().AllOptions.end());
    // Add predefs include
    if (!wrk.Moc().PredefsFileAbs.empty()) {
wahikihiki's avatar
wahikihiki committed
847
      cmd.emplace_back("--include");
848 849
      cmd.push_back(wrk.Moc().PredefsFileAbs);
    }
wahikihiki's avatar
wahikihiki committed
850
    cmd.emplace_back("-o");
851 852 853 854 855
    cmd.push_back(BuildFile);
    cmd.push_back(SourceFile);

    // Execute moc command
    ProcessResultT result;
856
    if (wrk.RunProcess(GenT::MOC, result, cmd)) {
857
      // Moc command success
858 859
      // Print moc output
      if (!result.StdOut.empty()) {
860
        wrk.LogInfo(GenT::MOC, result.StdOut);
861 862
      }
      // Notify the generator that a not included file changed (on demand)
863 864 865 866 867 868 869 870 871 872 873 874
      if (IncludeString.empty()) {
        wrk.Gen().ParallelMocAutoUpdated();
      }
    } else {
      // Moc command failed
      {
        std::string emsg = "The moc process failed to compile\n  ";
        emsg += Quoted(SourceFile);
        emsg += "\ninto\n  ";
        emsg += Quoted(BuildFile);
        emsg += ".\n";
        emsg += result.ErrorMessage;
875
        wrk.LogCommandError(GenT::MOC, emsg, cmd, result.StdOut);
876 877
      }
      wrk.FileSys().FileRemove(BuildFile);
878 879
    }
  }
880 881
}

882
void cmQtAutoGeneratorMocUic::JobUicT::Process(WorkerT& wrk)
883
{
884
  // Compute build file name
885 886
  BuildFile = wrk.Base().AutogenIncludeDir;
  BuildFile += '/';
887
  BuildFile += IncludeString;
888

889 890
  if (UpdateRequired(wrk)) {
    GenerateUic(wrk);
891
  }
892
}
893

894 895 896 897 898 899 900 901 902 903 904 905
bool cmQtAutoGeneratorMocUic::JobUicT::UpdateRequired(WorkerT& wrk)
{
  bool const verbose = wrk.Gen().Log().Verbose();

  // Test if the build file exists
  if (!wrk.FileSys().FileExists(BuildFile)) {
    if (verbose) {
      std::string reason = "Generating ";
      reason += Quoted(BuildFile);
      reason += " from its source file ";
      reason += Quoted(SourceFile);
      reason += " because it doesn't exist";
906
      wrk.LogInfo(GenT::UIC, reason);
907
    }
908
    return true;
909
  }
910

911 912 913 914 915 916 917 918
  // Test if the uic settings changed
  if (wrk.Uic().SettingsChanged) {
    if (verbose) {
      std::string reason = "Generating ";
      reason += Quoted(BuildFile);
      reason += " from ";
      reason += Quoted(SourceFile);
      reason += " because the UIC settings changed";
919
      wrk.LogInfo(GenT::UIC, reason);
920 921
    }
    return true;
922
  }
923

924 925 926 927 928 929 930
  // Test if the source file is newer
  {
    bool isOlder = false;
    {
      std::string error;
      isOlder = wrk.FileSys().FileIsOlderThan(BuildFile, SourceFile, &error);
      if (!isOlder && !error.empty()) {
931
        wrk.LogError(GenT::UIC, error);
932 933 934 935 936 937 938 939 940
        return false;
      }
    }
    if (isOlder) {
      if (verbose) {
        std::string reason = "Generating ";
        reason += Quoted(BuildFile);
        reason += " because it's older than its source file ";
        reason += Quoted(SourceFile);
941
        wrk.LogInfo(GenT::UIC, reason);
942 943 944
      }
      return true;
    }
945 946
  }

947
  return false;
948 949
}

950
void cmQtAutoGeneratorMocUic::JobUicT::GenerateUic(WorkerT& wrk)
951
{
952
  // Make sure the parent directory exists
953
  if (wrk.FileSys().MakeParentDirectory(GenT::UIC, BuildFile)) {
954 955 956 957 958 959 960 961 962
    // Compose uic command
    std::vector<std::string> cmd;
    cmd.push_back(wrk.Uic().Executable);
    {
      std::vector<std::string> allOpts = wrk.Uic().TargetOptions;
      auto optionIt = wrk.Uic().Options.find(SourceFile);
      if (optionIt != wrk.Uic().Options.end()) {
        UicMergeOptions(allOpts, optionIt->second,
                        (wrk.Base().QtVersionMajor == 5));
963
      }
964 965
      cmd.insert(cmd.end(), allOpts.begin(), allOpts.end());
    }
wahikihiki's avatar
wahikihiki committed
966
    cmd.emplace_back("-o");
967 968 969 970
    cmd.push_back(BuildFile);
    cmd.push_back(SourceFile);

    ProcessResultT result;
971
    if (wrk.RunProcess(GenT::UIC, result, cmd)) {
972 973 974
      // Uic command success
      // Print uic output
      if (!result.StdOut.empty()) {
975
        wrk.LogInfo(GenT::UIC, result.StdOut);
976
      }
977
    } else {
978
      // Uic command failed
979 980 981 982 983 984 985 986 987
      {
        std::string emsg = "The uic process failed to compile\n  ";
        emsg += Quoted(SourceFile);
        emsg += "\ninto\n  ";
        emsg += Quoted(BuildFile);
        emsg += "\nincluded by\n  ";
        emsg += Quoted(IncluderFile);
        emsg += ".\n";
        emsg += result.ErrorMessage;
988
        wrk.LogCommandError(GenT::UIC, emsg, cmd, result.StdOut);
989 990
      }
      wrk.FileSys().FileRemove(BuildFile);
991 992 993 994
    }
  }
}

995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
cmQtAutoGeneratorMocUic::WorkerT::WorkerT(cmQtAutoGeneratorMocUic* gen,
                                          uv_loop_t* uvLoop)
  : Gen_(gen)
{
  // Initialize uv asynchronous callback for process starting
  ProcessRequest_.init(*uvLoop, &WorkerT::UVProcessStart, this);
  // Start thread
  Thread_ = std::thread(&WorkerT::Loop, this);
}

cmQtAutoGeneratorMocUic::WorkerT::~WorkerT()
{
  // Join thread
  if (Thread_.joinable()) {
    Thread_.join();
1010 1011 1012
  }
}

1013
void cmQtAutoGeneratorMocUic::WorkerT::LogInfo(
1014
  GenT genType, std::string const& message) const
1015
{
1016
  Log().Info(genType, message);
1017 1018 1019
}

void cmQtAutoGeneratorMocUic::WorkerT::LogWarning(
1020
  GenT genType, std::string const& message) const
1021
{
1022
  Log().Warning(genType, message);
1023 1024 1025
}

void cmQtAutoGeneratorMocUic::WorkerT::LogFileWarning(
1026
  GenT genType, std::string const& filename, std::string const& message) const
1027
{
1028
  Log().WarningFile(genType, filename, message);
1029 1030 1031
}

void cmQtAutoGeneratorMocUic::WorkerT::LogError(
1032
  GenT genType, std::string const& message) const
1033 1034 1035 1036 1037 1038