cmGlobalNinjaGenerator.cxx 62.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
4
#include "cmGlobalNinjaGenerator.h"

5
#include "cmAlgorithms.h"
6
#include "cmDocumentationEntry.h"
7
#include "cmFortranParser.h"
8
9
10
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpressionEvaluationFile.h"
#include "cmGeneratorTarget.h"
11
#include "cmLocalGenerator.h"
12
13
#include "cmLocalNinjaGenerator.h"
#include "cmMakefile.h"
14
15
16
17
18
#include "cmOutputConverter.h"
#include "cmState.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetDepend.h"
19
#include "cmVersion.h"
20
#include "cm_auto_ptr.hxx"
21
#include "cmake.h"
22

23
24
25
#include "cm_jsoncpp_reader.h"
#include "cm_jsoncpp_writer.h"

26
#include <algorithm>
27
#include <ctype.h>
28
29
30
31
#include <functional>
#include <iterator>
#include <sstream>
#include <stdio.h>
32

33
34
35
36
37
38
const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE = "rules.ninja";
const char* cmGlobalNinjaGenerator::INDENT = "  ";

void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
{
39
  for (int i = 0; i < count; ++i) {
40
    os << cmGlobalNinjaGenerator::INDENT;
41
  }
42
43
44
45
}

void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
{
46
47
  os << "# ======================================"
     << "=======================================\n";
48
49
50
51
52
}

void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
                                          const std::string& comment)
{
53
  if (comment.empty()) {
54
    return;
55
  }
56
57
58

  std::string::size_type lpos = 0;
  std::string::size_type rpos;
59
  os << "\n#############################################\n";
60
61
  while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
    os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
62
    lpos = rpos + 1;
63
  }
64
  os << "# " << comment.substr(lpos) << "\n\n";
65
66
}

67
68
69
70
71
std::string cmGlobalNinjaGenerator::EncodeRuleName(std::string const& name)
{
  // Ninja rule names must match "[a-zA-Z0-9_.-]+".  Use ".xx" to encode
  // "." and all invalid characters as hexadecimal.
  std::string encoded;
72
73
  for (std::string::const_iterator i = name.begin(); i != name.end(); ++i) {
    if (isalnum(*i) || *i == '_' || *i == '-') {
74
      encoded += *i;
75
    } else {
76
77
78
79
      char buf[16];
      sprintf(buf, ".%02x", static_cast<unsigned int>(*i));
      encoded += buf;
    }
80
  }
81
82
83
  return encoded;
}

84
85
static bool IsIdentChar(char c)
{
86
87
88
  return ('a' <= c && c <= 'z') ||
    ('+' <= c && c <= '9') || // +,-./ and numbers
    ('A' <= c && c <= 'Z') || (c == '_') || (c == '$') || (c == '\\') ||
89
    (c == ' ') || (c == ':');
90
91
}

92
93
94
std::string cmGlobalNinjaGenerator::EncodeIdent(const std::string& ident,
                                                std::ostream& vars)
{
95
96
97
  if (std::find_if(ident.begin(), ident.end(),
                   std::not1(std::ptr_fun(IsIdentChar))) != ident.end()) {
    static unsigned VarNum = 0;
98
    std::ostringstream names;
99
100
101
102
    names << "ident" << VarNum++;
    vars << names.str() << " = " << ident << "\n";
    return "$" + names.str();
  }
103
104
105
106
  std::string result = ident;
  cmSystemTools::ReplaceString(result, " ", "$ ");
  cmSystemTools::ReplaceString(result, ":", "$:");
  return result;
107
108
}

109
std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
110
111
112
{
  std::string result = lit;
  cmSystemTools::ReplaceString(result, "$", "$$");
113
  cmSystemTools::ReplaceString(result, "\n", "$\n");
114
115
116
  return result;
}

117
std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
118
119
120
{
  std::string result = path;
#ifdef _WIN32
121
  if (this->IsGCCOnWindows())
122
    std::replace(result.begin(), result.end(), '\\', '/');
123
  else
124
    std::replace(result.begin(), result.end(), '/', '\\');
125
126
127
128
#endif
  return EncodeLiteral(result);
}

129
std::string cmGlobalNinjaGenerator::EncodeDepfileSpace(const std::string& path)
130
131
132
133
134
135
{
  std::string result = path;
  cmSystemTools::ReplaceString(result, " ", "\\ ");
  return result;
}

