Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmExtraSublimeTextGenerator.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmExtraSublimeTextGenerator.h"
4
5
#include <cstring>
6
#include <memory>
7
#include <set>
8
#include <sstream>
9
#include <utility>
10
11
#include "cmsys/RegularExpression.hxx"
12
13
#include "cmGeneratedFileStream.h"
14
#include "cmGeneratorExpression.h"
15
#include "cmGeneratorTarget.h"
16
#include "cmGlobalGenerator.h"
17
#include "cmList.h"
18
#include "cmLocalGenerator.h"
19
#include "cmMakefile.h"
20
#include "cmMessageType.h"
21
#include "cmSourceFile.h"
22
#include "cmStateTypes.h"
23
#include "cmStringAlgorithms.h"
24
#include "cmSystemTools.h"
25
#include "cmValue.h"
26
#include "cmake.h"
27
28
/*
29
Sublime Text 2 Generator
30
Author: Morné Chamberlain
31
This generator was initially based off of the CodeBlocks generator.
32
33
Some useful URLs:
34
Homepage:
35
http://www.sublimetext.com/
36
37
File format docs:
38
http://www.sublimetext.com/docs/2/projects.html
39
http://sublimetext.info/docs/en/reference/build_systems.html
40
*/
41
42
cmExternalMakefileProjectGeneratorFactory*
43
cmExtraSublimeTextGenerator::GetFactory()
44
35
{
45
35
  static cmExternalMakefileProjectGeneratorSimpleFactory<
46
35
    cmExtraSublimeTextGenerator>
47
35
    factory("Sublime Text 2",
48
35
            "Generates Sublime Text 2 project files (deprecated).");
49
50
35
  if (factory.GetSupportedGlobalGenerators().empty()) {
51
#if defined(_WIN32)
52
    factory.AddSupportedGlobalGenerator("MinGW Makefiles");
53
    factory.AddSupportedGlobalGenerator("NMake Makefiles");
54
// disable until somebody actually tests it:
55
// factory.AddSupportedGlobalGenerator("MSYS Makefiles");
56
#endif
57
1
    factory.AddSupportedGlobalGenerator("Ninja");
58
1
    factory.AddSupportedGlobalGenerator("Unix Makefiles");
59
1
  }
60
61
35
  return &factory;
62
35
}
63
64
cmExtraSublimeTextGenerator::cmExtraSublimeTextGenerator()
65
0
{
66
0
  this->ExcludeBuildFolder = false;
67
0
}
68
69
void cmExtraSublimeTextGenerator::Generate()
70
0
{
71
0
  this->ExcludeBuildFolder = this->GlobalGenerator->GlobalSettingIsOn(
72
0
    "CMAKE_SUBLIME_TEXT_2_EXCLUDE_BUILD_TREE");
73
0
  this->EnvSettings = this->GlobalGenerator->GetSafeGlobalSetting(
74
0
    "CMAKE_SUBLIME_TEXT_2_ENV_SETTINGS");
75
76
  // for each sub project in the project create a sublime text 2 project
77
0
  for (auto const& it : this->GlobalGenerator->GetProjectMap()) {
78
    // create a project file
79
0
    this->CreateProjectFile(it.second);
80
0
  }
81
0
}
82
83
void cmExtraSublimeTextGenerator::CreateProjectFile(
84
  std::vector<cmLocalGenerator*> const& lgs)
85
0
{
86
0
  std::string outputDir = lgs[0]->GetCurrentBinaryDirectory();
87
0
  std::string projectName = lgs[0]->GetProjectName();
88
89
0
  std::string const filename =
90
0
    cmStrCat(outputDir, '/', projectName, ".sublime-project");
91
92
0
  this->CreateNewProjectFile(lgs, filename);
93
0
}
94
95
void cmExtraSublimeTextGenerator::CreateNewProjectFile(
96
  std::vector<cmLocalGenerator*> const& lgs, std::string const& filename)
