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

5
6
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
7
#include "cmGlobalGenerator.h"
8
#include "cmLocalGenerator.h"
9
10
#include "cmMakefile.h"
#include "cmSourceFile.h"
11
#include "cmState.h"
12
13
#include "cmSystemTools.h"

14
15
16
17
18
#include <cmsys/RegularExpression.hxx>
#include <ostream>
#include <set>
#include <string.h>
#include <utility>
19

20
21
22
23
24
25
/*
Sublime Text 2 Generator
Author: Morné Chamberlain
This generator was initially based off of the CodeBlocks generator.

Some useful URLs:
26
27
28
29
30
31
32
33
Homepage:
http://www.sublimetext.com/

File format docs:
http://www.sublimetext.com/docs/2/projects.html
http://sublimetext.info/docs/en/reference/build_systems.html
*/

34
35
cmExternalMakefileProjectGeneratorFactory*
cmExtraSublimeTextGenerator::GetFactory()
36
{
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  static cmExternalMakefileProjectGeneratorSimpleFactory<
    cmExtraSublimeTextGenerator>
    factory("Sublime Text 2", "Generates Sublime Text 2 project files.");

  if (factory.GetSupportedGlobalGenerators().empty()) {
#if defined(_WIN32)
    factory.AddSupportedGlobalGenerator("MinGW Makefiles");
    factory.AddSupportedGlobalGenerator("NMake Makefiles");
// disable until somebody actually tests it:
// factory.AddSupportedGlobalGenerator("MSYS Makefiles");
#endif
    factory.AddSupportedGlobalGenerator("Ninja");
    factory.AddSupportedGlobalGenerator("Unix Makefiles");
  }

  return &factory;
53
54
55
}

cmExtraSublimeTextGenerator::cmExtraSublimeTextGenerator()
56
  : cmExternalMakefileProjectGenerator()
57
{
58
  this->ExcludeBuildFolder = false;
59
60
61
62
}

void cmExtraSublimeTextGenerator::Generate()
{
63
64
65
  this->ExcludeBuildFolder = this->GlobalGenerator->GlobalSettingIsOn(
    "CMAKE_SUBLIME_TEXT_2_EXCLUDE_BUILD_TREE");

66
  // for each sub project in the project create a sublime text 2 project
67
  for (std::map<std::string, std::vector<cmLocalGenerator*> >::const_iterator
68
69
         it = this->GlobalGenerator->GetProjectMap().begin();
       it != this->GlobalGenerator->GetProjectMap().end(); ++it) {
70
71
    // create a project file
    this->CreateProjectFile(it->second);
72
  }
73
74
75
}

void cmExtraSublimeTextGenerator::CreateProjectFile(
76
  const std::vector<cmLocalGenerator*>& lgs)
77
{
78
79
  std::string outputDir = lgs[0]->GetCurrentBinaryDirectory();
  std::string projectName = lgs[0]->GetProjectName();
80

81
  const std::string filename =
82
    outputDir + "/" + projectName + ".sublime-project";
83
84
85
86

  this->CreateNewProjectFile(lgs, filename);
}

87
88
void cmExtraSublimeTextGenerator::CreateNewProjectFile(
  const std::vector<cmLocalGenerator*>& lgs, const std::string& filename)