136
137
void cmGlobalNinjaGenerator::WriteBuild(
  std::ostream& os, const std::string& comment, const std::string& rule,
138
139
140
141
  const cmNinjaDeps& outputs, const cmNinjaDeps& implicitOuts,
  const cmNinjaDeps& explicitDeps, const cmNinjaDeps& implicitDeps,
  const cmNinjaDeps& orderOnlyDeps, const cmNinjaVars& variables,
  const std::string& rspfile, int cmdLineLimit, bool* usedResponseFile)
142
143
{
  // Make sure there is a rule.
144
  if (rule.empty()) {
145
146
147
148
    cmSystemTools::Error("No rule for WriteBuildStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
149
  }
150
151

  // Make sure there is at least one output file.
152
  if (outputs.empty()) {
153
154
155
156
    cmSystemTools::Error("No output files for WriteBuildStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
157
  }
158
159
160

  cmGlobalNinjaGenerator::WriteComment(os, comment);

161
  std::string arguments;
162
163
164
165

  // TODO: Better formatting for when there are multiple input/output files.

  // Write explicit dependencies.
166
167
  for (cmNinjaDeps::const_iterator i = explicitDeps.begin();
       i != explicitDeps.end(); ++i) {
168
    arguments += " " + EncodeIdent(EncodePath(*i), os);
169
  }
170

171
  // Write implicit dependencies.
172
  if (!implicitDeps.empty()) {
173
    arguments += " |";
174
    for (cmNinjaDeps::const_iterator i = implicitDeps.begin();
175
         i != implicitDeps.end(); ++i) {
176
      arguments += " " + EncodeIdent(EncodePath(*i), os);
177
    }
178
  }
179
180

  // Write order-only dependencies.
181
  if (!orderOnlyDeps.empty()) {
182
    arguments += " ||";
183
    for (cmNinjaDeps::const_iterator i = orderOnlyDeps.begin();
184
         i != orderOnlyDeps.end(); ++i) {
185
      arguments += " " + EncodeIdent(EncodePath(*i), os);
186
    }
187
  }
188

189
  arguments += "\n";
190

191
  std::string build;
192
193

  // Write outputs files.
194
  build += "build";
195
196
  for (cmNinjaDeps::const_iterator i = outputs.begin(); i != outputs.end();
       ++i) {
197
    build += " " + EncodeIdent(EncodePath(*i), os);
198
199
    if (this->ComputingUnknownDependencies) {
      this->CombinedBuildOutputs.insert(EncodePath(*i));
200
    }
201
  }
202
203
204
205
206
207
208
  if (!implicitOuts.empty()) {
    build += " |";
    for (cmNinjaDeps::const_iterator i = implicitOuts.begin();
         i != implicitOuts.end(); ++i) {
      build += " " + EncodeIdent(EncodePath(*i), os);
    }
  }
209
  build += ":";
210

211
  // Write the rule.
212
  build += " " + rule;
213
214

  // Write the variables bound to this build statement.
215
  std::ostringstream variable_assignments;
216
  for (cmNinjaVars::const_iterator i = variables.begin(); i != variables.end();
217
       ++i) {
218
219
    cmGlobalNinjaGenerator::WriteVariable(variable_assignments, i->first,
                                          i->second, "", 1);
220
  }
221
222

  // check if a response file rule should be used
223
  std::string buildstr = build;
224
  std::string assignments = variable_assignments.str();
225
  const std::string& args = arguments;
226
  bool useResponseFile = false;
227
228
  if (cmdLineLimit < 0 ||
      (cmdLineLimit > 0 &&
229
230
       (args.size() + buildstr.size() + assignments.size()) >
         static_cast<size_t>(cmdLineLimit))) {
231
    variable_assignments.str(std::string());
232
233
    cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
                                          rspfile, "", 1);
234
    assignments += variable_assignments.str();
235
    useResponseFile = true;
236
  }
237
  if (usedResponseFile) {
238
    *usedResponseFile = useResponseFile;
239
  }
240

241
  os << buildstr << args << assignments;
242
243
}

244
245
246
247
void cmGlobalNinjaGenerator::WritePhonyBuild(
  std::ostream& os, const std::string& comment, const cmNinjaDeps& outputs,
  const cmNinjaDeps& explicitDeps, const cmNinjaDeps& implicitDeps,
  const cmNinjaDeps& orderOnlyDeps, const cmNinjaVars& variables)
248
{
249
250
  this->WriteBuild(os, comment, "phony", outputs,
                   /*implicitOuts=*/cmNinjaDeps(), explicitDeps, implicitDeps,
251
                   orderOnlyDeps, variables);
252
253
254
255
}

void cmGlobalNinjaGenerator::AddCustomCommandRule()
{
256
  this->AddRule("CUSTOM_COMMAND", "$COMMAND", "$DESC",
257
258
                "Rule for running custom commands.",
                /*depfile*/ "",
259
                /*deptype*/ "",
260
                /*rspfile*/ "",
261
                /*rspcontent*/ "",
262
                /*restat*/ "", // bound on each build statement as needed
263
                /*generator*/ false);
264
265
}

266
267
void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
  const std::string& command, const std::string& description,
268
269
  const std::string& comment, const std::string& depfile, bool uses_terminal,
  bool restat, const cmNinjaDeps& outputs, const cmNinjaDeps& deps,
270
  const cmNinjaDeps& orderOnly)