97
0
{
98
0
  cmMakefile const* mf = lgs[0]->GetMakefile();
99
100
0
  cmGeneratedFileStream fout(filename);
101
0
  if (!fout) {
102
0
    return;
103
0
  }
104
105
0
  std::string const& sourceRootRelativeToOutput = cmSystemTools::RelativePath(
106
0
    lgs[0]->GetBinaryDirectory(), lgs[0]->GetSourceDirectory());
107
  // Write the folder entries to the project file
108
0
  fout << "{\n";
109
0
  fout << "\t\"folders\":\n\t[\n\t";
110
0
  if (!sourceRootRelativeToOutput.empty()) {
111
0
    fout << "\t{\n\t\t\t\"path\": \"" << sourceRootRelativeToOutput << "\"";
112
0
    std::string const& outputRelativeToSourceRoot =
113
0
      cmSystemTools::RelativePath(lgs[0]->GetSourceDirectory(),
114
0
                                  lgs[0]->GetBinaryDirectory());
115
0
    if ((!outputRelativeToSourceRoot.empty()) &&
116
0
        ((outputRelativeToSourceRoot.length() < 3) ||
117
0
         (outputRelativeToSourceRoot.substr(0, 3) != "../"))) {
118
0
      if (this->ExcludeBuildFolder) {
119
0
        fout << ",\n\t\t\t\"folder_exclude_patterns\": [\""
120
0
             << outputRelativeToSourceRoot << "\"]";
121
0
      }
122
0
    }
123
0
  } else {
124
0
    fout << "\t{\n\t\t\t\"path\": \"./\"";
125
0
  }
126
0
  fout << "\n\t\t}";
127
  // End of the folders section
128
0
  fout << "\n\t]";
129
130
  // Write the beginning of the build systems section to the project file
131
0
  fout << ",\n\t\"build_systems\":\n\t[\n\t";
132
133
  // Set of include directories over all targets (sublime text/sublimeclang
134
  // doesn't currently support these settings per build system, only project
135
  // wide
136
0
  MapSourceFileFlags sourceFileFlags;
137
0
  this->AppendAllTargets(lgs, mf, fout, sourceFileFlags);
138
139
  // End of build_systems
140
0
  fout << "\n\t]";
141
0
  std::string systemName = mf->GetSafeDefinition("CMAKE_SYSTEM_NAME");
142
0
  cmList tokens{ this->EnvSettings };
143
144
0
  if (!this->EnvSettings.empty()) {
145
0
    fout << ",";
146
0
    fout << "\n\t\"env\":";
147
0
    fout << "\n\t{";
148
0
    fout << "\n\t\t" << systemName << ":";
149
0
    fout << "\n\t\t{";
150
0
    for (std::string const& t : tokens) {
151
0
      size_t const pos = t.find_first_of('=');
152
153
0
      if (pos != std::string::npos) {
154
0
        std::string varName = t.substr(0, pos);
155
0
        std::string varValue = t.substr(pos + 1);
156
157
0
        fout << "\n\t\t\t\"" << varName << "\":\"" << varValue << "\"";
158
0
      } else {
159
0
        std::ostringstream e;
160
0
        e << "Could not parse Env Vars specified in "
161
0
             "\"CMAKE_SUBLIME_TEXT_2_ENV_SETTINGS\""
162
0
          << ", corrupted string " << t;
163
0
        mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
164
0
      }
165
0
    }
166
0
    fout << "\n\t\t}";
167
0
    fout << "\n\t}";
168
0
  }
169
0
  fout << "\n}";
170
0
}
171
172
void cmExtraSublimeTextGenerator::AppendAllTargets(
173
  std::vector<cmLocalGenerator*> const& lgs, cmMakefile const* mf,
174
  cmGeneratedFileStream& fout, MapSourceFileFlags& sourceFileFlags)
