cmGlobalNinjaGenerator.cxx 64.2 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
6
7
8
#include "cm_jsoncpp_reader.h"
#include "cm_jsoncpp_value.h"
#include "cm_jsoncpp_writer.h"
#include "cmsys/FStream.hxx"
9
10
11
12
13
14
15
#include <algorithm>
#include <ctype.h>
#include <functional>
#include <iterator>
#include <sstream>
#include <stdio.h>

16
#include "cmAlgorithms.h"
17
#include "cmDocumentationEntry.h"
18
#include "cmFortranParser.h"
19
20
21
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpressionEvaluationFile.h"
#include "cmGeneratorTarget.h"
22
#include "cmLocalGenerator.h"
23
24
#include "cmLocalNinjaGenerator.h"
#include "cmMakefile.h"
25
#include "cmNinjaLinkLineComputer.h"
26
#include "cmOutputConverter.h"
27
28
29
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
30
#include "cmStateTypes.h"
31
32
33
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetDepend.h"
34
#include "cmVersion.h"
35
#include "cm_auto_ptr.hxx"
36
#include "cmake.h"
37

38
class cmLinkLineComputer;
39

40
41
42
const char* cmGlobalNinjaGenerator::NINJA_BUILD_FILE = "build.ninja";
const char* cmGlobalNinjaGenerator::NINJA_RULES_FILE = "rules.ninja";
const char* cmGlobalNinjaGenerator::INDENT = "  ";
43
44
45
46
47
#ifdef _WIN32
std::string const cmGlobalNinjaGenerator::SHELL_NOOP = "cd .";
#else
std::string const cmGlobalNinjaGenerator::SHELL_NOOP = ":";
#endif
48
49
50

void cmGlobalNinjaGenerator::Indent(std::ostream& os, int count)
{
51
  for (int i = 0; i < count; ++i) {
52
    os << cmGlobalNinjaGenerator::INDENT;
53
  }
54
55
56
57
}

void cmGlobalNinjaGenerator::WriteDivider(std::ostream& os)
{
58
  os << "# ======================================"
59
        "=======================================\n";
60
61
62
63
64
}

void cmGlobalNinjaGenerator::WriteComment(std::ostream& os,
                                          const std::string& comment)
{
65
  if (comment.empty()) {
66
    return;
67
  }
68
69
70

  std::string::size_type lpos = 0;
  std::string::size_type rpos;
71
  os << "\n#############################################\n";
72
73
  while ((rpos = comment.find('\n', lpos)) != std::string::npos) {
    os << "# " << comment.substr(lpos, rpos - lpos) << "\n";
74
    lpos = rpos + 1;
75
  }
76
  os << "# " << comment.substr(lpos) << "\n\n";
77
78
}

79
cmLinkLineComputer* cmGlobalNinjaGenerator::CreateLinkLineComputer(
80
  cmOutputConverter* outputConverter, cmStateDirectory /* stateDir */) const
81
82
{
  return new cmNinjaLinkLineComputer(
83
    outputConverter,
84
85
86
    this->LocalGenerators[0]->GetStateSnapshot().GetDirectory(), this);
}

87
88
89
90
91
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;
92
93
  for (std::string::const_iterator i = name.begin(); i != name.end(); ++i) {
    if (isalnum(*i) || *i == '_' || *i == '-') {
94
      encoded += *i;
95
    } else {
96
97
98
99
      char buf[16];
      sprintf(buf, ".%02x", static_cast<unsigned int>(*i));
      encoded += buf;
    }
100
  }
101
102
103
  return encoded;
}

104
105
static bool IsIdentChar(char c)
{
106
107
108
  return ('a' <= c && c <= 'z') ||
    ('+' <= c && c <= '9') || // +,-./ and numbers
    ('A' <= c && c <= 'Z') || (c == '_') || (c == '$') || (c == '\\') ||
109
    (c == ' ') || (c == ':');
110
111
}