271
{
272
273
  std::string cmd = command;
#ifdef _WIN32
274
275
276
  if (cmd.empty())
    // TODO Shouldn't an empty command be handled by ninja?
    cmd = "cmd.exe /c";
277
278
#endif

279
280
281
  this->AddCustomCommandRule();

  cmNinjaVars vars;
282
  vars["COMMAND"] = cmd;
283
  vars["DESC"] = EncodeLiteral(description);
284
  if (restat) {
285
    vars["restat"] = "1";
286
287
  }
  if (uses_terminal && SupportsConsolePool()) {
288
    vars["pool"] = "console";
289
  }
290
291
292
  if (!depfile.empty()) {
    vars["depfile"] = depfile;
  }
293
  this->WriteBuild(*this->BuildFileStream, comment, "CUSTOM_COMMAND", outputs,
294
295
                   /*implicitOuts=*/cmNinjaDeps(), deps, cmNinjaDeps(),
                   orderOnly, vars);
296
297
298
299
300

  if (this->ComputingUnknownDependencies) {
    // we need to track every dependency that comes in, since we are trying
    // to find dependencies that are side effects of build commands
    for (cmNinjaDeps::const_iterator i = deps.begin(); i != deps.end(); ++i) {
301
      this->CombinedCustomCommandExplicitDependencies.insert(EncodePath(*i));
302
    }
303
  }
304
305
}

306
void cmGlobalNinjaGenerator::AddMacOSXContentRule()
307
{
308
  cmLocalGenerator* lg = this->LocalGenerators[0];
309

310
  std::ostringstream cmd;
311
  cmd << lg->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
312
                                   cmOutputConverter::SHELL)
313
314
      << " -E copy $in $out";

315
  this->AddRule("COPY_OSX_CONTENT", cmd.str(), "Copying OS X Content $out",
316
                "Rule for copying OS X bundle content file.",
317
                /*depfile*/ "",
318
319
320
                /*deptype*/ "",
                /*rspfile*/ "",
                /*rspcontent*/ "",
321
                /*restat*/ "",
322
                /*generator*/ false);
323
324
}

325
326
void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(const std::string& input,
                                                     const std::string& output)
327
328
329
330
331
332
333
334
335
{
  this->AddMacOSXContentRule();

  cmNinjaDeps outputs;
  outputs.push_back(output);
  cmNinjaDeps deps;
  deps.push_back(input);
  cmNinjaVars vars;

336
  this->WriteBuild(*this->BuildFileStream, "", "COPY_OSX_CONTENT", outputs,
337
338
                   /*implicitOuts=*/cmNinjaDeps(), deps, cmNinjaDeps(),
                   cmNinjaDeps(), cmNinjaVars());
339
340
}

341
342
343
344
345
346
void cmGlobalNinjaGenerator::WriteRule(
  std::ostream& os, const std::string& name, const std::string& command,
  const std::string& description, const std::string& comment,
  const std::string& depfile, const std::string& deptype,
  const std::string& rspfile, const std::string& rspcontent,
  const std::string& restat, bool generator)