89
{
90
  const cmMakefile* mf = lgs[0]->GetMakefile();
91

92
  cmGeneratedFileStream fout(filename.c_str());
93
  if (!fout) {
94
    return;
95
  }
96

97
98
  const std::string& sourceRootRelativeToOutput = cmSystemTools::RelativePath(
    lgs[0]->GetBinaryDirectory(), lgs[0]->GetSourceDirectory());
99
  // Write the folder entries to the project file
100
101
  fout << "{\n";
  fout << "\t\"folders\":\n\t[\n\t";
102
103
104
105
106
107
  if (!sourceRootRelativeToOutput.empty()) {
    fout << "\t{\n\t\t\t\"path\": \"" << sourceRootRelativeToOutput << "\"";
    const std::string& outputRelativeToSourceRoot =
      cmSystemTools::RelativePath(lgs[0]->GetSourceDirectory(),
                                  lgs[0]->GetBinaryDirectory());
    if ((!outputRelativeToSourceRoot.empty()) &&
108
        ((outputRelativeToSourceRoot.length() < 3) ||
109
         (outputRelativeToSourceRoot.substr(0, 3) != "../"))) {
110
111
112
113
      if (this->ExcludeBuildFolder) {
        fout << ",\n\t\t\t\"folder_exclude_patterns\": [\""
             << outputRelativeToSourceRoot << "\"]";
      }
114
    }
115
116
117
  } else {
    fout << "\t{\n\t\t\t\"path\": \"./\"";
  }
118
  fout << "\n\t\t}";
119
  // End of the folders section
120
  fout << "\n\t]";
121
122
123
124
125
126
127

  // Write the beginning of the build systems section to the project file
  fout << ",\n\t\"build_systems\":\n\t[\n\t";

  // Set of include directories over all targets (sublime text/sublimeclang
  // doesn't currently support these settings per build system, only project
  // wide
128
129
  MapSourceFileFlags sourceFileFlags;
  AppendAllTargets(lgs, mf, fout, sourceFileFlags);
130

131
132
  // End of build_systems
  fout << "\n\t]";
133
  fout << "\n\t}";
134
135
}

136
137
138
void cmExtraSublimeTextGenerator::AppendAllTargets(
  const std::vector<cmLocalGenerator*>& lgs, const cmMakefile* mf,
  cmGeneratedFileStream& fout, MapSourceFileFlags& sourceFileFlags)
139
{
140
141
  std::string make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM");
  std::string compiler = "";
142
  if (!lgs.empty()) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
143
    this->AppendTarget(fout, "all", lgs[0], CM_NULLPTR, make.c_str(), mf,
144
                       compiler.c_str(), sourceFileFlags, true);
Daniel Pfeifer's avatar
Daniel Pfeifer committed
145
    this->AppendTarget(fout, "clean", lgs[0], CM_NULLPTR, make.c_str(), mf,
146
147
                       compiler.c_str(), sourceFileFlags, false);
  }
148
149
150

  // add all executable and library targets and some of the GLOBAL
  // and UTILITY targets
151
152
153
154
  for (std::vector<cmLocalGenerator*>::const_iterator lg = lgs.begin();
       lg != lgs.end(); lg++) {
    cmMakefile* makefile = (*lg)->GetMakefile();
    std::vector<cmGeneratorTarget*> targets = (*lg)->GetGeneratorTargets();
Stephen Kelly's avatar
Stephen Kelly committed
155
    for (std::vector<cmGeneratorTarget*>::iterator ti = targets.begin();
156
         ti != targets.end(); ti++) {
Stephen Kelly's avatar
Stephen Kelly committed
157
      std::string targetName = (*ti)->GetName();
158
159
      switch ((*ti)->GetType()) {
        case cmState::GLOBAL_TARGET: {
160
161
          // Only add the global targets from CMAKE_BINARY_DIR,
          // not from the subdirs
162
          if (strcmp((*lg)->GetCurrentBinaryDirectory(),
163
                     (*lg)->GetBinaryDirectory()) == 0) {
Daniel Pfeifer's avatar
Daniel Pfeifer committed
164
            this->AppendTarget(fout, targetName, *lg, CM_NULLPTR, make.c_str(),
165
166
                               makefile, compiler.c_str(), sourceFileFlags,
                               false);
167
          }
168
        } break;
169
        case cmState::UTILITY:
170
171
          // Add all utility targets, except the Nightly/Continuous/
          // Experimental-"sub"targets as e.g. NightlyStart
172
173
174
175
176
177
          if (((targetName.find("Nightly") == 0) &&
               (targetName != "Nightly")) ||
              ((targetName.find("Continuous") == 0) &&
               (targetName != "Continuous")) ||
              ((targetName.find("Experimental") == 0) &&
               (targetName != "Experimental"))) {
178
            break;
179
          }
180

Daniel Pfeifer's avatar
Daniel Pfeifer committed
181
182
183
          this->AppendTarget(fout, targetName, *lg, CM_NULLPTR, make.c_str(),
                             makefile, compiler.c_str(), sourceFileFlags,
                             false);
184
          break;
185
186
187
188
        case cmState::EXECUTABLE:
        case cmState::STATIC_LIBRARY:
        case cmState::SHARED_LIBRARY:
        case cmState::MODULE_LIBRARY:
189
190
191
192
        case cmState::OBJECT_LIBRARY: {
          this->AppendTarget(fout, targetName, *lg, *ti, make.c_str(),
                             makefile, compiler.c_str(), sourceFileFlags,
                             false);
Stephen Kelly's avatar
Stephen Kelly committed
193
          std::string fastTarget = targetName;
194
          fastTarget += "/fast";
195
196
197
198
          this->AppendTarget(fout, fastTarget, *lg, *ti, make.c_str(),
                             makefile, compiler.c_str(), sourceFileFlags,
                             false);
        } break;
199
200
201
202
        default:
          break;
      }
    }
203
  }
