| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst 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 "cmGlobalGenerator.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, | |
| std::vector<std::string> const& configList, | |
| unsigned int xcVersion) | |
| : LocalGenerator(lg) | |
| , Target(xcObj) | |
| , Tests(std::move(tests)) | |
| , TargetName(xcObj->GetTarget()->GetName()) | |
| , ConfigList(configList) | |
| , XcodeVersion(xcVersion) | |
| { | |
| } | |
| void cmXCodeScheme::WriteXCodeSharedScheme(std::string const& xcProjDir, | |
| std::string const& 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, | |
| std::string const& 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"; | |
| cmValue propTstCfg = | |
| Target->GetTarget()->GetProperty("XCODE_SCHEME_TEST_CONFIGURATION"); | |
| std::string testConfiguration = | |
| !propTstCfg.IsEmpty() ? *propTstCfg : "Debug"; | |
| WriteBuildAction(xout, container); | |
| WriteTestAction(xout, FindConfiguration(testConfiguration), 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, | |
| std::string const& 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, | |
| std::string const& configuration, | |
| std::string const& 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"); | |
| WriteCustomLLDBInitFile(xout, configuration); | |
| 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, | |
| std::string const& configuration, | |
| std::string const& 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); | |
| WriteCustomLLDBInitFile(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; | |
| auto const 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, | |
| std::string const& attrName, | |
| std::string const& varName) | |
| { | |
| if (Target->GetTarget()->GetPropertyAsBool(varName)) { | |
| xout.Attribute(attrName.c_str(), "YES"); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool cmXCodeScheme::WriteLaunchActionBooleanAttribute( | |
| cmXMLWriter& xout, std::string const& attrName, std::string const& 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, std::string const& key, std::string const& value, | |
| std::string const& 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, | |
| std::string const& configuration, | |
| std::string const& 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, | |
| std::string const& configuration) | |
| { | |
| xout.StartElement("AnalyzeAction"); | |
| xout.BreakAttributes(); | |
| xout.Attribute("buildConfiguration", configuration); | |
| xout.EndElement(); | |
| } | |
| void cmXCodeScheme::WriteArchiveAction(cmXMLWriter& xout, | |
| std::string const& configuration) | |
| { | |
| xout.StartElement("ArchiveAction"); | |
| xout.BreakAttributes(); | |
| xout.Attribute("buildConfiguration", configuration); | |
| xout.Attribute("revealArchiveInOrganizer", "YES"); | |
| xout.EndElement(); | |
| } | |
| void cmXCodeScheme::WriteBuildableProductRunnable(cmXMLWriter& xout, | |
| cmXCodeObject const* xcObj, | |
| std::string const& container) | |
| { | |
| xout.StartElement("BuildableProductRunnable"); | |
| xout.BreakAttributes(); | |
| xout.Attribute("runnableDebuggingMode", "0"); | |
| WriteBuildableReference(xout, xcObj, container); | |
| xout.EndElement(); | |
| } | |
| void cmXCodeScheme::WriteBuildableReference(cmXMLWriter& xout, | |
| cmXCodeObject const* xcObj, | |
| std::string const& 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, std::string const& configuration) | |
| { | |
| cmGlobalGenerator* gg = this->LocalGenerator->GetGlobalGenerator(); | |
| cmValue propertyValue = | |
| gg->GetDebuggerWorkingDirectory(this->Target->GetTarget()); | |
| if (!propertyValue) { | |
| xout.Attribute("useCustomWorkingDirectory", "NO"); | |
| } else { | |
| xout.Attribute("useCustomWorkingDirectory", "YES"); | |
| auto customWorkingDirectory = cmGeneratorExpression::Evaluate( | |
| *propertyValue, this->LocalGenerator, configuration); | |
| xout.Attribute("customWorkingDirectory", customWorkingDirectory); | |
| } | |
| } | |
| void cmXCodeScheme::WriteCustomLLDBInitFile(cmXMLWriter& xout, | |
| std::string const& configuration) | |
| { | |
| std::string const& propertyValue = | |
| this->Target->GetTarget()->GetSafeProperty("XCODE_SCHEME_LLDB_INIT_FILE"); | |
| if (!propertyValue.empty()) { | |
| auto customLLDBInitFile = cmGeneratorExpression::Evaluate( | |
| propertyValue, this->LocalGenerator, configuration); | |
| xout.Attribute("customLLDBInitFile", customLLDBInitFile); | |
| } | |
| } | |
| std::string cmXCodeScheme::WriteVersionString() | |
| { | |
| std::ostringstream v; | |
| v << std::setfill('0') << std::setw(4) << this->XcodeVersion * 10; | |
| return v.str(); | |
| } | |
| std::string cmXCodeScheme::FindConfiguration(std::string const& 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(cmXCodeObject const* target) | |
| { | |
| cmGeneratorTarget* gt = target->GetTarget(); | |
| if (!gt) { | |
| cmSystemTools::Error("Error no target on xobject\n"); | |
| return false; | |
| } | |
| return gt->GetType() == cm::TargetType::EXECUTABLE; | |
| } |