175
0
{
176
0
  std::string const& make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM");
177
0
  std::string compiler;
178
0
  if (!lgs.empty()) {
179
0
    this->AppendTarget(fout, "all", lgs[0], nullptr, make.c_str(), mf,
180
0
                       compiler.c_str(), sourceFileFlags, true);
181
0
    this->AppendTarget(fout, "clean", lgs[0], nullptr, make.c_str(), mf,
182
0
                       compiler.c_str(), sourceFileFlags, false);
183
0
  }
184
185
  // add all executable and library targets and some of the GLOBAL
186
  // and UTILITY targets
187
0
  for (cmLocalGenerator* lg : lgs) {
188
0
    cmMakefile* makefile = lg->GetMakefile();
189
0
    auto const& targets = lg->GetGeneratorTargets();
190
0
    for (auto const& target : targets) {
191
0
      std::string targetName = target->GetName();
192
0
      switch (target->GetType()) {
193
0
        case cmStateEnums::GLOBAL_TARGET: {
194
          // Only add the global targets from CMAKE_BINARY_DIR,
195
          // not from the subdirs
196
0
          if (lg->GetCurrentBinaryDirectory() == lg->GetBinaryDirectory()) {
197
0
            this->AppendTarget(fout, targetName, lg, nullptr, make.c_str(),
198
0
                               makefile, compiler.c_str(), sourceFileFlags,
199
0
                               false);
200
0
          }
201
0
        } break;
202
0
        case cmStateEnums::UTILITY:
203
          // Add all utility targets, except the Nightly/Continuous/
204
          // Experimental-"sub"targets as e.g. NightlyStart
205
0
          if ((cmHasLiteralPrefix(targetName, "Nightly") &&
206
0
               (targetName != "Nightly")) ||
207
0
              (cmHasLiteralPrefix(targetName, "Continuous") &&
208
0
               (targetName != "Continuous")) ||
209
0
              (cmHasLiteralPrefix(targetName, "Experimental") &&
210
0
               (targetName != "Experimental"))) {
211
0
            break;
212
0
          }
213
214
0
          this->AppendTarget(fout, targetName, lg, nullptr, make.c_str(),
215
0
                             makefile, compiler.c_str(), sourceFileFlags,
216
0
                             false);
217
0
          break;
218
0
        case cmStateEnums::EXECUTABLE:
219
0
        case cmStateEnums::STATIC_LIBRARY:
220
0
        case cmStateEnums::SHARED_LIBRARY:
221
0
        case cmStateEnums::MODULE_LIBRARY:
222
0
        case cmStateEnums::OBJECT_LIBRARY: {
223
0
          this->AppendTarget(fout, targetName, lg, target.get(), make.c_str(),
224
0
                             makefile, compiler.c_str(), sourceFileFlags,
225
0
                             false);
226
0
          std::string fastTarget = cmStrCat(targetName, "/fast");
227
0
          this->AppendTarget(fout, fastTarget, lg, target.get(), make.c_str(),
228
0
                             makefile, compiler.c_str(), sourceFileFlags,
229
0
                             false);
230
0
        } break;
231
0
        default:
232
0
          break;
233
0
      }
234
0
    }
235
0
  }
236
0
}
237
238
void cmExtraSublimeTextGenerator::AppendTarget(
239
  cmGeneratedFileStream& fout, std::string const& targetName,
240
  cmLocalGenerator* lg, cmGeneratorTarget* target, char const* make,
241
  cmMakefile const* makefile, char const* /*compiler*/,
242
  MapSourceFileFlags& sourceFileFlags, bool firstTarget)
243
0
{
244
245
0
  if (target) {
246
0
    std::vector<cmSourceFile*> sourceFiles;
247
0
    target->GetSourceFiles(sourceFiles,
248
0
                           makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
249
0
    for (cmSourceFile* sourceFile : sourceFiles) {
250
0
      auto sourceFileFlagsIter =
251
0
        sourceFileFlags.find(sourceFile->ResolveFullPath());
252
0
      if (sourceFileFlagsIter == sourceFileFlags.end()) {
253
0
        sourceFileFlagsIter =
254
0
          sourceFileFlags
255
0
            .insert(MapSourceFileFlags::value_type(
256
0
              sourceFile->ResolveFullPath(), std::vector<std::string>()))
257
0
            .first;
258
0
      }
259
0
      std::vector<std::string>& flags = sourceFileFlagsIter->second;
260
0
      std::string flagsString =
261
0
        this->ComputeFlagsForObject(sourceFile, lg, target);
262
0
      std::string definesString = this->ComputeDefines(sourceFile, lg, target);
263
0
      std::string includesString =
264
0
        this->ComputeIncludes(sourceFile, lg, target);
265
0
      flags.clear();
266
0
      cmsys::RegularExpression flagRegex;
267
      // Regular expression to extract compiler flags from a string
268
      // https://gist.github.com/3944250
269
0
      char const* regexString =
270
0
        R"((^|[ ])-[DIOUWfgs][^= ]+(=\"[^"]+\"|=[^"][^ ]+)?)";