347
348
{
  // Make sure the rule has a name.
349
  if (name.empty()) {
350
351
352
353
    cmSystemTools::Error("No name given for WriteRuleStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
354
  }
355
356

  // Make sure a command is given.
357
  if (command.empty()) {
358
359
360
361
    cmSystemTools::Error("No command given for WriteRuleStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
362
  }
363
364
365
366
367
368
369

  cmGlobalNinjaGenerator::WriteComment(os, comment);

  // Write the rule.
  os << "rule " << name << "\n";

  // Write the depfile if any.
370
  if (!depfile.empty()) {
371
372
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "depfile = " << depfile << "\n";
373
  }
374

375
  // Write the deptype if any.
376
  if (!deptype.empty()) {
377
378
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "deps = " << deptype << "\n";
379
  }
380

381
382
383
384
385
  // Write the command.
  cmGlobalNinjaGenerator::Indent(os, 1);
  os << "command = " << command << "\n";

  // Write the description if any.
386
  if (!description.empty()) {
387
388
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "description = " << description << "\n";
389
  }
390

391
392
  if (!rspfile.empty()) {
    if (rspcontent.empty()) {
393
394
      cmSystemTools::Error("No rspfile_content given!", comment.c_str());
      return;
395
    }
396
397
398
399
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "rspfile = " << rspfile << "\n";
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "rspfile_content = " << rspcontent << "\n";
400
  }
401

402
  if (!restat.empty()) {
403
    cmGlobalNinjaGenerator::Indent(os, 1);
404
    os << "restat = " << restat << "\n";
405
  }
406

407
  if (generator) {
408
409
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "generator = 1\n";
410
  }
411
412

  os << "\n";
413
414
415
416
417
418
419
420
421
}

void cmGlobalNinjaGenerator::WriteVariable(std::ostream& os,
                                           const std::string& name,
                                           const std::string& value,
                                           const std::string& comment,
                                           int indent)
{
  // Make sure we have a name.
422
  if (name.empty()) {
423
424
425
426
    cmSystemTools::Error("No name given for WriteVariable! called "
                         "with comment: ",
                         comment.c_str());
    return;
427
  }
428
429
430

  // Do not add a variable if the value is empty.
  std::string val = cmSystemTools::TrimWhitespace(value);
431
  if (val.empty()) {
432
    return;
433
  }
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

  cmGlobalNinjaGenerator::WriteComment(os, comment);
  cmGlobalNinjaGenerator::Indent(os, indent);
  os << name << " = " << val << "\n";
}

void cmGlobalNinjaGenerator::WriteInclude(std::ostream& os,
                                          const std::string& filename,
                                          const std::string& comment)
{
  cmGlobalNinjaGenerator::WriteComment(os, comment);
  os << "include " << filename << "\n";
}

void cmGlobalNinjaGenerator::WriteDefault(std::ostream& os,
                                          const cmNinjaDeps& targets,
                                          const std::string& comment)
{
  cmGlobalNinjaGenerator::WriteComment(os, comment);
  os << "default";
454
  for (cmNinjaDeps::const_iterator i = targets.begin(); i != targets.end();
455
       ++i) {
456
    os << " " << *i;
457
  }
458
459
460
  os << "\n";
}

461
cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
462
  : cmGlobalCommonGenerator(cm)
Daniel Pfeifer's avatar
Daniel Pfeifer committed
463
464
465
  , BuildFileStream(CM_NULLPTR)
  , RulesFileStream(CM_NULLPTR)
  , CompileCommandsStream(CM_NULLPTR)
466
467
  , Rules()
  , AllDependencies()
468
  , UsingGCCOnWindows(false)
469
470
  , ComputingUnknownDependencies(false)
  , PolicyCMP0058(cmPolicies::WARN)
471
472
  , NinjaSupportsConsolePool(false)
  , NinjaSupportsImplicitOuts(false)
473
  , NinjaSupportsDyndeps(0)
474
{
475
#ifdef _WIN32
476
  cm->GetState()->SetWindowsShell(true);
477
#endif
478
479
480
481
482
483
484
  // // Ninja is not ported to non-Unix OS yet.
  // this->ForceUnixPaths = true;
  this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
}

// Virtual public methods.

485
cmLocalGenerator* cmGlobalNinjaGenerator::CreateLocalGenerator(cmMakefile* mf)
486
{
487
  return new cmLocalNinjaGenerator(this, mf);
488
489
}

490
void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry)
491
{
492
  entry.Name = cmGlobalNinjaGenerator::GetActualName();
493
  entry.Brief = "Generates build.ninja files.";
494
495
496
497
498
499
500
501
}

// Implemented in all cmGlobaleGenerator sub-classes.
// Used in:
//   Source/cmLocalGenerator.cxx
//   Source/cmake.cxx
void cmGlobalNinjaGenerator::Generate()
{
502
503
  // Check minimum Ninja version.
  if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS,
504
                                    this->NinjaVersion.c_str(),
505
                                    RequiredNinjaVersion().c_str())) {
506
    std::ostringstream msg;
507
    msg << "The detected version of Ninja (" << this->NinjaVersion;
508
509
510
511
    msg << ") is less than the version of Ninja required by CMake (";
    msg << this->RequiredNinjaVersion() << ").";
    this->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, msg.str());
    return;
