/* 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 #include #include #include #include #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& 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; }