271
0
      flagRegex.compile(regexString);
272
0
      std::string workString =
273
0
        cmStrCat(flagsString, ' ', definesString, ' ', includesString);
274
0
      while (flagRegex.find(workString)) {
275
0
        std::string::size_type start = flagRegex.start();
276
0
        if (workString[start] == ' ') {
277
0
          start++;
278
0
        }
279
0
        flags.push_back(workString.substr(start, flagRegex.end() - start));
280
0
        if (flagRegex.end() < workString.size()) {
281
0
          workString = workString.substr(flagRegex.end());
282
0
        } else {
283
0
          workString.clear();
284
0
        }
285
0
      }
286
0
    }
287
0
  }
288
289
  // Ninja uses ninja.build files (look for a way to get the output file name
290
  // from cmMakefile or something)
291
0
  std::string makefileName;
292
0
  if (this->GlobalGenerator->GetName() == "Ninja") {
293
0
    makefileName = "build.ninja";
294
0
  } else {
295
0
    makefileName = "Makefile";
296
0
  }
297
0
  if (!firstTarget) {
298
0
    fout << ",\n\t";
299
0
  }
300
0
  fout << "\t{\n\t\t\t\"name\": \"" << lg->GetProjectName() << " - "
301
0
       << targetName << "\",\n";
302
0
  fout << "\t\t\t\"cmd\": ["
303
0
       << this->BuildMakeCommand(make, makefileName, targetName) << "],\n";
304
0
  fout << "\t\t\t\"working_dir\": \"${project_path}\",\n";
305
0
  fout << "\t\t\t\"file_regex\": \""
306
0
          "^(..[^:]*)(?::|\\\\()([0-9]+)(?::|\\\\))(?:([0-9]+):)?\\\\s*(.*)"
307
0
          "\"\n";
308
0
  fout << "\t\t}";
309
0
}
310
311
// Create the command line for building the given target using the selected
312
// make
313
std::string cmExtraSublimeTextGenerator::BuildMakeCommand(
314
  std::string const& make, std::string const& makefile,
315
  std::string const& target)
316
0
{
317
0
  std::string command = cmStrCat('"', make, '"');
318
0
  std::string generator = this->GlobalGenerator->GetName();
319
0
  if (generator == "NMake Makefiles") {
320
0
    std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
321
0
    command = cmStrCat(std::move(command), R"(, "/NOLOGO", "/f", ")",
322
0
                       std::move(makefileName), "\", \"", target, '"');
323
0
  } else if (generator == "Ninja") {
324
0
    std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
325
0
    command = cmStrCat(std::move(command), R"(, "-f", ")",
326
0
                       std::move(makefileName), "\", \"", target, '"');
327
0
  } else {
328
0
    std::string makefileName;
329
0
    if (generator == "MinGW Makefiles") {
330
      // no escaping of spaces in this case, see
331
      // https://gitlab.kitware.com/cmake/cmake/-/issues/10014
332
0
      makefileName = makefile;
333
0
    } else {
334
0
      makefileName = cmSystemTools::ConvertToOutputPath(makefile);
335
0
    }
336
0
    command = cmStrCat(std::move(command), R"(, "-f", ")",
337
0
                       std::move(makefileName), "\", \"", target, '"');
338
0
  }
339
0
  return command;
340
0
}
341
342
// TODO: Most of the code is picked up from the Ninja generator, refactor it.
343
std::string cmExtraSublimeTextGenerator::ComputeFlagsForObject(
344
  cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* gtgt)
345
0
{
346
0
  std::string flags;
347
0
  std::string language = source->GetOrDetermineLanguage();
348
0
  if (language.empty()) {
349
0
    language = "C";
350
0
  }
351
352
  // Explicitly add the explicit language flag before any other flag
353
  // so user flags can override it.
354
0
  gtgt->AddExplicitLanguageFlags(flags, *source);
355
356
0
  std::string const& config =
357
0
    lg->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
358
359
0
  lg->GetTargetCompileFlags(gtgt, config, language, flags);
360
361
  // Add source file specific flags.
362
0
  cmGeneratorExpressionInterpreter genexInterpreter(lg, config, gtgt,
363
0
                                                    language);
364
365
0
  std::string const COMPILE_FLAGS("COMPILE_FLAGS");
366
0
  if (cmValue cflags = source->GetProperty(COMPILE_FLAGS)) {
367
0
    lg->AppendFlags(flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS));
368
0
  }