112
113
114
std::string cmGlobalNinjaGenerator::EncodeIdent(const std::string& ident,
                                                std::ostream& vars)
{
115
116
117
  if (std::find_if(ident.begin(), ident.end(),
                   std::not1(std::ptr_fun(IsIdentChar))) != ident.end()) {
    static unsigned VarNum = 0;
118
    std::ostringstream names;
119
120
121
122
    names << "ident" << VarNum++;
    vars << names.str() << " = " << ident << "\n";
    return "$" + names.str();
  }
123
124
125
126
  std::string result = ident;
  cmSystemTools::ReplaceString(result, " ", "$ ");
  cmSystemTools::ReplaceString(result, ":", "$:");
  return result;
127
128
}

129
std::string cmGlobalNinjaGenerator::EncodeLiteral(const std::string& lit)
130
131
132
{
  std::string result = lit;
  cmSystemTools::ReplaceString(result, "$", "$$");
133
  cmSystemTools::ReplaceString(result, "\n", "$\n");
134
135
136
  return result;
}

137
std::string cmGlobalNinjaGenerator::EncodePath(const std::string& path)
138
{
139
  std::string result = path; // NOLINT(clang-tidy)
140
#ifdef _WIN32
141
  if (this->IsGCCOnWindows())
142
    std::replace(result.begin(), result.end(), '\\', '/');
143
  else
144
    std::replace(result.begin(), result.end(), '/', '\\');
145
146
147
148
#endif
  return EncodeLiteral(result);
}

149
150
void cmGlobalNinjaGenerator::WriteBuild(
  std::ostream& os, const std::string& comment, const std::string& rule,
151
152
153
154
  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)