512
  }
513
514
515
  this->OpenBuildFileStream();
  this->OpenRulesFileStream();

516
517
  this->TargetDependsClosures.clear();

518
  this->InitOutputPathPrefix();
519
520
  this->TargetAll = this->NinjaOutputPath("all");
  this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
521

522
  this->PolicyCMP0058 =
523
524
    this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
      cmPolicies::CMP0058);
525
526
527
528
  this->ComputingUnknownDependencies =
    (this->PolicyCMP0058 == cmPolicies::OLD ||
     this->PolicyCMP0058 == cmPolicies::WARN);

529
530
  this->cmGlobalGenerator::Generate();

531
  this->WriteAssumedSourceDependencies();
532
  this->WriteTargetAliases(*this->BuildFileStream);
533
  this->WriteFolderTargets(*this->BuildFileStream);
534
  this->WriteUnknownExplicitDependencies(*this->BuildFileStream);
535
536
  this->WriteBuiltinTargets(*this->BuildFileStream);

537
  if (cmSystemTools::GetErrorOccuredFlag()) {
538
539
    this->RulesFileStream->setstate(std::ios::failbit);
    this->BuildFileStream->setstate(std::ios::failbit);
540
541
  }

542
  this->CloseCompileCommandsStream();
543
544
545
546
  this->CloseRulesFileStream();
  this->CloseBuildFileStream();
}

547
548
549
void cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
{
  this->cmGlobalGenerator::FindMakeProgram(mf);
550
  if (const char* ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
551
    this->NinjaCommand = ninjaCommand;
552
553
554
555
    std::vector<std::string> command;
    command.push_back(this->NinjaCommand);
    command.push_back("--version");
    std::string version;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
556
557
    cmSystemTools::RunSingleCommand(command, &version, CM_NULLPTR, CM_NULLPTR,
                                    CM_NULLPTR, cmSystemTools::OUTPUT_NONE);
558
    this->NinjaVersion = cmSystemTools::TrimWhitespace(version);
559
    this->CheckNinjaFeatures();
560
  }
561
562
}

563
564
565
566
567
568
569
570
void cmGlobalNinjaGenerator::CheckNinjaFeatures()
{
  this->NinjaSupportsConsolePool = !cmSystemTools::VersionCompare(
    cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
    RequiredNinjaVersionForConsolePool().c_str());
  this->NinjaSupportsImplicitOuts = !cmSystemTools::VersionCompare(
    cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
    this->RequiredNinjaVersionForImplicitOuts().c_str());
571
572
573
574
575
576
577
578
579
580
  {
    // Our ninja branch adds ".dyndep-#" to its version number,
    // where '#' is a feature-specific version number.  Extract it.
    static std::string const k_DYNDEP_ = ".dyndep-";
    std::string::size_type pos = this->NinjaVersion.find(k_DYNDEP_);
    if (pos != std::string::npos) {
      const char* fv = this->NinjaVersion.c_str() + pos + k_DYNDEP_.size();
      cmSystemTools::StringToULong(fv, &this->NinjaSupportsDyndeps);
    }
  }
581
582
}

583
584
585
586
587
bool cmGlobalNinjaGenerator::CheckLanguages(
  std::vector<std::string> const& languages, cmMakefile* mf) const
{
  if (std::find(languages.begin(), languages.end(), "Fortran") !=
      languages.end()) {
588
    return this->CheckFortran(mf);
589
590
591
592
  }
  return true;
}

