
The "extra" generators were created in CMake's early years to provide support for users of specific IDEs by directly generating their project files alongside make or ninja files. Nowadays the file-api provides a more generic, maintainable, well-tested, and robust way for IDEs to view CMake project build trees. Deprecate the legacy "extra" generators to encourage the corresponding IDEs to use the file-api. Fixes: #19090
466 lines
16 KiB
C++
466 lines
16 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmExtraSublimeTextGenerator.h"
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include "cmsys/RegularExpression.hxx"
|
|
|
|
#include "cmGeneratedFileStream.h"
|
|
#include "cmGeneratorExpression.h"
|
|
#include "cmGeneratorTarget.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmLocalGenerator.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmMessageType.h"
|
|
#include "cmSourceFile.h"
|
|
#include "cmStateTypes.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmValue.h"
|
|
#include "cmake.h"
|
|
|
|
/*
|
|
Sublime Text 2 Generator
|
|
Author: Morné Chamberlain
|
|
This generator was initially based off of the CodeBlocks generator.
|
|
|
|
Some useful URLs:
|
|
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
|
|
*/
|
|
|
|
cmExternalMakefileProjectGeneratorFactory*
|
|
cmExtraSublimeTextGenerator::GetFactory()
|
|
{
|
|
static cmExternalMakefileProjectGeneratorSimpleFactory<
|
|
cmExtraSublimeTextGenerator>
|
|
factory("Sublime Text 2",
|
|
"Generates Sublime Text 2 project files (deprecated).");
|
|
|
|
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;
|
|
}
|
|
|
|
cmExtraSublimeTextGenerator::cmExtraSublimeTextGenerator()
|
|
{
|
|
this->ExcludeBuildFolder = false;
|
|
}
|
|
|
|
void cmExtraSublimeTextGenerator::Generate()
|
|
{
|
|
this->ExcludeBuildFolder = this->GlobalGenerator->GlobalSettingIsOn(
|
|
"CMAKE_SUBLIME_TEXT_2_EXCLUDE_BUILD_TREE");
|
|
this->EnvSettings = this->GlobalGenerator->GetSafeGlobalSetting(
|
|
"CMAKE_SUBLIME_TEXT_2_ENV_SETTINGS");
|
|
|
|
// for each sub project in the project create a sublime text 2 project
|
|
for (auto const& it : this->GlobalGenerator->GetProjectMap()) {
|
|
// create a project file
|
|
this->CreateProjectFile(it.second);
|
|
}
|
|
}
|
|
|
|
void cmExtraSublimeTextGenerator::CreateProjectFile(
|
|
const std::vector<cmLocalGenerator*>& lgs)
|
|
{
|
|
std::string outputDir = lgs[0]->GetCurrentBinaryDirectory();
|
|
std::string projectName = lgs[0]->GetProjectName();
|
|
|
|
const std::string filename =
|
|
outputDir + "/" + projectName + ".sublime-project";
|
|
|
|
this->CreateNewProjectFile(lgs, filename);
|
|
}
|
|
|
|
void cmExtraSublimeTextGenerator::CreateNewProjectFile(
|
|
const std::vector<cmLocalGenerator*>& lgs, const std::string& filename)
|
|
{
|
|
const cmMakefile* mf = lgs[0]->GetMakefile();
|
|
|
|
cmGeneratedFileStream fout(filename);
|
|
if (!fout) {
|
|
return;
|
|
}
|
|
|
|
const std::string& sourceRootRelativeToOutput = cmSystemTools::RelativePath(
|
|
lgs[0]->GetBinaryDirectory(), lgs[0]->GetSourceDirectory());
|
|
// Write the folder entries to the project file
|
|
fout << "{\n";
|
|
fout << "\t\"folders\":\n\t[\n\t";
|
|
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()) &&
|
|
((outputRelativeToSourceRoot.length() < 3) ||
|
|
(outputRelativeToSourceRoot.substr(0, 3) != "../"))) {
|
|
if (this->ExcludeBuildFolder) {
|
|
fout << ",\n\t\t\t\"folder_exclude_patterns\": [\""
|
|
<< outputRelativeToSourceRoot << "\"]";
|
|
}
|
|
}
|
|
} else {
|
|
fout << "\t{\n\t\t\t\"path\": \"./\"";
|
|
}
|
|
fout << "\n\t\t}";
|
|
// End of the folders section
|
|
fout << "\n\t]";
|
|
|
|
// 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
|
|
MapSourceFileFlags sourceFileFlags;
|
|
this->AppendAllTargets(lgs, mf, fout, sourceFileFlags);
|
|
|
|
// End of build_systems
|
|
fout << "\n\t]";
|
|
std::string systemName = mf->GetSafeDefinition("CMAKE_SYSTEM_NAME");
|
|
std::vector<std::string> tokens = cmExpandedList(this->EnvSettings);
|
|
|
|
if (!this->EnvSettings.empty()) {
|
|
fout << ",";
|
|
fout << "\n\t\"env\":";
|
|
fout << "\n\t{";
|
|
fout << "\n\t\t" << systemName << ":";
|
|
fout << "\n\t\t{";
|
|
for (std::string const& t : tokens) {
|
|
size_t const pos = t.find_first_of('=');
|
|
|
|
if (pos != std::string::npos) {
|
|
std::string varName = t.substr(0, pos);
|
|
std::string varValue = t.substr(pos + 1);
|
|
|
|
fout << "\n\t\t\t\"" << varName << "\":\"" << varValue << "\"";
|
|
} else {
|
|
std::ostringstream e;
|
|
e << "Could not parse Env Vars specified in "
|
|
"\"CMAKE_SUBLIME_TEXT_2_ENV_SETTINGS\""
|
|
<< ", corrupted string " << t;
|
|
mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
|
|
}
|
|
}
|
|
fout << "\n\t\t}";
|
|
fout << "\n\t}";
|
|
}
|
|
fout << "\n}";
|
|
}
|
|
|
|
void cmExtraSublimeTextGenerator::AppendAllTargets(
|
|
const std::vector<cmLocalGenerator*>& lgs, const cmMakefile* mf,
|
|
cmGeneratedFileStream& fout, MapSourceFileFlags& sourceFileFlags)
|
|
{
|
|
const std::string& make = mf->GetRequiredDefinition("CMAKE_MAKE_PROGRAM");
|
|
std::string compiler;
|
|
if (!lgs.empty()) {
|
|
this->AppendTarget(fout, "all", lgs[0], nullptr, make.c_str(), mf,
|
|
compiler.c_str(), sourceFileFlags, true);
|
|
this->AppendTarget(fout, "clean", lgs[0], nullptr, make.c_str(), mf,
|
|
compiler.c_str(), sourceFileFlags, false);
|
|
}
|
|
|
|
// add all executable and library targets and some of the GLOBAL
|
|
// and UTILITY targets
|
|
for (cmLocalGenerator* lg : lgs) {
|
|
cmMakefile* makefile = lg->GetMakefile();
|
|
const auto& targets = lg->GetGeneratorTargets();
|
|
for (const auto& target : targets) {
|
|
std::string targetName = target->GetName();
|
|
switch (target->GetType()) {
|
|
case cmStateEnums::GLOBAL_TARGET: {
|
|
// Only add the global targets from CMAKE_BINARY_DIR,
|
|
// not from the subdirs
|
|
if (lg->GetCurrentBinaryDirectory() == lg->GetBinaryDirectory()) {
|
|
this->AppendTarget(fout, targetName, lg, nullptr, make.c_str(),
|
|
makefile, compiler.c_str(), sourceFileFlags,
|
|
false);
|
|
}
|
|
} break;
|
|
case cmStateEnums::UTILITY:
|
|
// Add all utility targets, except the Nightly/Continuous/
|
|
// Experimental-"sub"targets as e.g. NightlyStart
|
|
if ((cmHasLiteralPrefix(targetName, "Nightly") &&
|
|
(targetName != "Nightly")) ||
|
|
(cmHasLiteralPrefix(targetName, "Continuous") &&
|
|
(targetName != "Continuous")) ||
|
|
(cmHasLiteralPrefix(targetName, "Experimental") &&
|
|
(targetName != "Experimental"))) {
|
|
break;
|
|
}
|
|
|
|
this->AppendTarget(fout, targetName, lg, nullptr, make.c_str(),
|
|
makefile, compiler.c_str(), sourceFileFlags,
|
|
false);
|
|
break;
|
|
case cmStateEnums::EXECUTABLE:
|
|
case cmStateEnums::STATIC_LIBRARY:
|
|
case cmStateEnums::SHARED_LIBRARY:
|
|
case cmStateEnums::MODULE_LIBRARY:
|
|
case cmStateEnums::OBJECT_LIBRARY: {
|
|
this->AppendTarget(fout, targetName, lg, target.get(), make.c_str(),
|
|
makefile, compiler.c_str(), sourceFileFlags,
|
|
false);
|
|
std::string fastTarget = cmStrCat(targetName, "/fast");
|
|
this->AppendTarget(fout, fastTarget, lg, target.get(), make.c_str(),
|
|
makefile, compiler.c_str(), sourceFileFlags,
|
|
false);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void cmExtraSublimeTextGenerator::AppendTarget(
|
|
cmGeneratedFileStream& fout, const std::string& targetName,
|
|
cmLocalGenerator* lg, cmGeneratorTarget* target, const char* make,
|
|
const cmMakefile* makefile, const char* /*compiler*/,
|
|
MapSourceFileFlags& sourceFileFlags, bool firstTarget)
|
|
{
|
|
|
|
if (target != nullptr) {
|
|
std::vector<cmSourceFile*> sourceFiles;
|
|
target->GetSourceFiles(sourceFiles,
|
|
makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
|
|
for (cmSourceFile* sourceFile : sourceFiles) {
|
|
auto sourceFileFlagsIter =
|
|
sourceFileFlags.find(sourceFile->ResolveFullPath());
|
|
if (sourceFileFlagsIter == sourceFileFlags.end()) {
|
|
sourceFileFlagsIter =
|
|
sourceFileFlags
|
|
.insert(MapSourceFileFlags::value_type(
|
|
sourceFile->ResolveFullPath(), std::vector<std::string>()))
|
|
.first;
|
|
}
|
|
std::vector<std::string>& flags = sourceFileFlagsIter->second;
|
|
std::string flagsString =
|
|
this->ComputeFlagsForObject(sourceFile, lg, target);
|
|
std::string definesString = this->ComputeDefines(sourceFile, lg, target);
|
|
std::string includesString =
|
|
this->ComputeIncludes(sourceFile, lg, target);
|
|
flags.clear();
|
|
cmsys::RegularExpression flagRegex;
|
|
// Regular expression to extract compiler flags from a string
|
|
// https://gist.github.com/3944250
|
|
const char* regexString =
|
|
R"((^|[ ])-[DIOUWfgs][^= ]+(=\"[^"]+\"|=[^"][^ ]+)?)";
|
|
flagRegex.compile(regexString);
|
|
std::string workString =
|
|
cmStrCat(flagsString, " ", definesString, " ", includesString);
|
|
while (flagRegex.find(workString)) {
|
|
std::string::size_type start = flagRegex.start();
|
|
if (workString[start] == ' ') {
|
|
start++;
|
|
}
|
|
flags.push_back(workString.substr(start, flagRegex.end() - start));
|
|
if (flagRegex.end() < workString.size()) {
|
|
workString = workString.substr(flagRegex.end());
|
|
} else {
|
|
workString.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ninja uses ninja.build files (look for a way to get the output file name
|
|
// from cmMakefile or something)
|
|
std::string makefileName;
|
|
if (this->GlobalGenerator->GetName() == "Ninja") {
|
|
makefileName = "build.ninja";
|
|
} else {
|
|
makefileName = "Makefile";
|
|
}
|
|
if (!firstTarget) {
|
|
fout << ",\n\t";
|
|
}
|
|
fout << "\t{\n\t\t\t\"name\": \"" << lg->GetProjectName() << " - "
|
|
<< targetName << "\",\n";
|
|
fout << "\t\t\t\"cmd\": ["
|
|
<< this->BuildMakeCommand(make, makefileName, targetName) << "],\n";
|
|
fout << "\t\t\t\"working_dir\": \"${project_path}\",\n";
|
|
fout << "\t\t\t\"file_regex\": \""
|
|
"^(..[^:]*)(?::|\\\\()([0-9]+)(?::|\\\\))(?:([0-9]+):)?\\\\s*(.*)"
|
|
"\"\n";
|
|
fout << "\t\t}";
|
|
}
|
|
|
|
// Create the command line for building the given target using the selected
|
|
// make
|
|
std::string cmExtraSublimeTextGenerator::BuildMakeCommand(
|
|
const std::string& make, const std::string& makefile,
|
|
const std::string& target)
|
|
{
|
|
std::string command = cmStrCat('"', make, '"');
|
|
std::string generator = this->GlobalGenerator->GetName();
|
|
if (generator == "NMake Makefiles") {
|
|
std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
|
|
command += R"(, "/NOLOGO", "/f", ")";
|
|
command += makefileName + "\"";
|
|
command += ", \"" + target + "\"";
|
|
} else if (generator == "Ninja") {
|
|
std::string makefileName = cmSystemTools::ConvertToOutputPath(makefile);
|
|
command += R"(, "-f", ")";
|
|
command += makefileName + "\"";
|
|
command += ", \"" + target + "\"";
|
|
} else {
|
|
std::string makefileName;
|
|
if (generator == "MinGW Makefiles") {
|
|
// no escaping of spaces in this case, see
|
|
// https://gitlab.kitware.com/cmake/cmake/-/issues/10014
|
|
makefileName = makefile;
|
|
} else {
|
|
makefileName = cmSystemTools::ConvertToOutputPath(makefile);
|
|
}
|
|
command += R"(, "-f", ")";
|
|
command += makefileName + "\"";
|
|
command += ", \"" + target + "\"";
|
|
}
|
|
return command;
|
|
}
|
|
|
|
// TODO: Most of the code is picked up from the Ninja generator, refactor it.
|
|
std::string cmExtraSublimeTextGenerator::ComputeFlagsForObject(
|
|
cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* gtgt)
|
|
{
|
|
std::string flags;
|
|
std::string language = source->GetOrDetermineLanguage();
|
|
if (language.empty()) {
|
|
language = "C";
|
|
}
|
|
|
|
// Explicitly add the explicit language flag before any other flag
|
|
// so user flags can override it.
|
|
gtgt->AddExplicitLanguageFlags(flags, *source);
|
|
|
|
std::string const& config =
|
|
lg->GetMakefile()->GetSafeDefinition("CMAKE_BUILD_TYPE");
|
|
|
|
lg->GetTargetCompileFlags(gtgt, config, language, flags);
|
|
|
|
// Add source file specific flags.
|
|
cmGeneratorExpressionInterpreter genexInterpreter(lg, config, gtgt,
|
|
language);
|
|
|
|
const std::string COMPILE_FLAGS("COMPILE_FLAGS");
|
|
if (cmValue cflags = source->GetProperty(COMPILE_FLAGS)) {
|
|
lg->AppendFlags(flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS));
|
|
}
|
|
|
|
const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
|
|
if (cmValue coptions = source->GetProperty(COMPILE_OPTIONS)) {
|
|
lg->AppendCompileOptions(
|
|
flags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS));
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
// TODO: Refactor with
|
|
// void cmMakefileTargetGenerator::WriteTargetLanguageFlags().
|
|
std::string cmExtraSublimeTextGenerator::ComputeDefines(
|
|
cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* target)
|
|
|
|
{
|
|
std::set<std::string> defines;
|
|
cmMakefile* makefile = lg->GetMakefile();
|
|
const std::string& language = source->GetOrDetermineLanguage();
|
|
const std::string& config = makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
|
|
cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
|
|
language);
|
|
|
|
// Add preprocessor definitions for this target and configuration.
|
|
lg->GetTargetDefines(target, config, language, defines);
|
|
const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
|
|
if (cmValue compile_defs = source->GetProperty(COMPILE_DEFINITIONS)) {
|
|
lg->AppendDefines(
|
|
defines, genexInterpreter.Evaluate(*compile_defs, COMPILE_DEFINITIONS));
|
|
}
|
|
|
|
std::string defPropName =
|
|
cmStrCat("COMPILE_DEFINITIONS_", cmSystemTools::UpperCase(config));
|
|
if (cmValue config_compile_defs = source->GetProperty(defPropName)) {
|
|
lg->AppendDefines(
|
|
defines,
|
|
genexInterpreter.Evaluate(*config_compile_defs, COMPILE_DEFINITIONS));
|
|
}
|
|
|
|
std::string definesString;
|
|
lg->JoinDefines(defines, definesString, language);
|
|
|
|
return definesString;
|
|
}
|
|
|
|
std::string cmExtraSublimeTextGenerator::ComputeIncludes(
|
|
cmSourceFile* source, cmLocalGenerator* lg, cmGeneratorTarget* target)
|
|
|
|
{
|
|
std::vector<std::string> includes;
|
|
cmMakefile* makefile = lg->GetMakefile();
|
|
const std::string& language = source->GetOrDetermineLanguage();
|
|
const std::string& config = makefile->GetSafeDefinition("CMAKE_BUILD_TYPE");
|
|
cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
|
|
language);
|
|
|
|
// Add include directories for this source file
|
|
const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
|
|
if (cmValue cincludes = source->GetProperty(INCLUDE_DIRECTORIES)) {
|
|
lg->AppendIncludeDirectories(
|
|
includes, genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES),
|
|
*source);
|
|
}
|
|
|
|
// Add include directory flags.
|
|
lg->GetIncludeDirectories(includes, target, language, config);
|
|
|
|
std::string includesString =
|
|
lg->GetIncludeFlags(includes, target, language, config, false);
|
|
|
|
return includesString;
|
|
}
|
|
|
|
bool cmExtraSublimeTextGenerator::Open(const std::string& bindir,
|
|
const std::string& projectName,
|
|
bool dryRun)
|
|
{
|
|
cmValue sublExecutable =
|
|
this->GlobalGenerator->GetCMakeInstance()->GetCacheDefinition(
|
|
"CMAKE_SUBLIMETEXT_EXECUTABLE");
|
|
if (!sublExecutable) {
|
|
return false;
|
|
}
|
|
if (cmIsNOTFOUND(*sublExecutable)) {
|
|
return false;
|
|
}
|
|
|
|
std::string filename = bindir + "/" + projectName + ".sublime-project";
|
|
if (dryRun) {
|
|
return cmSystemTools::FileExists(filename, true);
|
|
}
|
|
|
|
return cmSystemTools::RunSingleCommand(
|
|
{ *sublExecutable, "--project", filename });
|
|
}
|