155
156
{
  // Make sure there is a rule.
157
  if (rule.empty()) {
158
159
160
161
    cmSystemTools::Error("No rule for WriteBuildStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
162
  }
163
164

  // Make sure there is at least one output file.
165
  if (outputs.empty()) {
166
167
168
169
    cmSystemTools::Error("No output files for WriteBuildStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
170
  }
171
172
173

  cmGlobalNinjaGenerator::WriteComment(os, comment);

174
  std::string arguments;
175
176
177
178

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

  // Write explicit dependencies.
179
180
  for (cmNinjaDeps::const_iterator i = explicitDeps.begin();
       i != explicitDeps.end(); ++i) {
181
    arguments += " " + EncodeIdent(EncodePath(*i), os);
182
  }
183

184
  // Write implicit dependencies.
185
  if (!implicitDeps.empty()) {
186
    arguments += " |";
187
    for (cmNinjaDeps::const_iterator i = implicitDeps.begin();
188
         i != implicitDeps.end(); ++i) {
189
      arguments += " " + EncodeIdent(EncodePath(*i), os);
190
    }
191
  }
192
193

  // Write order-only dependencies.
194
  if (!orderOnlyDeps.empty()) {
195
    arguments += " ||";
196
    for (cmNinjaDeps::const_iterator i = orderOnlyDeps.begin();
197
         i != orderOnlyDeps.end(); ++i) {
198
      arguments += " " + EncodeIdent(EncodePath(*i), os);
199
    }
200
  }
201

202
  arguments += "\n";
203

204
  std::string build;
205
206

  // Write outputs files.
207
  build += "build";
208
209
  for (cmNinjaDeps::const_iterator i = outputs.begin(); i != outputs.end();
       ++i) {
210
    build += " " + EncodeIdent(EncodePath(*i), os);
211
212
    if (this->ComputingUnknownDependencies) {
      this->CombinedBuildOutputs.insert(EncodePath(*i));
213
    }
214
  }
215
216
217
218
219
220
221
  if (!implicitOuts.empty()) {
    build += " |";
    for (cmNinjaDeps::const_iterator i = implicitOuts.begin();
         i != implicitOuts.end(); ++i) {
      build += " " + EncodeIdent(EncodePath(*i), os);
    }
  }
222
  build += ":";
223

224
  // Write the rule.
225
  build += " " + rule;
226
227

  // Write the variables bound to this build statement.
228
  std::ostringstream variable_assignments;
229
  for (cmNinjaVars::const_iterator i = variables.begin(); i != variables.end();
230
       ++i) {
231
232
    cmGlobalNinjaGenerator::WriteVariable(variable_assignments, i->first,
                                          i->second, "", 1);
233
  }
234
235

  // check if a response file rule should be used
236
  std::string buildstr = build;
237
  std::string assignments = variable_assignments.str();
238
  const std::string& args = arguments;
239
  bool useResponseFile = false;
240
241
  if (cmdLineLimit < 0 ||
      (cmdLineLimit > 0 &&
242
       (args.size() + buildstr.size() + assignments.size() + 1000) >
243
         static_cast<size_t>(cmdLineLimit))) {
244
    variable_assignments.str(std::string());
245
246
    cmGlobalNinjaGenerator::WriteVariable(variable_assignments, "RSP_FILE",
                                          rspfile, "", 1);
247
    assignments += variable_assignments.str();
248
    useResponseFile = true;
249
  }
250
  if (usedResponseFile) {
251
    *usedResponseFile = useResponseFile;
252
  }
253

254
  os << buildstr << args << assignments;
255
256
}

257
258
259
260
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)
261
{
262
263
  this->WriteBuild(os, comment, "phony", outputs,
                   /*implicitOuts=*/cmNinjaDeps(), explicitDeps, implicitDeps,
264
                   orderOnlyDeps, variables);
265
266
267
268
}

void cmGlobalNinjaGenerator::AddCustomCommandRule()
{
269
  this->AddRule("CUSTOM_COMMAND", "$COMMAND", "$DESC",
270
271
                "Rule for running custom commands.",
                /*depfile*/ "",
272
                /*deptype*/ "",
273
                /*rspfile*/ "",
274
                /*rspcontent*/ "",
275
                /*restat*/ "", // bound on each build statement as needed
276
                /*generator*/ false);
277
278
}

279
280
void cmGlobalNinjaGenerator::WriteCustomCommandBuild(
  const std::string& command, const std::string& description,
281
282
  const std::string& comment, const std::string& depfile, bool uses_terminal,
  bool restat, const cmNinjaDeps& outputs, const cmNinjaDeps& deps,
283
  const cmNinjaDeps& orderOnly)
284
{
285
  std::string cmd = command; // NOLINT(clang-tidy)
286
#ifdef _WIN32
287
288
289
  if (cmd.empty())
    // TODO Shouldn't an empty command be handled by ninja?
    cmd = "cmd.exe /c";
290
291
#endif

292
293
294
  this->AddCustomCommandRule();

  cmNinjaVars vars;
295
  vars["COMMAND"] = cmd;
296
  vars["DESC"] = EncodeLiteral(description);
297
  if (restat) {
298
    vars["restat"] = "1";
299
300
  }
  if (uses_terminal && SupportsConsolePool()) {
301
    vars["pool"] = "console";
302
  }
303
304
305
  if (!depfile.empty()) {
    vars["depfile"] = depfile;
  }
306
  this->WriteBuild(*this->BuildFileStream, comment, "CUSTOM_COMMAND", outputs,
307
308
                   /*implicitOuts=*/cmNinjaDeps(), deps, cmNinjaDeps(),
                   orderOnly, vars);
309
310
311
312
313

  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) {
314
      this->CombinedCustomCommandExplicitDependencies.insert(EncodePath(*i));
315
    }
316
  }
317
318
}

319
void cmGlobalNinjaGenerator::AddMacOSXContentRule()
320
{
321
  cmLocalGenerator* lg = this->LocalGenerators[0];
322

323
  std::ostringstream cmd;
324
  cmd << lg->ConvertToOutputFormat(cmSystemTools::GetCMakeCommand(),
325
                                   cmOutputConverter::SHELL)
326
327
      << " -E copy $in $out";

328
  this->AddRule("COPY_OSX_CONTENT", cmd.str(), "Copying OS X Content $out",
329
                "Rule for copying OS X bundle content file.",
330
                /*depfile*/ "",
331
332
333
                /*deptype*/ "",
                /*rspfile*/ "",
                /*rspcontent*/ "",
334
                /*restat*/ "",
335
                /*generator*/ false);
336
337
}

338
339
void cmGlobalNinjaGenerator::WriteMacOSXContentBuild(const std::string& input,
                                                     const std::string& output)