593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
{
  if (this->NinjaSupportsDyndeps == 1) {
    return true;
  }

  std::ostringstream e;
  if (this->NinjaSupportsDyndeps == 0) {
    /* clang-format off */
    e <<
      "The Ninja generator does not support Fortran using Ninja version\n"
      "  " + this->NinjaVersion + "\n"
      "due to lack of required features.  "
      "Kitware has implemented the required features but as of this version "
      "of CMake they have not been integrated to upstream ninja.  "
      "Pending integration, Kitware maintains a branch at:\n"
      "  https://github.com/Kitware/ninja/tree/features-for-fortran#readme\n"
      "with the required features.  "
      "One may build ninja from that branch to get support for Fortran."
      ;
    /* clang-format on */
  } else {
    /* clang-format off */
    e <<
      "The Ninja generator in this version of CMake does not support Fortran "
      "using Ninja version\n"
      "  " + this->NinjaVersion + "\n"
      "because its 'dyndep' feature version is " <<
      this->NinjaSupportsDyndeps << ".  "
      "This version of CMake is aware only of 'dyndep' feature version 1."
      ;
    /* clang-format on */
  }
  mf->IssueMessage(cmake::FATAL_ERROR, e.str());
  cmSystemTools::SetFatalErrorOccured();
  return false;
}

631
632
void cmGlobalNinjaGenerator::EnableLanguage(
  std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
633
{
634
  this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
635
636
637
  for (std::vector<std::string>::const_iterator l = langs.begin();
       l != langs.end(); ++l) {
    if (*l == "NONE") {
638
639
      continue;
    }
640
641
    this->ResolveLanguageCompiler(*l, mf, optional);
  }
642
#ifdef _WIN32
643
644
645
646
647
648
649
  if (strcmp(mf->GetSafeDefinition("CMAKE_C_SIMULATE_ID"), "MSVC") != 0 &&
      strcmp(mf->GetSafeDefinition("CMAKE_CXX_SIMULATE_ID"), "MSVC") != 0 &&
      (mf->IsOn("CMAKE_COMPILER_IS_MINGW") ||
       strcmp(mf->GetSafeDefinition("CMAKE_C_COMPILER_ID"), "GNU") == 0 ||
       strcmp(mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID"), "GNU") == 0 ||
       strcmp(mf->GetSafeDefinition("CMAKE_C_COMPILER_ID"), "Clang") == 0 ||
       strcmp(mf->GetSafeDefinition("CMAKE_CXX_COMPILER_ID"), "Clang") == 0)) {
650
    this->UsingGCCOnWindows = true;
651
  }
652
#endif
653
654
655
656
}

// Implemented by:
//   cmGlobalUnixMakefileGenerator3
657
//   cmGlobalGhsMultiGenerator
658
659
660
661
662
//   cmGlobalVisualStudio10Generator
//   cmGlobalVisualStudio7Generator
//   cmGlobalXCodeGenerator
// Called by:
//   cmGlobalGenerator::Build()
663
664
665
666
667
void cmGlobalNinjaGenerator::GenerateBuildCommand(
  std::vector<std::string>& makeCommand, const std::string& makeProgram,
  const std::string& /*projectName*/, const std::string& /*projectDir*/,
  const std::string& targetName, const std::string& /*config*/, bool /*fast*/,
  bool verbose, std::vector<std::string> const& makeOptions)
668
{
669
  makeCommand.push_back(this->SelectMakeProgram(makeProgram));
670

671
  if (verbose) {
672
    makeCommand.push_back("-v");
673
  }
674

675
676
677
678
  makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                     makeOptions.end());
  if (!targetName.empty()) {
    if (targetName == "clean") {
679
680
      makeCommand.push_back("-t");
      makeCommand.push_back("clean");
681
    } else {
682
      makeCommand.push_back(targetName);
683
    }
684
  }
685
686
687
688
}

// Non-virtual public methods.

689
690
691
692
693
694
void cmGlobalNinjaGenerator::AddRule(
  const std::string& name, const std::string& command,
  const std::string& description, const std::string& comment,
  const std::string& depfile, const std::string& deptype,
  const std::string& rspfile, const std::string& rspcontent,
  const std::string& restat, bool generator)
695
696
{
  // Do not add the same rule twice.
697
  if (this->HasRule(name)) {
698
    return;
699
  }
700
701

  this->Rules.insert(name);
702
703
704
705
706
  cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, name, command,
                                    description, comment, depfile, deptype,
                                    rspfile, rspcontent, restat, generator);

  this->RuleCmdLength[name] = (int)command.size();
707
708
}