369
370
0
  std::string const COMPILE_OPTIONS("COMPILE_OPTIONS");
371
0
  if (cmValue coptions = source->GetProperty(COMPILE_OPTIONS)) {
372
0
    lg->AppendCompileOptions(
373
0
      flags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS));
374
0
  }
375
376
0
  return flags;
377
0
}
378
379
// TODO: Refactor with
380
// void cmMakefileTargetGenerator::WriteTargetLanguageFlags().
381
std::string cmExtraSublimeTextGenerator::ComputeDefines(
382
  cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* target)
383
384
0
{
385
0
  std::set<std::string> defines;
386
0
  cmMakefile* makefile = lg->GetMakefile();
387
0
  std::string const& language = source->GetOrDetermineLanguage();
388
0
  std::string const& config = makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
389
0
  cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
390
0
                                                    language);
391
392
  // Add preprocessor definitions for this target and configuration.
393
0
  lg->GetTargetDefines(target, config, language, defines);
394
0
  std::string const COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
395
0
  if (cmValue compile_defs = source->GetProperty(COMPILE_DEFINITIONS)) {
396
0
    lg->AppendDefines(
397
0
      defines, genexInterpreter.Evaluate(*compile_defs, COMPILE_DEFINITIONS));
398
0
  }
399
400
0
  std::string defPropName =
401
0
    cmStrCat("COMPILE_DEFINITIONS_", cmSystemTools::UpperCase(config));
402
0
  if (cmValue config_compile_defs = source->GetProperty(defPropName)) {
403
0
    lg->AppendDefines(
404
0
      defines,
405
0
      genexInterpreter.Evaluate(*config_compile_defs, COMPILE_DEFINITIONS));
406
0
  }
407
408
0
  std::string definesString;
409
0
  lg->JoinDefines(defines, definesString, language);
410
411
0
  return definesString;
412
0
}
413
414
std::string cmExtraSublimeTextGenerator::ComputeIncludes(
415
  cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* target)
416
417
0
{
418
0
  std::vector<std::string> includes;
419
0
  cmMakefile* makefile = lg->GetMakefile();
420
0
  std::string const& language = source->GetOrDetermineLanguage();
421
0
  std::string const& config = makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
422
0
  cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
423
0
                                                    language);
424
425
  // Add include directories for this source file
426
0
  std::string const INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
427
0
  if (cmValue cincludes = source->GetProperty(INCLUDE_DIRECTORIES)) {
428
0
    lg->AppendIncludeDirectories(
429
0
      includes, genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES),
430
0
      *source);
431
0
  }
432
433
  // Add include directory flags.
434
0
  lg->GetIncludeDirectories(includes, target, language, config);
435
436
0
  std::string includesString =
437
0
    lg->GetIncludeFlags(includes, target, language, config, false);
438
439
0
  return includesString;
440
0
}
441
442
bool cmExtraSublimeTextGenerator::Open(std::string const& bindir,
443
                                       std::string const& projectName,
444
                                       bool dryRun)
445
0
{
446
0
  cmValue sublExecutable =
447
0
    this->GlobalGenerator->GetCMakeInstance()->GetCacheDefinition(
448
0
      "CMAKE_SUBLIMETEXT_EXECUTABLE");
449
0
  if (!sublExecutable) {
450
0
    return false;
451
0
  }
452
0
  if (cmIsNOTFOUND(*sublExecutable)) {
453
0
    return false;
454
0
  }
455
456
0
  std::string filename =
457
0
    cmStrCat(bindir, '/', projectName, ".sublime-project");
458
0
  if (dryRun) {
459
0
    return cmSystemTools::FileExists(filename, true);
460
0
  }
461
462
0
  return cmSystemTools::RunSingleCommand(
463
0
    { *sublExecutable, "--project", filename });
464
0
}