340
341
342
343
344
345
346
347
348
{
  this->AddMacOSXContentRule();

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

349
  this->WriteBuild(*this->BuildFileStream, "", "COPY_OSX_CONTENT", outputs,
350
351
                   /*implicitOuts=*/cmNinjaDeps(), deps, cmNinjaDeps(),
                   cmNinjaDeps(), cmNinjaVars());
352
353
}

354
355
356
357
358
359
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)
360
361
{
  // Make sure the rule has a name.
362
  if (name.empty()) {
363
364
365
366
    cmSystemTools::Error("No name given for WriteRuleStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
367
  }
368
369

  // Make sure a command is given.
370
  if (command.empty()) {
371
372
373
374
    cmSystemTools::Error("No command given for WriteRuleStatement! called "
                         "with comment: ",
                         comment.c_str());
    return;
375
  }
376
377
378
379
380
381
382

  cmGlobalNinjaGenerator::WriteComment(os, comment);

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

  // Write the depfile if any.
383
  if (!depfile.empty()) {
384
385
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "depfile = " << depfile << "\n";
386
  }
387

388
  // Write the deptype if any.
389
  if (!deptype.empty()) {
390
391
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "deps = " << deptype << "\n";
392
  }
393

394
395
396
397
398
  // Write the command.
  cmGlobalNinjaGenerator::Indent(os, 1);
  os << "command = " << command << "\n";

  // Write the description if any.
399
  if (!description.empty()) {
400
401
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "description = " << description << "\n";
402
  }
403

404
405
  if (!rspfile.empty()) {
    if (rspcontent.empty()) {
406
407
      cmSystemTools::Error("No rspfile_content given!", comment.c_str());
      return;
408
    }
409
410
411
412
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "rspfile = " << rspfile << "\n";
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "rspfile_content = " << rspcontent << "\n";
413
  }
414

415
  if (!restat.empty()) {
416
    cmGlobalNinjaGenerator::Indent(os, 1);
417
    os << "restat = " << restat << "\n";
418
  }
419

420
  if (generator) {
421
422
    cmGlobalNinjaGenerator::Indent(os, 1);
    os << "generator = 1\n";
423
  }
424
425

  os << "\n";
426
427
428
429
430
431
432
433
434
}

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.
435
  if (name.empty()) {
436
437
438
439
    cmSystemTools::Error("No name given for WriteVariable! called "
                         "with comment: ",
                         comment.c_str());
    return;
440
  }
441
442
443

  // Do not add a variable if the value is empty.
  std::string val = cmSystemTools::TrimWhitespace(value);
444
  if (val.empty()) {
445
    return;
446
  }
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466

  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";
467
  for (cmNinjaDeps::const_iterator i = targets.begin(); i != targets.end();
468
       ++i) {
469
    os << " " << *i;
470
  }
471
472
473
  os << "\n";
}

474
cmGlobalNinjaGenerator::cmGlobalNinjaGenerator(cmake* cm)
475
  : cmGlobalCommonGenerator(cm)
Daniel Pfeifer's avatar
Daniel Pfeifer committed
476
477
478
  , BuildFileStream(CM_NULLPTR)
  , RulesFileStream(CM_NULLPTR)
  , CompileCommandsStream(CM_NULLPTR)
479
480
  , Rules()
  , AllDependencies()
481
  , UsingGCCOnWindows(false)
482
483
  , ComputingUnknownDependencies(false)
  , PolicyCMP0058(cmPolicies::WARN)
484
485
  , NinjaSupportsConsolePool(false)
  , NinjaSupportsImplicitOuts(false)
486
  , NinjaSupportsDyndeps(0)
487
{
488
#ifdef _WIN32
489
  cm->GetState()->SetWindowsShell(true);
490
#endif
491
492
493
494
495
496
497
  // // Ninja is not ported to non-Unix OS yet.
  // this->ForceUnixPaths = true;
  this->FindMakeProgramFile = "CMakeNinjaFindMake.cmake";
}

// Virtual public methods.

498
cmLocalGenerator* cmGlobalNinjaGenerator::CreateLocalGenerator(cmMakefile* mf)
499
{
500
  return new cmLocalNinjaGenerator(this, mf);
501
502
}