709
bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
710
711
712
713
714
{
  RulesSetType::const_iterator rule = this->Rules.find(name);
  return (rule != this->Rules.end());
}

715
716
// Private virtual overrides

717
718
719
720
721
722
723
std::string cmGlobalNinjaGenerator::GetEditCacheCommand() const
{
  // Ninja by design does not run interactive tools in the terminal,
  // so our only choice is cmake-gui.
  return cmSystemTools::GetCMakeGUICommand();
}

724
725
void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
  cmGeneratorTarget* gt) const
726
727
{
  // Compute full path to object file directory for this target.
728
  std::string dir;
729
  dir += gt->LocalGenerator->GetCurrentBinaryDirectory();
730
  dir += "/";
731
  dir += gt->LocalGenerator->GetTargetDirectory(gt);
732
733
  dir += "/";
  gt->ObjectDirectory = dir;
734
735
}

736
737
738
739
740
741
742
743
744
745
746
// Private methods

void cmGlobalNinjaGenerator::OpenBuildFileStream()
{
  // Compute Ninja's build file path.
  std::string buildFilePath =
    this->GetCMakeInstance()->GetHomeOutputDirectory();
  buildFilePath += "/";
  buildFilePath += cmGlobalNinjaGenerator::NINJA_BUILD_FILE;

  // Get a stream where to generate things.
747
  if (!this->BuildFileStream) {
748
    this->BuildFileStream = new cmGeneratedFileStream(buildFilePath.c_str());
749
    if (!this->BuildFileStream) {
750
751
752
753
      // An error message is generated by the constructor if it cannot
      // open the file.
      return;
    }
754
  }
755
756
757
758
759
760
761

  // Write the do not edit header.
  this->WriteDisclaimer(*this->BuildFileStream);

  // Write a comment about this file.
  *this->BuildFileStream
    << "# This file contains all the build statements describing the\n"
762
    << "# compilation DAG.\n\n";
763
764
765
766
}

void cmGlobalNinjaGenerator::CloseBuildFileStream()
{
767
  if (this->BuildFileStream) {
768
    delete this->BuildFileStream;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
769
    this->BuildFileStream = CM_NULLPTR;
770
  } else {
771
    cmSystemTools::Error("Build file stream was not open.");
772
  }
773
774
775
776
777
778
779
780
781
782
783
}

void cmGlobalNinjaGenerator::OpenRulesFileStream()
{
  // Compute Ninja's build file path.
  std::string rulesFilePath =
    this->GetCMakeInstance()->GetHomeOutputDirectory();
  rulesFilePath += "/";
  rulesFilePath += cmGlobalNinjaGenerator::NINJA_RULES_FILE;

  // Get a stream where to generate things.
784
  if (!this->RulesFileStream) {
785
    this->RulesFileStream = new cmGeneratedFileStream(rulesFilePath.c_str());
786
    if (!this->RulesFileStream) {
787
788
789
790
      // An error message is generated by the constructor if it cannot
      // open the file.
      return;
    }
791
  }
792
793
794
795
796

  // Write the do not edit header.
  this->WriteDisclaimer(*this->RulesFileStream);

  // Write comment about this file.
797
  /* clang-format off */
798
799
800
801
802
  *this->RulesFileStream
    << "# This file contains all the rules used to get the outputs files\n"
    << "# built from the input files.\n"
    << "# It is included in the main '" << NINJA_BUILD_FILE << "'.\n\n"
    ;
803
  /* clang-format on */
804
805
806
807
}

void cmGlobalNinjaGenerator::CloseRulesFileStream()
{
808
  if (this->RulesFileStream) {
809
    delete this->RulesFileStream;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
810
    this->RulesFileStream = CM_NULLPTR;
811
  } else {
812
    cmSystemTools::Error("Rules file stream was not open.");
813
  }
814
815
}

816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
static void EnsureTrailingSlash(std::string& path)
{
  if (path.empty()) {
    return;
  }
  std::string::value_type last = path[path.size() - 1];
#ifdef _WIN32
  if (last != '\\') {
    path += '\\';
  }
#else
  if (last != '/') {
    path += '/';
  }
#endif
}

Stephen Kelly's avatar
Stephen Kelly committed
833
834
std::string cmGlobalNinjaGenerator::ConvertToNinjaPath(
  const std::string& path) const
