| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmXCodeScheme.h" |
| |
| #include <iomanip> |
| #include <sstream> |
| #include <utility> |
| |
| #include <cmext/algorithm> |
| #include <cmext/string_view> |
| |
| #include "cmsys/String.h" |
| |
| #include "cmGeneratedFileStream.h" |
| #include "cmGeneratorExpression.h" |
| #include "cmGeneratorTarget.h" |
| #include "cmList.h" |
| #include "cmStateTypes.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmValue.h" |
| #include "cmXCodeObject.h" |
| #include "cmXMLWriter.h" |
| |
| class cmLocalGenerator; |
| |
| cmXCodeScheme::cmXCodeScheme(cmLocalGenerator* lg, cmXCodeObject* xcObj, |
| TestObjects tests, |
| const std::vector<std::string>& configList, |
| unsigned int xcVersion) |
| : LocalGenerator(lg) |
| , Target(xcObj) |
| , Tests(std::move(tests)) |
| , TargetName(xcObj->GetTarget()->GetName()) |
| , ConfigList(configList) |
| , XcodeVersion(xcVersion) |
| { |
| } |
| |
| void cmXCodeScheme::WriteXCodeSharedScheme(const std::string& xcProjDir, |
| const std::string& container) |
| { |
| // Create shared scheme sub-directory tree |
| // |
| std::string xcodeSchemeDir = cmStrCat(xcProjDir, "/xcshareddata/xcschemes"); |
| cmSystemTools::MakeDirectory(xcodeSchemeDir); |
| |
| std::string xcodeSchemeFile = |
| cmStrCat(xcodeSchemeDir, '/', this->TargetName, ".xcscheme"); |
| |
| cmGeneratedFileStream fout(xcodeSchemeFile); |
| fout.SetCopyIfDifferent(true); |
| if (!fout) { |
| return; |
| } |
| |
| WriteXCodeXCScheme(fout, container); |
| } |
| |
| void cmXCodeScheme::WriteXCodeXCScheme(std::ostream& fout, |
| const std::string& container) |
| { |
| cmXMLWriter xout(fout); |
| xout.SetIndentationElement(std::string(3, ' ')); |
| xout.StartDocument(); |
| |
| xout.StartElement("Scheme"); |
| xout.BreakAttributes(); |
| xout.Attribute("LastUpgradeVersion", WriteVersionString()); |
| xout.Attribute("version", "1.3"); |
| |
| cmValue propDftCfg = |
| Target->GetTarget()->GetProperty("XCODE_SCHEME_LAUNCH_CONFIGURATION"); |
| std::string launchConfiguration = |
| !propDftCfg.IsEmpty() ? *propDftCfg : "Debug"; |
| |
| WriteBuildAction(xout, container); |
| WriteTestAction(xout, FindConfiguration("Debug"), container); |
| WriteLaunchAction(xout, FindConfiguration(launchConfiguration), container); |
| WriteProfileAction(xout, FindConfiguration("Release"), container); |
| WriteAnalyzeAction(xout, FindConfiguration("Debug")); |
| WriteArchiveAction(xout, FindConfiguration("Release")); |
| |
| xout.EndElement(); |
| } |
| |
| void cmXCodeScheme::WriteBuildAction(cmXMLWriter& xout, |
| const std::string& container) |
| { |
| xout.StartElement("BuildAction"); |
| xout.BreakAttributes(); |
| xout.Attribute("parallelizeBuildables", "YES"); |
| xout.Attribute("buildImplicitDependencies", "YES"); |
| |
| xout.StartElement("BuildActionEntries"); |
| xout.StartElement("BuildActionEntry"); |
| xout.BreakAttributes(); |
| xout.Attribute("buildForTesting", "YES"); |
| xout.Attribute("buildForRunning", "YES"); |
| xout.Attribute("buildForProfiling", "YES"); |
| xout.Attribute("buildForArchiving", "YES"); |
| xout.Attribute("buildForAnalyzing", "YES"); |
| |
| WriteBuildableReference(xout, this->Target, container); |
| |
| xout.EndElement(); // BuildActionEntry |
| xout.EndElement(); // BuildActionEntries |
| xout.EndElement(); // BuildAction |
| } |
| |
| void cmXCodeScheme::WriteTestAction(cmXMLWriter& xout, |
| const std::string& configuration, |
| const std::string& container) |
| { |
| xout.StartElement("TestAction"); |
| xout.BreakAttributes(); |
| xout.Attribute("buildConfiguration", configuration); |
| xout.Attribute("selectedDebuggerIdentifier", |
| "Xcode.DebuggerFoundation.Debugger.LLDB"); |
| xout.Attribute("selectedLauncherIdentifier", |
| "Xcode.DebuggerFoundation.Launcher.LLDB"); |
| xout.Attribute("shouldUseLaunchSchemeArgsEnv", "YES"); |
| |
| xout.StartElement("Testables"); |
| for (auto const* test : this->Tests) { |
| xout.StartElement("TestableReference"); |
| xout.BreakAttributes(); |
| xout.Attribute("skipped", "NO"); |
| WriteBuildableReference(xout, test, container); |
| xout.EndElement(); // TestableReference |
| } |
| xout.EndElement(); |
| |
| if (IsTestable()) { |
| xout.StartElement("MacroExpansion"); |
| WriteBuildableReference(xout, this->Target, container); |
| xout.EndElement(); // MacroExpansion |
| } |
| |
| xout.StartElement("AdditionalOptions"); |
| xout.EndElement(); |
| |
| xout.EndElement(); // TestAction |
| } |
| |
| void cmXCodeScheme::WriteLaunchAction(cmXMLWriter& xout, |
| const std::string& configuration, |
| const std::string& container) |
| { |
| xout.StartElement("LaunchAction"); |
| xout.BreakAttributes(); |
| xout.Attribute("buildConfiguration", configuration); |
| xout.Attribute("selectedDebuggerIdentifier", |
| "Xcode.DebuggerFoundation.Debugger.LLDB"); |
| xout.Attribute("selectedLauncherIdentifier", |
| "Xcode.DebuggerFoundation.Launcher.LLDB"); |
| { |
| cmValue launchMode = |
| this->Target->GetTarget()->GetProperty("XCODE_SCHEME_LAUNCH_MODE"); |
| std::string value = "0"; // == 'AUTO' |
| if (launchMode && *launchMode == "WAIT"_s) { |
| value = "1"; |
| } |
| xout.Attribute("launchStyle", value); |
| } |
| WriteCustomWorkingDirectory(xout, configuration); |
| |
| xout.Attribute("ignoresPersistentStateOnLaunch", "NO"); |
| WriteLaunchActionBooleanAttribute(xout, "debugDocumentVersioning", |
| "XCODE_SCHEME_DEBUG_DOCUMENT_VERSIONING", |
| true); |
| xout.Attribute("debugServiceExtension", "internal"); |
| xout.Attribute("allowLocationSimulation", "YES"); |
| if (cmValue gpuFrameCaptureMode = this->Target->GetTarget()->GetProperty( |
| "XCODE_SCHEME_ENABLE_GPU_FRAME_CAPTURE_MODE")) { |
| std::string value = *gpuFrameCaptureMode; |
| if (cmsysString_strcasecmp(value.c_str(), "Metal") == 0) { |
| value = "1"; |
| } else if (cmsysString_strcasecmp(value.c_str(), "Disabled") == 0) { |
| value = "3"; |
| } |
| xout.Attribute("enableGPUFrameCaptureMode", value); |
| } |
| |
| // Diagnostics tab begin |
| |
| bool useAddressSanitizer = WriteLaunchActionAttribute( |
| xout, "enableAddressSanitizer", |
| "XCODE_SCHEME_ADDRESS_SANITIZER"); // not allowed with |
| // enableThreadSanitizer=YES |
| WriteLaunchActionAttribute( |
| xout, "enableASanStackUseAfterReturn", |
| "XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN"); |
| |
| bool useThreadSanitizer = false; |
| if (!useAddressSanitizer) { |
| useThreadSanitizer = WriteLaunchActionAttribute( |
| xout, "enableThreadSanitizer", |
| "XCODE_SCHEME_THREAD_SANITIZER"); // not allowed with |
| // enableAddressSanitizer=YES |
| } |
| |
| WriteLaunchActionAttribute(xout, "stopOnEveryThreadSanitizerIssue", |
| "XCODE_SCHEME_THREAD_SANITIZER_STOP"); |
| |
| WriteLaunchActionAttribute(xout, "enableUBSanitizer", |
| "XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER"); |
| |
| if (cmValue value = this->Target->GetTarget()->GetProperty( |
| "XCODE_SCHEME_ENABLE_GPU_API_VALIDATION")) { |
| if (value.IsOff()) { |
| xout.Attribute("enableGPUValidationMode", |
| "1"); // unset means YES, "1" means NO |
| } |
| } |
| |
| if (cmValue value = this->Target->GetTarget()->GetProperty( |
| "XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION")) { |
| if (value.IsOn()) { |
| xout.Attribute("enableGPUShaderValidationMode", |
| "2"); // unset means NO, "2" means YES |
| } |
| } |
| |
| WriteLaunchActionAttribute( |
| xout, "stopOnEveryUBSanitizerIssue", |
| "XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER_STOP"); |
| |
| WriteLaunchActionAttribute( |
| xout, "disableMainThreadChecker", |
| "XCODE_SCHEME_DISABLE_MAIN_THREAD_CHECKER"); // negative enabled! |
| WriteLaunchActionAttribute(xout, "stopOnEveryMainThreadCheckerIssue", |
| "XCODE_SCHEME_MAIN_THREAD_CHECKER_STOP"); |
| |
| if (this->Target->GetTarget()->GetPropertyAsBool( |
| "XCODE_SCHEME_DEBUG_AS_ROOT")) { |
| xout.Attribute("debugAsWhichUser", "root"); |
| } |
| |
| // Diagnostics tab end |
| |
| if (IsExecutable(this->Target)) { |
| WriteBuildableProductRunnable(xout, this->Target, container); |
| } else { |
| xout.StartElement("MacroExpansion"); |
| WriteBuildableReference(xout, this->Target, container); |
| xout.EndElement(); |
| } |
| |
| // Info tab begin |
| |
| if (cmValue exe = |
| this->Target->GetTarget()->GetProperty("XCODE_SCHEME_EXECUTABLE")) { |
| |
| xout.StartElement("PathRunnable"); |
| xout.BreakAttributes(); |
| |
| xout.Attribute("runnableDebuggingMode", "0"); |
| xout.Attribute("FilePath", *exe); |
| |
| xout.EndElement(); // PathRunnable |
| } |
| |
| // Info tab end |
| |
| // Arguments tab begin |
| |
| if (cmValue argList = |
| this->Target->GetTarget()->GetProperty("XCODE_SCHEME_ARGUMENTS")) { |
| cmList arguments{ *argList }; |
| if (!arguments.empty()) { |
| xout.StartElement("CommandLineArguments"); |
| |
| for (auto const& argument : arguments) { |
| xout.StartElement("CommandLineArgument"); |
| xout.BreakAttributes(); |
| |
| xout.Attribute("argument", argument); |
| xout.Attribute("isEnabled", "YES"); |
| |
| xout.EndElement(); // CommandLineArgument |
| } |
| |
| xout.EndElement(); // CommandLineArguments |
| } |
| } |
| |
| if (cmValue envList = |
| this->Target->GetTarget()->GetProperty("XCODE_SCHEME_ENVIRONMENT")) { |
| cmList envs{ *envList }; |
| if (!envs.empty()) { |
| xout.StartElement("EnvironmentVariables"); |
| |
| for (auto env : envs) { |
| |
| xout.StartElement("EnvironmentVariable"); |
| xout.BreakAttributes(); |
| |
| std::string envValue; |
| const auto p = env.find_first_of('='); |
| if (p != std::string::npos) { |
| envValue = env.substr(p + 1); |
| env.resize(p); |
| } |
| |
| xout.Attribute("key", env); |
| xout.Attribute("value", envValue); |
| xout.Attribute("isEnabled", "YES"); |
| |
| xout.EndElement(); // EnvironmentVariable |
| } |
| |
| xout.EndElement(); // EnvironmentVariables |
| } |
| } |
| |
| // Arguments tab end |
| |
| xout.StartElement("AdditionalOptions"); |
| |
| if (!useThreadSanitizer) { |
| WriteLaunchActionAdditionalOption(xout, "MallocScribble", "", |
| "XCODE_SCHEME_MALLOC_SCRIBBLE"); |
| } |
| |
| if (!useThreadSanitizer && !useAddressSanitizer) { |
| WriteLaunchActionAdditionalOption(xout, "MallocGuardEdges", "", |
| "XCODE_SCHEME_MALLOC_GUARD_EDGES"); |
| } |
| |
| if (!useThreadSanitizer && !useAddressSanitizer) { |
| WriteLaunchActionAdditionalOption(xout, "DYLD_INSERT_LIBRARIES", |
| "/usr/lib/libgmalloc.dylib", |
| "XCODE_SCHEME_GUARD_MALLOC"); |
| } |
| |
| WriteLaunchActionAdditionalOption(xout, "NSZombieEnabled", "YES", |
| "XCODE_SCHEME_ZOMBIE_OBJECTS"); |
| |
| if (!useThreadSanitizer && !useAddressSanitizer) { |
| WriteLaunchActionAdditionalOption(xout, "MallocStackLogging", "", |
| "XCODE_SCHEME_MALLOC_STACK"); |
| } |
| |
| WriteLaunchActionAdditionalOption(xout, "DYLD_PRINT_APIS", "", |
| "XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE"); |
| |
| WriteLaunchActionAdditionalOption(xout, "DYLD_PRINT_LIBRARIES", "", |
| "XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS"); |
| |
| xout.EndElement(); |
| |
| xout.EndElement(); // LaunchAction |
| } |
| |
| bool cmXCodeScheme::WriteLaunchActionAttribute(cmXMLWriter& xout, |
| const std::string& attrName, |
| const std::string& varName) |
| { |
| if (Target->GetTarget()->GetPropertyAsBool(varName)) { |
| xout.Attribute(attrName.c_str(), "YES"); |
| return true; |
| } |
| return false; |
| } |
| |
| bool cmXCodeScheme::WriteLaunchActionBooleanAttribute( |
| cmXMLWriter& xout, const std::string& attrName, const std::string& varName, |
| bool defaultValue) |
| { |
| cmValue property = Target->GetTarget()->GetProperty(varName); |
| bool isOn = (!property && defaultValue) || property.IsOn(); |
| |
| if (isOn) { |
| xout.Attribute(attrName.c_str(), "YES"); |
| } else { |
| xout.Attribute(attrName.c_str(), "NO"); |
| } |
| return isOn; |
| } |
| |
| bool cmXCodeScheme::WriteLaunchActionAdditionalOption( |
| cmXMLWriter& xout, const std::string& key, const std::string& value, |
| const std::string& varName) |
| { |
| if (Target->GetTarget()->GetPropertyAsBool(varName)) { |
| xout.StartElement("AdditionalOption"); |
| xout.BreakAttributes(); |
| |
| xout.Attribute("key", key); |
| xout.Attribute("value", value); |
| xout.Attribute("isEnabled", "YES"); |
| |
| xout.EndElement(); // AdditionalOption |
| |
| return true; |
| } |
| return false; |
| } |
| |
| void cmXCodeScheme::WriteProfileAction(cmXMLWriter& xout, |
| const std::string& configuration, |
| const std::string& container) |
| { |
| xout.StartElement("ProfileAction"); |
| xout.BreakAttributes(); |
| xout.Attribute("buildConfiguration", configuration); |
| xout.Attribute("shouldUseLaunchSchemeArgsEnv", "YES"); |
| xout.Attribute("savedToolIdentifier", ""); |
| WriteCustomWorkingDirectory(xout, configuration); |
| WriteLaunchActionBooleanAttribute(xout, "debugDocumentVersioning", |
| "XCODE_SCHEME_DEBUG_DOCUMENT_VERSIONING", |
| true); |
| |
| if (IsExecutable(this->Target)) { |
| WriteBuildableProductRunnable(xout, this->Target, container); |
| } |
| |
| xout.EndElement(); |
| } |
| |
| void cmXCodeScheme::WriteAnalyzeAction(cmXMLWriter& xout, |
| const std::string& configuration) |
| { |
| xout.StartElement("AnalyzeAction"); |
| xout.BreakAttributes(); |
| xout.Attribute("buildConfiguration", configuration); |
| xout.EndElement(); |
| } |
| |
| void cmXCodeScheme::WriteArchiveAction(cmXMLWriter& xout, |
| const std::string& configuration) |
| { |
| xout.StartElement("ArchiveAction"); |
| xout.BreakAttributes(); |
| xout.Attribute("buildConfiguration", configuration); |
| xout.Attribute("revealArchiveInOrganizer", "YES"); |
| xout.EndElement(); |
| } |
| |
| void cmXCodeScheme::WriteBuildableProductRunnable(cmXMLWriter& xout, |
| const cmXCodeObject* xcObj, |
| const std::string& container) |
| { |
| xout.StartElement("BuildableProductRunnable"); |
| xout.BreakAttributes(); |
| xout.Attribute("runnableDebuggingMode", "0"); |
| WriteBuildableReference(xout, xcObj, container); |
| xout.EndElement(); |
| } |
| |
| void cmXCodeScheme::WriteBuildableReference(cmXMLWriter& xout, |
| const cmXCodeObject* xcObj, |
| const std::string& container) |
| { |
| xout.StartElement("BuildableReference"); |
| xout.BreakAttributes(); |
| xout.Attribute("BuildableIdentifier", "primary"); |
| xout.Attribute("BlueprintIdentifier", xcObj->GetId()); |
| std::string const noConfig; // FIXME: What config to use here? |
| xout.Attribute("BuildableName", xcObj->GetTarget()->GetFullName(noConfig)); |
| xout.Attribute("BlueprintName", xcObj->GetTarget()->GetName()); |
| xout.Attribute("ReferencedContainer", cmStrCat("container:", container)); |
| xout.EndElement(); |
| } |
| |
| void cmXCodeScheme::WriteCustomWorkingDirectory( |
| cmXMLWriter& xout, const std::string& configuration) |
| { |
| std::string const& propertyValue = |
| this->Target->GetTarget()->GetSafeProperty( |
| "XCODE_SCHEME_WORKING_DIRECTORY"); |
| if (propertyValue.empty()) { |
| xout.Attribute("useCustomWorkingDirectory", "NO"); |
| } else { |
| xout.Attribute("useCustomWorkingDirectory", "YES"); |
| |
| auto customWorkingDirectory = cmGeneratorExpression::Evaluate( |
| propertyValue, this->LocalGenerator, configuration); |
| xout.Attribute("customWorkingDirectory", customWorkingDirectory); |
| } |
| } |
| |
| std::string cmXCodeScheme::WriteVersionString() |
| { |
| std::ostringstream v; |
| v << std::setfill('0') << std::setw(4) << this->XcodeVersion * 10; |
| return v.str(); |
| } |
| |
| std::string cmXCodeScheme::FindConfiguration(const std::string& name) |
| { |
| // Try to find the desired configuration by name, |
| // and if it's not found return first from the list |
| // |
| if (!cm::contains(this->ConfigList, name) && !this->ConfigList.empty()) { |
| return this->ConfigList[0]; |
| } |
| |
| return name; |
| } |
| |
| bool cmXCodeScheme::IsTestable() const |
| { |
| return !this->Tests.empty() || IsExecutable(this->Target); |
| } |
| |
| bool cmXCodeScheme::IsExecutable(const cmXCodeObject* target) |
| { |
| cmGeneratorTarget* gt = target->GetTarget(); |
| if (!gt) { |
| cmSystemTools::Error("Error no target on xobject\n"); |
| return false; |
| } |
| |
| return gt->GetType() == cmStateEnums::EXECUTABLE; |
| } |