503
504
505
506
507
508
509
510
511
512
513
514
codecvt::Encoding cmGlobalNinjaGenerator::GetMakefileEncoding() const
{
#ifdef _WIN32
  // Ninja on Windows does not support non-ANSI characters.
  // https://github.com/ninja-build/ninja/issues/1195
  return codecvt::ANSI;
#else
  // No encoding conversion needed on other platforms.
  return codecvt::None;
#endif
}

515
void cmGlobalNinjaGenerator::GetDocumentation(cmDocumentationEntry& entry)
516
{
517
  entry.Name = cmGlobalNinjaGenerator::GetActualName();
518
  entry.Brief = "Generates build.ninja files.";
519
520
521
522
523
524
525
526
}

// Implemented in all cmGlobaleGenerator sub-classes.
// Used in:
//   Source/cmLocalGenerator.cxx
//   Source/cmake.cxx
void cmGlobalNinjaGenerator::Generate()
{
527
528
  // Check minimum Ninja version.
  if (cmSystemTools::VersionCompare(cmSystemTools::OP_LESS,
529
                                    this->NinjaVersion.c_str(),
530
                                    RequiredNinjaVersion().c_str())) {
531
    std::ostringstream msg;
532
    msg << "The detected version of Ninja (" << this->NinjaVersion;
533
534
535
536
    msg << ") is less than the version of Ninja required by CMake (";
    msg << this->RequiredNinjaVersion() << ").";
    this->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, msg.str());
    return;
537
  }
538
539
540
  this->OpenBuildFileStream();
  this->OpenRulesFileStream();

541
542
  this->TargetDependsClosures.clear();

543
  this->InitOutputPathPrefix();
544
545
  this->TargetAll = this->NinjaOutputPath("all");
  this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
546

547
  this->PolicyCMP0058 =
548
549
    this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
      cmPolicies::CMP0058);
550
551
552
553
  this->ComputingUnknownDependencies =
    (this->PolicyCMP0058 == cmPolicies::OLD ||
     this->PolicyCMP0058 == cmPolicies::WARN);

554
555
  this->cmGlobalGenerator::Generate();

556
  this->WriteAssumedSourceDependencies();
557
  this->WriteTargetAliases(*this->BuildFileStream);
558
  this->WriteFolderTargets(*this->BuildFileStream);
559
  this->WriteUnknownExplicitDependencies(*this->BuildFileStream);
560
561
  this->WriteBuiltinTargets(*this->BuildFileStream);

562
  if (cmSystemTools::GetErrorOccuredFlag()) {
563
564
    this->RulesFileStream->setstate(std::ios::failbit);
    this->BuildFileStream->setstate(std::ios::failbit);
565
566
  }

567
  this->CloseCompileCommandsStream();
568
569
570
571
  this->CloseRulesFileStream();
  this->CloseBuildFileStream();
}

572
bool cmGlobalNinjaGenerator::FindMakeProgram(cmMakefile* mf)
573
{
574
575
576
  if (!this->cmGlobalGenerator::FindMakeProgram(mf)) {
    return false;
  }
577
  if (const char* ninjaCommand = mf->GetDefinition("CMAKE_MAKE_PROGRAM")) {
578
    this->NinjaCommand = ninjaCommand;
579
580
581
582
    std::vector<std::string> command;
    command.push_back(this->NinjaCommand);
    command.push_back("--version");
    std::string version;
583
584
585
586
587
588
589
590
591
592
593
    std::string error;
    if (!cmSystemTools::RunSingleCommand(command, &version, &error, CM_NULLPTR,
                                         CM_NULLPTR,
                                         cmSystemTools::OUTPUT_NONE)) {
      mf->IssueMessage(cmake::FATAL_ERROR, "Running\n '" +
                         cmJoin(command, "' '") + "'\n"
                                                  "failed with:\n " +
                         error);
      cmSystemTools::SetFatalErrorOccured();
      return false;
    }
594
    this->NinjaVersion = cmSystemTools::TrimWhitespace(version);
595
    this->CheckNinjaFeatures();
596
  }
597
  return true;
598
599
}