835
{
836
837
  cmLocalNinjaGenerator* ng =
    static_cast<cmLocalNinjaGenerator*>(this->LocalGenerators[0]);
838
839
  std::string convPath = ng->ConvertToRelativePath(
    this->LocalGenerators[0]->GetState()->GetBinaryDirectory(), path);
840
  convPath = this->NinjaOutputPath(convPath);
841
#ifdef _WIN32
842
  std::replace(convPath.begin(), convPath.end(), '/', '\\');
843
844
845
846
#endif
  return convPath;
}

847
848
std::string cmGlobalNinjaGenerator::ConvertToNinjaFolderRule(
  const std::string& path)
849
{
850
851
  cmLocalNinjaGenerator* ng =
    static_cast<cmLocalNinjaGenerator*>(this->LocalGenerators[0]);
852
853
  std::string convPath = ng->ConvertToRelativePath(
    this->LocalGenerators[0]->GetState()->GetSourceDirectory(), path + "/all");
854
  convPath = this->NinjaOutputPath(convPath);
855
#ifdef _WIN32
856
  std::replace(convPath.begin(), convPath.end(), '/', '\\');
857
858
859
860
#endif
  return convPath;
}

861
void cmGlobalNinjaGenerator::AddCXXCompileCommand(
862
  const std::string& commandLine, const std::string& sourceFile)
863
864
865
866
{
  // Compute Ninja's build file path.
  std::string buildFileDir =
    this->GetCMakeInstance()->GetHomeOutputDirectory();
867
  if (!this->CompileCommandsStream) {
868
869
870
871
872
873
    std::string buildFilePath = buildFileDir + "/compile_commands.json";

    // Get a stream where to generate things.
    this->CompileCommandsStream =
      new cmGeneratedFileStream(buildFilePath.c_str());
    *this->CompileCommandsStream << "[";
874
  } else {
875
    *this->CompileCommandsStream << "," << std::endl;
876
  }
877

878
  std::string sourceFileName = sourceFile;
879
  if (!cmSystemTools::FileIsFullPath(sourceFileName.c_str())) {
880
    sourceFileName = cmSystemTools::CollapseFullPath(
881
882
      sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
  }
883

884
  /* clang-format off */
885
886
887
888
889
890
  *this->CompileCommandsStream << "\n{\n"
     << "  \"directory\": \""
     << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
     << "  \"command\": \""
     << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
     << "  \"file\": \""
891
     << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n"
892
     << "}";
893
  /* clang-format on */
894
895
896
897
}

void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
{
898
  if (this->CompileCommandsStream) {
899
900
    *this->CompileCommandsStream << "\n]";
    delete this->CompileCommandsStream;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
901
    this->CompileCommandsStream = CM_NULLPTR;
902
  }
903
904
}

905
906
void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os)
{
907
908
909
910
  os << "# CMAKE generated file: DO NOT EDIT!\n"
     << "# Generated by \"" << this->GetName() << "\""
     << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
     << cmVersion::GetMinorVersion() << "\n\n";
911
912
}

913
void cmGlobalNinjaGenerator::AddDependencyToAll(cmGeneratorTarget* target)
914
915
916
917
{
  this->AppendTargetOutputs(target, this->AllDependencies);
}

918
919
920
921
922
void cmGlobalNinjaGenerator::AddDependencyToAll(const std::string& input)
{
  this->AllDependencies.push_back(input);
}

923
void cmGlobalNinjaGenerator::WriteAssumedSourceDependencies()
924
{
925
926
  for (std::map<std::string, std::set<std::string> >::iterator i =
         this->AssumedSourceDependencies.begin();
927
       i != this->AssumedSourceDependencies.end(); ++i) {
928
929
    cmNinjaDeps deps;
    std::copy(i->second.begin(), i->second.end(), std::back_inserter(deps));
930
931
    WriteCustomCommandBuild(/*command=*/"", /*description=*/"",
                            "Assume dependencies for generated source file.",
932
                            /*depfile*/ "", /*uses_terminal*/ false,
933
                            /*restat*/ true, cmNinjaDeps(1, i->first), deps);
934
935
936
  }
}

937
938
void cmGlobalNinjaGenerator::AppendTargetOutputs(
  cmGeneratorTarget const* target, cmNinjaDeps& outputs)
939
{
940
  std::string configName =
941
    target->Target->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");