204
205
}

206
207
208
void cmExtraSublimeTextGenerator::AppendTarget(
  cmGeneratedFileStream& fout, const std::string& targetName,
  cmLocalGenerator* lg, cmGeneratorTarget* target, const char* make,
209
  const cmMakefile* makefile, const char* /*compiler*/,
210
  MapSourceFileFlags& sourceFileFlags, bool firstTarget)
211
{
212

Daniel Pfeifer's avatar
Daniel Pfeifer committed
213
  if (target != CM_NULLPTR) {
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    std::vector<cmSourceFile*> sourceFiles;
    target->GetSourceFiles(sourceFiles,
                           makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
    std::vector<cmSourceFile*>::const_iterator sourceFilesEnd =
      sourceFiles.end();
    for (std::vector<cmSourceFile*>::const_iterator iter = sourceFiles.begin();
         iter != sourceFilesEnd; ++iter) {
      cmSourceFile* sourceFile = *iter;
      MapSourceFileFlags::iterator sourceFileFlagsIter =
        sourceFileFlags.find(sourceFile->GetFullPath());
      if (sourceFileFlagsIter == sourceFileFlags.end()) {
        sourceFileFlagsIter =
          sourceFileFlags
            .insert(MapSourceFileFlags::value_type(sourceFile->GetFullPath(),
                                                   std::vector<std::string>()))
            .first;
      }
      std::vector<std::string>& flags = sourceFileFlagsIter->second;
      std::string flagsString = this->ComputeFlagsForObject(*iter, lg, target);
      std::string definesString = this->ComputeDefines(*iter, lg, target);
      flags.clear();
      cmsys::RegularExpression flagRegex;
      // Regular expression to extract compiler flags from a string
      // https://gist.github.com/3944250
      const char* regexString =
        "(^|[ ])-[DIOUWfgs][^= ]+(=\\\"[^\"]+\\\"|=[^\"][^ ]+)?";
      flagRegex.compile(regexString);
      std::string workString = flagsString + " " + definesString;
      while (flagRegex.find(workString)) {
        std::string::size_type start = flagRegex.start();
        if (workString[start] == ' ') {
          start++;
246
        }
247
248
249
250
251
252
253
        flags.push_back(workString.substr(start, flagRegex.end() - start));
        if (flagRegex.end() < workString.size()) {
          workString = workString.substr(flagRegex.end());
        } else {
          workString = "";
        }
      }
254
    }
255
  }
256

257
  // Ninja uses ninja.build files (look for a way to get the output file name
258
259
  // from cmMakefile or something)
  std::string makefileName;
260
261
262
263
264
265
  if (this->GlobalGenerator->GetName() == "Ninja") {
    makefileName = "build.ninja";
  } else {
    makefileName = "Makefile";
  }
  if (!firstTarget) {
266
    fout << ",\n\t";
267
268
269
270
271
272
  }
  fout << "\t{\n\t\t\t\"name\": \"" << lg->GetProjectName() << " - "
       << targetName << "\",\n";
  fout << "\t\t\t\"cmd\": ["
       << this->BuildMakeCommand(make, makefileName.c_str(), targetName)
       << "],\n";
273
274
275
276
277
278
279
280
  fout << "\t\t\t\"working_dir\": \"${project_path}\",\n";
  fout << "\t\t\t\"file_regex\": \"^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$\"\n";
  fout << "\t\t}";
}

// Create the command line for building the given target using the selected
// make
std::string cmExtraSublimeTextGenerator::BuildMakeCommand(
281
  const std::string& make, const char* makefile, const std::string& target)
282
283
284
{
  std::string command = "\"";
  command += make + "\"";
285
  std::string generator = this->GlobalGenerator->GetName();
286
  if (generator == "NMake Makefiles") {
287
288
289
290
291
292
    std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
    command += ", \"/NOLOGO\", \"/f\", \"";
    command += makefileName + "\"";
    command += ", \"VERBOSE=1\", \"";
    command += target;
    command += "\"";
293
  } else if (generator == "Ninja") {
294
    std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
295
296
    command += ", \"-f\", \"";
    command += makefileName + "\"";
297
    command += ", \"-v\", \"";
298
299
    command += target;
    command += "\"";
300
  } else {
301
    std::string makefileName;
302
303
    if (generator == "MinGW Makefiles") {
      // no escaping of spaces in this case, see
304
      // https://gitlab.kitware.com/cmake/cmake/issues/10014
305
306
307
308
      makefileName = makefile;
    } else {
      makefileName = cmSystemTools::ConvertToOutputPath(makefile);
    }
309
310
311
312
313
    command += ", \"-f\", \"";
    command += makefileName + "\"";
    command += ", \"VERBOSE=1\", \"";
    command += target;
    command += "\"";
314
  }
315
316
  return command;
}
317
318

// TODO: Most of the code is picked up from the Ninja generator, refactor it.
319
320
std::string cmExtraSublimeTextGenerator::ComputeFlagsForObject(
  cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* gtgt)
321
322
{
  std::string flags;
323
  std::string language = source->GetLanguage();
324
325
326
  if (language.empty()) {
    language = "C";
  }
327
328
  std::string const& config =
    lg->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
329

330
  lg->GetTargetCompileFlags(gtgt, config, language, flags);
331
332
333

  // Add include directory flags.
  {
334
335
336
337
338
    std::vector<std::string> includes;
    lg->GetIncludeDirectories(includes, gtgt, language, config);
    std::string includeFlags = lg->GetIncludeFlags(includes, gtgt, language,
                                                   true); // full include paths
    lg->AppendFlags(flags, includeFlags);
339
340
341
  }

  // Add source file specific flags.
342
  lg->AppendFlags(flags, source->GetProperty("COMPILE_FLAGS"));
343
344
345
346
347
348

  return flags;
}

// TODO: Refactor with
// void cmMakefileTargetGenerator::WriteTargetLanguageFlags().
349
350
std::string cmExtraSublimeTextGenerator::ComputeDefines(
  cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* target)
351
352
353

{
  std::set<std::string> defines;
354
  cmMakefile* makefile = lg->GetMakefile();
355
  const std::string& language = source->GetLanguage();
356
  const std::string& config = makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
357
358

  // Add the export symbol definition for shared library objects.
359
  if (const char* exportMacro = target->GetExportMacro()) {
360
    lg->AppendDefines(defines, exportMacro);
361
  }
362
363

  // Add preprocessor definitions for this target and configuration.
364
  lg->AddCompileDefinitions(defines, target, config, language);
365
366
  lg->AppendDefines(defines, source->GetProperty("COMPILE_DEFINITIONS"));
  {
367
368
369
    std::string defPropName = "COMPILE_DEFINITIONS_";
    defPropName += cmSystemTools::UpperCase(config);
    lg->AppendDefines(defines, source->GetProperty(defPropName));
370
371
372
373
374
375
376
  }

  std::string definesString;
  lg->JoinDefines(defines, definesString, language);

  return definesString;
}