600
601
602
603
604
605
606
607
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());
608
609
610
611
612
613
614
615
616
617
  {
    // 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);
    }
  }
618
619
}

620
621
622
623
624
bool cmGlobalNinjaGenerator::CheckLanguages(
  std::vector<std::string> const& languages, cmMakefile* mf) const
{
  if (std::find(languages.begin(), languages.end(), "Fortran") !=
      languages.end()) {
625
    return this->CheckFortran(mf);
626
627
628
629
  }
  return true;
}

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
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;
}

668
669
void cmGlobalNinjaGenerator::EnableLanguage(
  std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
670
{
671
  this->cmGlobalGenerator::EnableLanguage(langs, mf, optional);
672
673
674
  for (std::vector<std::string>::const_iterator l = langs.begin();
       l != langs.end(); ++l) {
    if (*l == "NONE") {
675
676
      continue;
    }
677
678
    this->ResolveLanguageCompiler(*l, mf, optional);
  }
679
#ifdef _WIN32
680
681
682
683
684
685
686
  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)) {
687
    this->UsingGCCOnWindows = true;
688
  }
689
#endif
690
691
692
693
}

// Implemented by:
//   cmGlobalUnixMakefileGenerator3
694
//   cmGlobalGhsMultiGenerator
695
696
697
698
699
//   cmGlobalVisualStudio10Generator
//   cmGlobalVisualStudio7Generator
//   cmGlobalXCodeGenerator
// Called by:
//   cmGlobalGenerator::Build()
700
701
702
703
704
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)
705
{
706
  makeCommand.push_back(this->SelectMakeProgram(makeProgram));
707

708
  if (verbose) {
709
    makeCommand.push_back("-v");
710
  }
711

712
713
714
715
  makeCommand.insert(makeCommand.end(), makeOptions.begin(),
                     makeOptions.end());
  if (!targetName.empty()) {
    if (targetName == "clean") {
716
717
      makeCommand.push_back("-t");
      makeCommand.push_back("clean");
718
    } else {
719
      makeCommand.push_back(targetName);
720
    }
721
  }
722
723
724
725
}

// Non-virtual public methods.

726
727
728
729
730
731
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)
732
733
{
  // Do not add the same rule twice.
734
  if (this->HasRule(name)) {
735
    return;
736
  }
737
738

  this->Rules.insert(name);
739
740
741
742
743
  cmGlobalNinjaGenerator::WriteRule(*this->RulesFileStream, name, command,
                                    description, comment, depfile, deptype,
                                    rspfile, rspcontent, restat, generator);

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

746
bool cmGlobalNinjaGenerator::HasRule(const std::string& name)
747
748
749
750
751
{
  RulesSetType::const_iterator rule = this->Rules.find(name);
  return (rule != this->Rules.end());
}

752
753
// Private virtual overrides

754
755
756
757
758
759
760
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();
}

761
762
void cmGlobalNinjaGenerator::ComputeTargetObjectDirectory(
  cmGeneratorTarget* gt) const
763
764
{
  // Compute full path to object file directory for this target.
765
  std::string dir;
766
  dir += gt->LocalGenerator->GetCurrentBinaryDirectory();
767
  dir += "/";
768
  dir += gt->LocalGenerator->GetTargetDirectory(gt);
769
770
  dir += "/";
  gt->ObjectDirectory = dir;
771
772
}

773
774
775
776
777
778
779
780
781
782
783
// 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.
784
  if (!this->BuildFileStream) {
785
786
    this->BuildFileStream = new cmGeneratedFileStream(
      buildFilePath.c_str(), false, this->GetMakefileEncoding());
787
    if (!this->BuildFileStream) {
788
789
790
791
      // An error message is generated by the constructor if it cannot
      // open the file.
      return;
    }
792
  }
793
794
795
796
797
798
799

  // 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"
800
    << "# compilation DAG.\n\n";
801
802
803
804
}

