/* 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 #include #include #include #include #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 "cmProperty.h" #include "cmSourceFile.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.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."); 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& 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& 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; AppendAllTargets(lgs, mf, fout, sourceFileFlags); // End of build_systems fout << "\n\t]"; std::string systemName = mf->GetSafeDefinition("CMAKE_SYSTEM_NAME"); std::vector 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& 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 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())) .first; } std::vector& 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"; } 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 (cmProp cflags = source->GetProperty(COMPILE_FLAGS)) { lg->AppendFlags(flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS)); } const std::string COMPILE_OPTIONS("COMPILE_OPTIONS"); if (cmProp 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 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 (cmProp 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 (cmProp 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 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 (cmProp 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, true, false, config); return includesString; } bool cmExtraSublimeTextGenerator::Open(const std::string& bindir, const std::string& projectName, bool dryRun) { cmProp 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 }); }