Files
CMake/Source/cmVisualStudioGeneratorOptions.cxx
AJIOB 6874efb592 MSVC: Always define a character set
When targeting the MSVC ABI, define `_MBCS` by default if the project
does not define `_SBCS` or `_UNICODE`.  Visual Studio has long defined
one of the three character set macros automatically.  For consistency,
define it when compiling for the MSVC ABI with other generators.
Add policy CMP0204 for compatibility.

Fixes: #27275
2025-10-07 09:29:32 -04:00

479 lines
14 KiB
C++

#include "cmVisualStudioGeneratorOptions.h"
#include <algorithm>
#include <map>
#include <sstream>
#include <utility>
#include <vector>
#include <cm/iterator>
#include <cmext/string_view>
#include "cmAlgorithms.h"
#include "cmLocalVisualStudioGenerator.h"
#include "cmOutputConverter.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
static void cmVS10EscapeForMSBuild(std::string& ret)
{
cmSystemTools::ReplaceString(ret, ";", "%3B");
}
cmVisualStudioGeneratorOptions::cmVisualStudioGeneratorOptions(
cmLocalVisualStudioGenerator* lg, Tool tool, cmVS7FlagTable const* table,
cmVS7FlagTable const* extraTable)
: cmIDEOptions()
, LocalGenerator(lg)
, CurrentTool(tool)
{
// Store the given flag tables.
this->AddTable(table);
this->AddTable(extraTable);
// Preprocessor definitions are not allowed for linker tools.
this->AllowDefine = (tool != Linker);
// include directories are not allowed for linker tools.
this->AllowInclude = (tool != Linker);
// Slash options are allowed for VS.
this->AllowSlash = true;
this->FortranRuntimeDebug = false;
this->FortranRuntimeDLL = false;
this->FortranRuntimeMT = false;
this->UnknownFlagField = "AdditionalOptions";
}
void cmVisualStudioGeneratorOptions::AddTable(cmVS7FlagTable const* table)
{
if (table) {
for (auto& flag : this->FlagTable) {
if (!flag) {
flag = table;
break;
}
}
}
}
void cmVisualStudioGeneratorOptions::ClearTables()
{
for (auto& flag : this->FlagTable) {
flag = nullptr;
}
}
void cmVisualStudioGeneratorOptions::FixExceptionHandlingDefault()
{
// Exception handling is on by default because the platform file has
// "/EHsc" in the flags. Normally, that will override this
// initialization to off, but the user has the option of removing
// the flag to disable exception handling. When the user does
// remove the flag we need to override the IDE default of on.
if (!this->LocalGenerator->IsVFProj()) {
// by default VS puts <ExceptionHandling></ExceptionHandling> empty
// for a project, to make our projects look the same put a new line
// and space over for the closing </ExceptionHandling> as the default
// value
this->FlagMap["ExceptionHandling"] = "\n ";
} else {
this->FlagMap["ExceptionHandling"] = "0";
}
}
void cmVisualStudioGeneratorOptions::SetVerboseMakefile(bool verbose)
{
// If verbose makefiles have been requested and the /nologo option
// was not given explicitly in the flags we want to add an attribute
// to the generated project to disable logo suppression. Otherwise
// the GUI default is to enable suppression.
//
// In '.vfproj' files, the value of this attribute should be
// "FALSE", instead of an empty string.
if (verbose &&
this->FlagMap.find("SuppressStartupBanner") == this->FlagMap.end()) {
this->FlagMap["SuppressStartupBanner"] =
!this->LocalGenerator->IsVFProj() ? "" : "FALSE";
}
}
bool cmVisualStudioGeneratorOptions::UsingDebugInfo() const
{
if (this->CurrentTool != CSharpCompiler) {
return this->FlagMap.find("DebugInformationFormat") != this->FlagMap.end();
}
auto i = this->FlagMap.find("DebugType");
if (i != this->FlagMap.end()) {
if (i->second.size() == 1) {
return i->second[0] != "none"_s;
}
}
return false;
}
cm::optional<bool> cmVisualStudioGeneratorOptions::UsingDebugRuntime() const
{
cm::optional<bool> result;
if (char const* rtl = this->GetFlag("RuntimeLibrary")) {
result = strstr(rtl, "Debug") != nullptr;
}
return result;
}
bool cmVisualStudioGeneratorOptions::IsWinRt() const
{
return this->FlagMap.find("CompileAsWinRT") != this->FlagMap.end();
}
bool cmVisualStudioGeneratorOptions::IsManaged() const
{
return this->FlagMap.find("CompileAsManaged") != this->FlagMap.end();
}
void cmVisualStudioGeneratorOptions::CacheCharsetValue() const
{
using MsvcCharSet = cmGeneratorTarget::MsvcCharSet;
if (this->CachedCharset.has_value()) {
return;
}
MsvcCharSet newValue = MsvcCharSet::None;
// Look for a any charset definition.
// Real charset will be updated if something is found.
auto it = std::find_if(this->Defines.begin(), this->Defines.end(),
[&newValue](std::string const& define) {
newValue =
cmGeneratorTarget::GetMsvcCharSet(define);
return newValue != MsvcCharSet::None;
});
if (it == this->Defines.end()) {
// Default to multi-byte, as Visual Studio does
newValue = MsvcCharSet::MultiByte;
}
this->CachedCharset = newValue;
}
bool cmVisualStudioGeneratorOptions::UsingUnicode() const
{
this->CacheCharsetValue();
return this->CachedCharset.value() ==
cmGeneratorTarget::MsvcCharSet::Unicode;
}
bool cmVisualStudioGeneratorOptions::UsingSBCS() const
{
this->CacheCharsetValue();
return this->CachedCharset.value() ==
cmGeneratorTarget::MsvcCharSet::SingleByte;
}
void cmVisualStudioGeneratorOptions::FixCudaCodeGeneration()
{
// Create an empty CodeGeneration field, and pass the the actual
// compile flags via additional options so that we have consistent
// behavior and avoid issues with MSBuild extensions injecting
// virtual code when we request real only.
FlagValue& code_gen_flag = this->FlagMap["CodeGeneration"];
code_gen_flag = "";
}
void cmVisualStudioGeneratorOptions::FixManifestUACFlags()
{
static std::string const ENABLE_UAC = "EnableUAC";
if (!HasFlag(ENABLE_UAC)) {
return;
}
std::string const uacFlag = GetFlag(ENABLE_UAC);
std::vector<std::string> subOptions;
cmsys::SystemTools::Split(uacFlag, subOptions, ' ');
if (subOptions.empty()) {
AddFlag(ENABLE_UAC, "true");
return;
}
if (subOptions.size() == 1 && subOptions[0] == "NO"_s) {
AddFlag(ENABLE_UAC, "false");
return;
}
std::map<std::string, std::string> uacMap;
uacMap["level"] = "UACExecutionLevel";
uacMap["uiAccess"] = "UACUIAccess";
std::map<std::string, std::string> uacExecuteLevelMap;
uacExecuteLevelMap["asInvoker"] = "AsInvoker";
uacExecuteLevelMap["highestAvailable"] = "HighestAvailable";
uacExecuteLevelMap["requireAdministrator"] = "RequireAdministrator";
for (std::string const& subopt : subOptions) {
std::vector<std::string> keyValue;
cmsys::SystemTools::Split(subopt, keyValue, '=');
if (keyValue.size() != 2 || (uacMap.find(keyValue[0]) == uacMap.end())) {
// ignore none key=value option or unknown flags
continue;
}
if (keyValue[1].front() == '\'' && keyValue[1].back() == '\'') {
keyValue[1] = keyValue[1].substr(
1, std::max(std::string::size_type(0), keyValue[1].length() - 2));
}
if (keyValue[0] == "level"_s) {
if (uacExecuteLevelMap.find(keyValue[1]) == uacExecuteLevelMap.end()) {
// unknown level value
continue;
}
AddFlag(uacMap[keyValue[0]], uacExecuteLevelMap[keyValue[1]]);
continue;
}
if (keyValue[0] == "uiAccess"_s) {
if (keyValue[1] != "true"_s && keyValue[1] != "false"_s) {
// unknown uiAccess value
continue;
}
AddFlag(uacMap[keyValue[0]], keyValue[1]);
continue;
}
// unknown sub option
}
AddFlag(ENABLE_UAC, "true");
}
void cmVisualStudioGeneratorOptions::Parse(std::string const& flags)
{
// Parse the input string as a windows command line since the string
// is intended for writing directly into the build files.
std::vector<std::string> args;
cmSystemTools::ParseWindowsCommandLine(flags.c_str(), args);
// Process flags that need to be represented specially in the IDE
// project file.
for (std::string const& ai : args) {
this->HandleFlag(ai);
}
}
void cmVisualStudioGeneratorOptions::ParseFinish()
{
if (this->CurrentTool == FortranCompiler) {
// "RuntimeLibrary" attribute values:
// "rtMultiThreaded", "0", /threads /libs:static
// "rtMultiThreadedDLL", "2", /threads /libs:dll
// "rtMultiThreadedDebug", "1", /threads /dbglibs /libs:static
// "rtMultiThreadedDebugDLL", "3", /threads /dbglibs /libs:dll
// These seem unimplemented by the IDE:
// "rtSingleThreaded", "4", /libs:static
// "rtSingleThreadedDLL", "10", /libs:dll
// "rtSingleThreadedDebug", "5", /dbglibs /libs:static
// "rtSingleThreadedDebugDLL", "11", /dbglibs /libs:dll
std::string rl =
cmStrCat("rtMultiThreaded", this->FortranRuntimeDebug ? "Debug" : "",
this->FortranRuntimeDLL ? "DLL" : "");
this->FlagMap["RuntimeLibrary"] = rl;
}
if (this->CurrentTool == CudaCompiler) {
auto i = this->FlagMap.find("CudaRuntime");
if (i != this->FlagMap.end() && i->second.size() == 1) {
std::string& cudaRuntime = i->second[0];
if (cudaRuntime == "static"_s) {
cudaRuntime = "Static";
} else if (cudaRuntime == "shared"_s) {
cudaRuntime = "Shared";
} else if (cudaRuntime == "none"_s) {
cudaRuntime = "None";
}
}
}
}
void cmVisualStudioGeneratorOptions::PrependInheritedString(
std::string const& key)
{
auto i = this->FlagMap.find(key);
if (i == this->FlagMap.end() || i->second.size() != 1) {
return;
}
std::string& value = i->second[0];
value = cmStrCat("%(", key, ") ", value);
}
void cmVisualStudioGeneratorOptions::Reparse(std::string const& key)
{
auto i = this->FlagMap.find(key);
if (i == this->FlagMap.end() || i->second.size() != 1) {
return;
}
std::string const original = i->second[0];
i->second[0] = "";
this->UnknownFlagField = key;
this->Parse(original);
}
void cmVisualStudioGeneratorOptions::StoreUnknownFlag(std::string const& flag)
{
// Look for Intel Fortran flags that do not map well in the flag table.
if (this->CurrentTool == FortranCompiler) {
if (flag == "/dbglibs"_s || flag == "-dbglibs"_s) {
this->FortranRuntimeDebug = true;
return;
}
if (flag == "/threads"_s || flag == "-threads"_s) {
this->FortranRuntimeMT = true;
return;
}
if (flag == "/libs:dll"_s || flag == "-libs:dll"_s) {
this->FortranRuntimeDLL = true;
return;
}
if (flag == "/libs:static"_s || flag == "-libs:static"_s) {
this->FortranRuntimeDLL = false;
return;
}
}
// This option is not known. Store it in the output flags.
std::string const opts = cmOutputConverter::EscapeWindowsShellArgument(
flag.c_str(),
cmOutputConverter::Shell_Flag_AllowMakeVariables |
cmOutputConverter::Shell_Flag_VSIDE);
this->AppendFlagString(this->UnknownFlagField, opts);
}
cmIDEOptions::FlagValue cmVisualStudioGeneratorOptions::TakeFlag(
std::string const& key)
{
FlagValue value;
auto i = this->FlagMap.find(key);
if (i != this->FlagMap.end()) {
value = i->second;
this->FlagMap.erase(i);
}
return value;
}
void cmVisualStudioGeneratorOptions::SetConfiguration(
std::string const& config)
{
this->Configuration = config;
}
std::string const& cmVisualStudioGeneratorOptions::GetConfiguration() const
{
return this->Configuration;
}
void cmVisualStudioGeneratorOptions::OutputPreprocessorDefinitions(
std::ostream& fout, int indent, std::string const& lang)
{
if (this->Defines.empty()) {
return;
}
std::string tag = "PreprocessorDefinitions";
if (lang == "CUDA"_s) {
tag = "Defines";
}
std::ostringstream oss;
if (!this->LocalGenerator->IsVFProj()) {
oss << "%(" << tag << ')';
}
auto de = cmRemoveDuplicates(this->Defines);
for (std::string const& di : cmMakeRange(this->Defines.cbegin(), de)) {
std::string define;
if (!this->LocalGenerator->IsVFProj()) {
// Escape the definition for MSBuild.
define = di;
cmVS10EscapeForMSBuild(define);
if (lang == "RC"_s) {
cmSystemTools::ReplaceString(define, "\"", "\\\"");
}
} else {
// Escape the definition for the compiler.
define = this->LocalGenerator->EscapeForShell(di, true);
}
// Store the flag in the project file.
oss << ';' << define;
}
this->OutputFlag(fout, indent, tag, oss.str());
}
void cmVisualStudioGeneratorOptions::OutputAdditionalIncludeDirectories(
std::ostream& fout, int indent, std::string const& lang)
{
if (this->Includes.empty()) {
return;
}
std::string tag = "AdditionalIncludeDirectories";
if (lang == "CUDA"_s) {
tag = "Include";
} else if (lang == "ASM_MASM"_s || lang == "ASM_NASM"_s) {
tag = "IncludePaths";
}
std::ostringstream oss;
char const* sep = "";
for (std::string include : this->Includes) {
// first convert all of the slashes
std::string::size_type pos = 0;
while ((pos = include.find('/', pos)) != std::string::npos) {
include[pos] = '\\';
pos++;
}
if (lang == "ASM_NASM"_s) {
include += '\\';
}
// Escape this include for the MSBuild.
if (!this->LocalGenerator->IsVFProj()) {
cmVS10EscapeForMSBuild(include);
}
oss << sep << include;
sep = ";";
if (lang == "Fortran"_s) {
include += "/$(ConfigurationName)";
oss << sep << include;
}
}
if (!this->LocalGenerator->IsVFProj()) {
oss << sep << "%(" << tag << ')';
}
this->OutputFlag(fout, indent, tag, oss.str());
}
void cmVisualStudioGeneratorOptions::OutputFlagMap(std::ostream& fout,
int indent)
{
for (auto const& m : this->FlagMap) {
std::ostringstream oss;
char const* sep = "";
for (std::string i : m.second) {
if (!this->LocalGenerator->IsVFProj()) {
cmVS10EscapeForMSBuild(i);
}
oss << sep << i;
sep = ";";
}
this->OutputFlag(fout, indent, m.first, oss.str());
}
}