void cmGlobalNinjaGenerator::CloseBuildFileStream()
{
805
  if (this->BuildFileStream) {
806
    delete this->BuildFileStream;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
807
    this->BuildFileStream = CM_NULLPTR;
808
  } else {
809
    cmSystemTools::Error("Build file stream was not open.");
810
  }
811
812
813
814
815
816
817
818
819
820
821
}

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.
822
  if (!this->RulesFileStream) {
823
824
    this->RulesFileStream = new cmGeneratedFileStream(
      rulesFilePath.c_str(), false, this->GetMakefileEncoding());
825
    if (!this->RulesFileStream) {
826
827
828
829
      // An error message is generated by the constructor if it cannot
      // open the file.
      return;
    }
830
  }
831
832
833
834
835

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

  // Write comment about this file.
836
  /* clang-format off */
837
838
839
840
841
  *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"
    ;
842
  /* clang-format on */
843
844
845
846
}

void cmGlobalNinjaGenerator::CloseRulesFileStream()
{
847
  if (this->RulesFileStream) {
848
    delete this->RulesFileStream;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
849
    this->RulesFileStream = CM_NULLPTR;
850
  } else {
851
    cmSystemTools::Error("Rules file stream was not open.");
852
  }
853
854
}

855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
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
872
873
std::string cmGlobalNinjaGenerator::ConvertToNinjaPath(
  const std::string& path) const
874
{
875
876
  cmLocalNinjaGenerator* ng =
    static_cast<cmLocalNinjaGenerator*>(this->LocalGenerators[0]);
877
878
  std::string convPath = ng->ConvertToRelativePath(
    this->LocalGenerators[0]->GetState()->GetBinaryDirectory(), path);
879
  convPath = this->NinjaOutputPath(convPath);
880
#ifdef _WIN32
881
  std::replace(convPath.begin(), convPath.end(), '/', '\\');
882
883
884
885
#endif
  return convPath;
}

886
void cmGlobalNinjaGenerator::AddCXXCompileCommand(
887
  const std::string& commandLine, const std::string& sourceFile)
888
889
890
891
{
  // Compute Ninja's build file path.
  std::string buildFileDir =
    this->GetCMakeInstance()->GetHomeOutputDirectory();
892
  if (!this->CompileCommandsStream) {
893
    std::string buildFilePath = buildFileDir + "/compile_commands.json";
894
895
896
897
    if (this->ComputingUnknownDependencies) {
      this->CombinedBuildOutputs.insert(
        this->NinjaOutputPath("compile_commands.json"));
    }
898
899
900
901
902

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

907
  std::string sourceFileName = sourceFile;
908
  if (!cmSystemTools::FileIsFullPath(sourceFileName.c_str())) {
909
    sourceFileName = cmSystemTools::CollapseFullPath(
910
911
      sourceFileName, this->GetCMakeInstance()->GetHomeOutputDirectory());
  }
912

913
  /* clang-format off */
914
915
916
917
918
919
  *this->CompileCommandsStream << "\n{\n"
     << "  \"directory\": \""
     << cmGlobalGenerator::EscapeJSON(buildFileDir) << "\",\n"
     << "  \"command\": \""
     << cmGlobalGenerator::EscapeJSON(commandLine) << "\",\n"
     << "  \"file\": \""
920
     << cmGlobalGenerator::EscapeJSON(sourceFileName) << "\"\n"
921
     << "}";
922
  /* clang-format on */
923
924
925
926
}

void cmGlobalNinjaGenerator::CloseCompileCommandsStream()
{
927
  if (this->CompileCommandsStream) {
928
929
    *this->CompileCommandsStream << "\n]";
    delete this->CompileCommandsStream;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
930
    this->CompileCommandsStream = CM_NULLPTR;
931
  }
932
933
}

934
935
void cmGlobalNinjaGenerator::WriteDisclaimer(std::ostream& os)
{
936
937
938
939
  os << "# CMAKE generated file: DO NOT EDIT!\n"
     << "# Generated by \"" << this->GetName() << "\""
     << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "."
     << cmVersion::GetMinorVersion() << "\n\n";
940
941
}

942
void cmGlobalNinjaGenerator::AddDependencyToAll(cmGeneratorTarget* target)
943
944
945
946
{
  this->AppendTargetOutputs(target, this->AllDependencies);
}

Nicolas Despres's avatar