| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file Copyright.txt or https://cmake.org/licensing for details. */ | |
| #include "cmGlobalXCodeGenerator.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cstdio> | |
| #include <cstring> | |
| #include <functional> | |
| #include <iomanip> | |
| #include <sstream> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <cm/memory> | |
| #include <cm/optional> | |
| #include <cmext/algorithm> | |
| #include <cmext/string_view> | |
| #include "cmsys/RegularExpression.hxx" | |
| #include "cmComputeLinkInformation.h" | |
| #include "cmCryptoHash.h" | |
| #include "cmCustomCommand.h" | |
| #include "cmCustomCommandGenerator.h" | |
| #include "cmCustomCommandLines.h" | |
| #include "cmCustomCommandTypes.h" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmGeneratorOptions.h" | |
| #include "cmGeneratorTarget.h" | |
| #include "cmGlobalGeneratorFactory.h" | |
| #include "cmLinkItem.h" | |
| #include "cmList.h" | |
| #include "cmListFileCache.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmLocalXCodeGenerator.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmOutputConverter.h" | |
| #include "cmPolicies.h" | |
| #include "cmSourceFile.h" | |
| #include "cmSourceFileLocation.h" | |
| #include "cmSourceFileLocationKind.h" | |
| #include "cmSourceGroup.h" | |
| #include "cmState.h" | |
| #include "cmStateSnapshot.h" | |
| #include "cmStateTypes.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTarget.h" | |
| #include "cmTargetDepend.h" | |
| #include "cmXCode21Object.h" | |
| #include "cmXCodeObject.h" | |
| #include "cmXCodeScheme.h" | |
| #include "cmXMLWriter.h" | |
| #include "cmXcFramework.h" | |
| #include "cmake.h" | |
| #if !defined(CMAKE_BOOTSTRAP) && defined(__APPLE__) | |
| # include <CoreFoundation/CoreFoundation.h> | |
| # if !TARGET_OS_IPHONE | |
| # define HAVE_APPLICATION_SERVICES | |
| # include <ApplicationServices/ApplicationServices.h> | |
| # endif | |
| #endif | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| # include "cmXMLParser.h" | |
| // parse the xml file storing the installed version of Xcode on | |
| // the machine | |
| class cmXcodeVersionParser : public cmXMLParser | |
| { | |
| public: | |
| cmXcodeVersionParser() | |
| : Version("1.5") | |
| { | |
| } | |
| void StartElement(std::string const&, char const**) override | |
| { | |
| this->Data = ""; | |
| } | |
| void EndElement(std::string const& name) override | |
| { | |
| if (name == "key"_s) { | |
| this->Key = this->Data; | |
| } else if (name == "string"_s) { | |
| if (this->Key == "CFBundleShortVersionString"_s) { | |
| this->Version = this->Data; | |
| } | |
| } | |
| } | |
| void CharacterDataHandler(char const* data, int length) override | |
| { | |
| this->Data.append(data, length); | |
| } | |
| std::string Version; | |
| std::string Key; | |
| std::string Data; | |
| }; | |
| #endif | |
| // Builds either an object list or a space-separated string from the | |
| // given inputs. | |
| class cmGlobalXCodeGenerator::BuildObjectListOrString | |
| { | |
| cmGlobalXCodeGenerator* Generator; | |
| cmXCodeObject* Group = nullptr; | |
| bool Empty = true; | |
| std::string String; | |
| public: | |
| BuildObjectListOrString(cmGlobalXCodeGenerator* gen, bool buildObjectList) | |
| : Generator(gen) | |
| { | |
| if (buildObjectList) { | |
| this->Group = this->Generator->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| } | |
| } | |
| bool IsEmpty() const { return this->Empty; } | |
| void Add(std::string const& newString) | |
| { | |
| this->Empty = false; | |
| if (this->Group) { | |
| this->Group->AddObject(this->Generator->CreateString(newString)); | |
| } else { | |
| this->String += newString; | |
| this->String += ' '; | |
| } | |
| } | |
| std::string const& GetString() const { return this->String; } | |
| cmXCodeObject* CreateList() | |
| { | |
| if (this->Group) { | |
| return this->Group; | |
| } | |
| return this->Generator->CreateString(this->String); | |
| } | |
| }; | |
| class cmGlobalXCodeGenerator::Factory : public cmGlobalGeneratorFactory | |
| { | |
| public: | |
| std::unique_ptr<cmGlobalGenerator> CreateGlobalGenerator( | |
| std::string const& name, cmake* cm) const override; | |
| cmDocumentationEntry GetDocumentation() const override | |
| { | |
| return cmGlobalXCodeGenerator::GetDocumentation(); | |
| } | |
| std::vector<std::string> GetGeneratorNames() const override | |
| { | |
| std::vector<std::string> names; | |
| names.push_back(cmGlobalXCodeGenerator::GetActualName()); | |
| return names; | |
| } | |
| bool SupportsToolset() const override { return true; } | |
| bool SupportsPlatform() const override { return false; } | |
| std::vector<std::string> GetKnownPlatforms() const override | |
| { | |
| return std::vector<std::string>(); | |
| } | |
| std::string GetDefaultPlatformName() const override { return std::string(); } | |
| }; | |
| cmGlobalXCodeGenerator::cmGlobalXCodeGenerator( | |
| cmake* cm, std::string const& version_string, unsigned int version_number) | |
| : cmGlobalGenerator(cm) | |
| { | |
| this->VersionString = version_string; | |
| this->XcodeVersion = version_number; | |
| if (this->XcodeVersion >= 120) { | |
| this->XcodeBuildSystem = BuildSystem::Twelve; | |
| } else { | |
| this->XcodeBuildSystem = BuildSystem::One; | |
| } | |
| this->RootObject = nullptr; | |
| this->MainGroupChildren = nullptr; | |
| this->FrameworkGroup = nullptr; | |
| this->CurrentMakefile = nullptr; | |
| this->CurrentLocalGenerator = nullptr; | |
| this->XcodeBuildCommandInitialized = false; | |
| this->ObjectDirArchDefault = "$(CURRENT_ARCH)"; | |
| this->ObjectDirArch = this->ObjectDirArchDefault; | |
| cm->GetState()->SetIsGeneratorMultiConfig(true); | |
| } | |
| std::unique_ptr<cmGlobalGeneratorFactory> cmGlobalXCodeGenerator::NewFactory() | |
| { | |
| return std::unique_ptr<cmGlobalGeneratorFactory>(new Factory); | |
| } | |
| std::unique_ptr<cmGlobalGenerator> | |
| cmGlobalXCodeGenerator::Factory::CreateGlobalGenerator(std::string const& name, | |
| cmake* cm) const | |
| { | |
| if (name != GetActualName()) { | |
| return std::unique_ptr<cmGlobalGenerator>(); | |
| } | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| cmXcodeVersionParser parser; | |
| std::string versionFile; | |
| { | |
| std::string out; | |
| bool commandResult = cmSystemTools::RunSingleCommand( | |
| "xcode-select --print-path", &out, nullptr, nullptr, nullptr, | |
| cmSystemTools::OUTPUT_NONE); | |
| if (commandResult) { | |
| std::string::size_type pos = out.find(".app/"); | |
| if (pos != std::string::npos) { | |
| versionFile = | |
| cmStrCat(out.substr(0, pos + 5), "Contents/version.plist"); | |
| } | |
| } | |
| } | |
| if (!versionFile.empty() && cmSystemTools::FileExists(versionFile)) { | |
| parser.ParseFile(versionFile.c_str()); | |
| } else if (cmSystemTools::FileExists( | |
| "/Applications/Xcode.app/Contents/version.plist")) { | |
| parser.ParseFile("/Applications/Xcode.app/Contents/version.plist"); | |
| } else { | |
| parser.ParseFile( | |
| "/Developer/Applications/Xcode.app/Contents/version.plist"); | |
| } | |
| std::string const& version_string = parser.Version; | |
| // Compute an integer form of the version number. | |
| unsigned int v[2] = { 0, 0 }; | |
| sscanf(version_string.c_str(), "%u.%u", &v[0], &v[1]); | |
| unsigned int version_number = 10 * v[0] + v[1]; | |
| if (version_number < 50) { | |
| cm->IssueMessage(MessageType::FATAL_ERROR, | |
| cmStrCat("Xcode ", version_string, " not supported.")); | |
| return std::unique_ptr<cmGlobalGenerator>(); | |
| } | |
| return std::unique_ptr<cmGlobalGenerator>( | |
| cm::make_unique<cmGlobalXCodeGenerator>(cm, version_string, | |
| version_number)); | |
| #else | |
| std::cerr << "CMake should be built with cmake to use Xcode, " | |
| "default to Xcode 1.5\n"; | |
| return std::unique_ptr<cmGlobalGenerator>( | |
| cm::make_unique<cmGlobalXCodeGenerator>(cm)); | |
| #endif | |
| } | |
| namespace { | |
| std::string ConvertToMakefilePath(std::string const& path) | |
| { | |
| std::string result; | |
| result.reserve(path.size()); | |
| for (char c : path) { | |
| switch (c) { | |
| case '\\': | |
| case ' ': | |
| case '#': | |
| result.push_back('\\'); | |
| CM_FALLTHROUGH; | |
| default: | |
| result.push_back(c); | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| } | |
| bool cmGlobalXCodeGenerator::FindMakeProgram(cmMakefile* mf) | |
| { | |
| // The Xcode generator knows how to lookup its build tool | |
| // directly instead of needing a helper module to do it, so we | |
| // do not actually need to put CMAKE_MAKE_PROGRAM into the cache. | |
| if (mf->GetDefinition("CMAKE_MAKE_PROGRAM").IsOff()) { | |
| mf->AddDefinition("CMAKE_MAKE_PROGRAM", this->GetXcodeBuildCommand()); | |
| } | |
| return true; | |
| } | |
| std::string const& cmGlobalXCodeGenerator::GetXcodeBuildCommand() | |
| { | |
| if (!this->XcodeBuildCommandInitialized) { | |
| this->XcodeBuildCommandInitialized = true; | |
| this->XcodeBuildCommand = this->FindXcodeBuildCommand(); | |
| } | |
| return this->XcodeBuildCommand; | |
| } | |
| std::string cmGlobalXCodeGenerator::FindXcodeBuildCommand() | |
| { | |
| std::string makeProgram = cmSystemTools::FindProgram("xcodebuild"); | |
| if (makeProgram.empty()) { | |
| makeProgram = "xcodebuild"; | |
| } | |
| return makeProgram; | |
| } | |
| bool cmGlobalXCodeGenerator::SetSystemName(std::string const& s, | |
| cmMakefile* mf) | |
| { | |
| this->SystemName = s; | |
| return this->cmGlobalGenerator::SetSystemName(s, mf); | |
| } | |
| namespace { | |
| cm::string_view cmXcodeBuildSystemString(cmGlobalXCodeGenerator::BuildSystem b) | |
| { | |
| switch (b) { | |
| case cmGlobalXCodeGenerator::BuildSystem::One: | |
| return "1"_s; | |
| case cmGlobalXCodeGenerator::BuildSystem::Twelve: | |
| return "12"_s; | |
| } | |
| return {}; | |
| } | |
| } | |
| bool cmGlobalXCodeGenerator::SetGeneratorToolset(std::string const& ts, | |
| bool build, cmMakefile* mf) | |
| { | |
| if (!this->ParseGeneratorToolset(ts, mf)) { | |
| return false; | |
| } | |
| if (build) { | |
| return true; | |
| } | |
| if (!this->GeneratorToolset.empty()) { | |
| mf->AddDefinition("CMAKE_XCODE_PLATFORM_TOOLSET", this->GeneratorToolset); | |
| } | |
| mf->AddDefinition("CMAKE_XCODE_BUILD_SYSTEM", | |
| cmXcodeBuildSystemString(this->XcodeBuildSystem)); | |
| return true; | |
| } | |
| bool cmGlobalXCodeGenerator::ParseGeneratorToolset(std::string const& ts, | |
| cmMakefile* mf) | |
| { | |
| std::vector<std::string> const fields = | |
| cmTokenize(ts, ',', cmTokenizerMode::New); | |
| if (fields.empty()) { | |
| return true; | |
| } | |
| auto fi = fields.cbegin(); | |
| // The first field may be the Xcode GCC_VERSION. | |
| if (fi->find('=') == fi->npos) { | |
| this->GeneratorToolset = *fi; | |
| ++fi; | |
| } | |
| std::unordered_set<std::string> handled; | |
| // The rest of the fields must be key=value pairs. | |
| for (; fi != fields.cend(); ++fi) { | |
| std::string::size_type pos = fi->find('='); | |
| if (pos == fi->npos) { | |
| /* clang-format off */ | |
| std::string const& e = cmStrCat( | |
| "Generator\n" | |
| " ", this->GetName(), "\n" | |
| "given toolset specification\n" | |
| " ", ts, "\n" | |
| "that contains a field after the first ',' with no '='." | |
| ); | |
| /* clang-format on */ | |
| mf->IssueMessage(MessageType::FATAL_ERROR, e); | |
| return false; | |
| } | |
| std::string const key = fi->substr(0, pos); | |
| std::string const value = fi->substr(pos + 1); | |
| if (!handled.insert(key).second) { | |
| /* clang-format off */ | |
| std::string const& e = cmStrCat( | |
| "Generator\n" | |
| " ", this->GetName(), "\n" | |
| "given toolset specification\n" | |
| " ", ts, "\n" | |
| "that contains duplicate field key '", key, "'." | |
| ); | |
| /* clang-format on */ | |
| mf->IssueMessage(MessageType::FATAL_ERROR, e); | |
| return false; | |
| } | |
| if (!this->ProcessGeneratorToolsetField(key, value, mf)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool cmGlobalXCodeGenerator::ProcessGeneratorToolsetField( | |
| std::string const& key, std::string const& value, cmMakefile* mf) | |
| { | |
| if (key == "buildsystem"_s) { | |
| if (value == "1"_s) { | |
| this->XcodeBuildSystem = BuildSystem::One; | |
| } else if (value == "12"_s) { | |
| this->XcodeBuildSystem = BuildSystem::Twelve; | |
| } else { | |
| /* clang-format off */ | |
| std::string const& e = cmStrCat( | |
| "Generator\n" | |
| " ", this->GetName(), "\n" | |
| "toolset specification field\n" | |
| " buildsystem=", value, "\n" | |
| "value is unknown. It must be '1' or '12'." | |
| ); | |
| /* clang-format on */ | |
| mf->IssueMessage(MessageType::FATAL_ERROR, e); | |
| return false; | |
| } | |
| if ((this->XcodeBuildSystem == BuildSystem::Twelve && | |
| this->XcodeVersion < 120) || | |
| (this->XcodeBuildSystem == BuildSystem::One && | |
| this->XcodeVersion >= 140)) { | |
| /* clang-format off */ | |
| std::string const& e = cmStrCat( | |
| "Generator\n" | |
| " ", this->GetName(), "\n" | |
| "toolset specification field\n" | |
| " buildsystem=", value, "\n" | |
| "is not allowed with Xcode ", this->VersionString, '.' | |
| ); | |
| /* clang-format on */ | |
| mf->IssueMessage(MessageType::FATAL_ERROR, e); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* clang-format off */ | |
| std::string const& e = cmStrCat( | |
| "Generator\n" | |
| " ", this->GetName(), "\n" | |
| "given toolset specification that contains invalid field '", key, "'." | |
| ); | |
| /* clang-format on */ | |
| mf->IssueMessage(MessageType::FATAL_ERROR, e); | |
| return false; | |
| } | |
| void cmGlobalXCodeGenerator::EnableLanguage( | |
| std::vector<std::string> const& lang, cmMakefile* mf, bool optional) | |
| { | |
| mf->AddDefinition("XCODE", "1"); | |
| mf->AddDefinition("XCODE_VERSION", this->VersionString); | |
| mf->InitCMAKE_CONFIGURATION_TYPES("Debug;Release;MinSizeRel;RelWithDebInfo"); | |
| mf->AddDefinition("CMAKE_GENERATOR_NO_COMPILER_ENV", "1"); | |
| this->cmGlobalGenerator::EnableLanguage(lang, mf, optional); | |
| this->ComputeArchitectures(mf); | |
| } | |
| bool cmGlobalXCodeGenerator::Open(std::string const& bindir, | |
| std::string const& projectName, bool dryRun) | |
| { | |
| bool ret = false; | |
| #ifdef HAVE_APPLICATION_SERVICES | |
| std::string url = cmStrCat(bindir, '/', projectName, ".xcodeproj"); | |
| if (dryRun) { | |
| return cmSystemTools::FileExists(url, false); | |
| } | |
| CFStringRef cfStr = CFStringCreateWithCString( | |
| kCFAllocatorDefault, url.c_str(), kCFStringEncodingUTF8); | |
| if (cfStr) { | |
| CFURLRef cfUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfStr, | |
| kCFURLPOSIXPathStyle, true); | |
| if (cfUrl) { | |
| OSStatus err = LSOpenCFURLRef(cfUrl, nullptr); | |
| ret = err == noErr; | |
| CFRelease(cfUrl); | |
| } | |
| CFRelease(cfStr); | |
| } | |
| #else | |
| (void)bindir; | |
| (void)projectName; | |
| (void)dryRun; | |
| #endif | |
| return ret; | |
| } | |
| std::vector<cmGlobalGenerator::GeneratedMakeCommand> | |
| cmGlobalXCodeGenerator::GenerateBuildCommand( | |
| std::string const& makeProgram, std::string const& projectName, | |
| std::string const& /*projectDir*/, | |
| std::vector<std::string> const& targetNames, std::string const& config, | |
| int jobs, bool /*verbose*/, cmBuildOptions const& /*buildOptions*/, | |
| std::vector<std::string> const& makeOptions) | |
| { | |
| GeneratedMakeCommand makeCommand; | |
| // now build the test | |
| makeCommand.Add( | |
| this->SelectMakeProgram(makeProgram, this->GetXcodeBuildCommand())); | |
| if (!projectName.empty()) { | |
| makeCommand.Add("-project"); | |
| std::string projectArg = cmStrCat(projectName, ".xcodeproj"); | |
| makeCommand.Add(projectArg); | |
| } | |
| if (cm::contains(targetNames, "clean")) { | |
| makeCommand.Add("clean"); | |
| makeCommand.Add("-target", "ALL_BUILD"); | |
| } else { | |
| makeCommand.Add("build"); | |
| if (targetNames.empty() || | |
| ((targetNames.size() == 1) && targetNames.front().empty())) { | |
| makeCommand.Add("-target", "ALL_BUILD"); | |
| } else { | |
| for (auto const& tname : targetNames) { | |
| if (!tname.empty()) { | |
| makeCommand.Add("-target", tname); | |
| } | |
| } | |
| } | |
| } | |
| if ((this->XcodeBuildSystem >= BuildSystem::Twelve) || | |
| (jobs != cmake::NO_BUILD_PARALLEL_LEVEL)) { | |
| makeCommand.Add("-parallelizeTargets"); | |
| } | |
| makeCommand.Add("-configuration", (config.empty() ? "Debug" : config)); | |
| if ((jobs != cmake::NO_BUILD_PARALLEL_LEVEL) && | |
| (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL)) { | |
| makeCommand.Add("-jobs", std::to_string(jobs)); | |
| } | |
| if (this->XcodeVersion >= 70) { | |
| makeCommand.Add("-hideShellScriptEnvironment"); | |
| } | |
| makeCommand.Add(makeOptions.begin(), makeOptions.end()); | |
| return { std::move(makeCommand) }; | |
| } | |
| //! Create a local generator appropriate to this Global Generator | |
| std::unique_ptr<cmLocalGenerator> cmGlobalXCodeGenerator::CreateLocalGenerator( | |
| cmMakefile* mf) | |
| { | |
| std::unique_ptr<cmLocalGenerator> lg( | |
| cm::make_unique<cmLocalXCodeGenerator>(this, mf)); | |
| if (this->XcodeBuildSystem >= BuildSystem::Twelve) { | |
| // For this build system variant we generate custom commands as | |
| // shell scripts directly rather than inside Makefiles. | |
| // FIXME: Rename or refactor this option for clarity. | |
| lg->SetLinkScriptShell(true); | |
| } | |
| return lg; | |
| } | |
| void cmGlobalXCodeGenerator::AddExtraIDETargets() | |
| { | |
| // make sure extra targets are added before calling | |
| // the parent generate which will call trace depends | |
| for (auto keyVal : this->ProjectMap) { | |
| cmLocalGenerator* root = keyVal.second[0]; | |
| this->SetGenerationRoot(root); | |
| // add ALL_BUILD, INSTALL, etc | |
| this->AddExtraTargets(root, keyVal.second); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::Generate() | |
| { | |
| this->cmGlobalGenerator::Generate(); | |
| if (cmSystemTools::GetErrorOccurredFlag()) { | |
| return; | |
| } | |
| for (auto keyVal : this->ProjectMap) { | |
| cmLocalGenerator* root = keyVal.second[0]; | |
| bool generateTopLevelProjectOnly = | |
| root->GetMakefile()->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY"); | |
| if (generateTopLevelProjectOnly) { | |
| cmStateSnapshot snp = root->GetStateSnapshot(); | |
| if (snp.GetBuildsystemDirectoryParent().IsValid()) { | |
| continue; | |
| } | |
| } | |
| // cache the enabled languages for source file type queries | |
| this->GetEnabledLanguages(this->EnabledLangs); | |
| this->SetGenerationRoot(root); | |
| // now create the project | |
| this->OutputXCodeProject(root, keyVal.second); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::SetGenerationRoot(cmLocalGenerator* root) | |
| { | |
| this->CurrentProject = root->GetProjectName(); | |
| this->SetCurrentLocalGenerator(root); | |
| this->CurrentRootGenerator = root; | |
| this->CurrentXCodeHackMakefile = | |
| cmStrCat(root->GetCurrentBinaryDirectory(), "/CMakeScripts"); | |
| cmSystemTools::MakeDirectory(this->CurrentXCodeHackMakefile); | |
| this->CurrentXCodeHackMakefile += "/XCODE_DEPEND_HELPER.make"; | |
| } | |
| std::string cmGlobalXCodeGenerator::PostBuildMakeTarget( | |
| std::string const& tName, std::string const& configName) | |
| { | |
| std::string target = tName; | |
| std::replace(target.begin(), target.end(), ' ', '_'); | |
| std::string out = cmStrCat("PostBuild.", target, '.', configName); | |
| return out; | |
| } | |
| #define CMAKE_CHECK_BUILD_SYSTEM_TARGET "ZERO_CHECK" | |
| void cmGlobalXCodeGenerator::AddExtraTargets( | |
| cmLocalGenerator* root, std::vector<cmLocalGenerator*>& gens) | |
| { | |
| // Add ALL_BUILD | |
| auto cc = cm::make_unique<cmCustomCommand>(); | |
| cc->SetCommandLines( | |
| cmMakeSingleCommandLine({ "echo", "Build all projects" })); | |
| cmTarget* allbuild = | |
| root->AddUtilityCommand("ALL_BUILD", true, std::move(cc)); | |
| // Add xcconfig files to ALL_BUILD sources | |
| for (auto& config : this->CurrentConfigurationTypes) { | |
| auto xcconfig = cmGeneratorExpression::Evaluate( | |
| this->CurrentMakefile->GetSafeDefinition("CMAKE_XCODE_XCCONFIG"), | |
| this->CurrentLocalGenerator, config); | |
| if (!xcconfig.empty()) { | |
| allbuild->AddSource(xcconfig); | |
| } | |
| } | |
| root->AddGeneratorTarget(cm::make_unique<cmGeneratorTarget>(allbuild, root)); | |
| // Add XCODE depend helper | |
| std::string legacyDependHelperDir = root->GetCurrentBinaryDirectory(); | |
| cmCustomCommandLines legacyDependHelperCommandLines; | |
| if (this->XcodeBuildSystem == BuildSystem::One) { | |
| legacyDependHelperCommandLines = cmMakeSingleCommandLine( | |
| { "make", "-C", legacyDependHelperDir, "-f", | |
| this->CurrentXCodeHackMakefile, "OBJDIR=$(OBJDIR)", | |
| /* placeholder, see below */ "" }); | |
| } | |
| // Add ZERO_CHECK | |
| bool regenerate = !this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION"); | |
| bool generateTopLevelProjectOnly = | |
| root->GetMakefile()->IsOn("CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY"); | |
| bool isTopLevel = | |
| !root->GetStateSnapshot().GetBuildsystemDirectoryParent().IsValid(); | |
| bool isGenerateProject = isTopLevel || !generateTopLevelProjectOnly; | |
| if (regenerate && isGenerateProject) { | |
| this->CreateReRunCMakeFile(root, gens); | |
| std::string file = | |
| cmSystemTools::ConvertToOutputPath(this->CurrentReRunCMakeMakefile); | |
| cmSystemTools::ReplaceString(file, "\\ ", " "); | |
| cc = cm::make_unique<cmCustomCommand>(); | |
| cc->SetCommandLines(cmMakeSingleCommandLine({ "make", "-f", file })); | |
| cmTarget* check = root->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET, | |
| true, std::move(cc)); | |
| root->AddGeneratorTarget(cm::make_unique<cmGeneratorTarget>(check, root)); | |
| } | |
| // now make the allbuild depend on all the non-utility targets | |
| // in the project | |
| for (auto& gen : gens) { | |
| for (auto const& target : gen->GetGeneratorTargets()) { | |
| if (target->GetType() == cmStateEnums::GLOBAL_TARGET) { | |
| continue; | |
| } | |
| if (regenerate && | |
| (target->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) { | |
| target->Target->AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET, false); | |
| } | |
| // make all exe, shared libs and modules | |
| // run the depend check makefile as a post build rule | |
| // this will make sure that when the next target is built | |
| // things are up-to-date | |
| if (this->XcodeBuildSystem == BuildSystem::One && isGenerateProject && | |
| target->GetType() == cmStateEnums::OBJECT_LIBRARY) { | |
| legacyDependHelperCommandLines.front().back() = // fill placeholder | |
| this->PostBuildMakeTarget(target->GetName(), "$(CONFIGURATION)"); | |
| cc = cm::make_unique<cmCustomCommand>(); | |
| cc->SetCommandLines(legacyDependHelperCommandLines); | |
| cc->SetComment("Depend check for xcode"); | |
| cc->SetWorkingDirectory(legacyDependHelperDir.c_str()); | |
| gen->AddCustomCommandToTarget( | |
| target->GetName(), cmCustomCommandType::POST_BUILD, std::move(cc), | |
| cmObjectLibraryCommands::Accept); | |
| } | |
| if (!this->IsExcluded(gens[0], target.get())) { | |
| allbuild->AddUtility(target->GetName(), false); | |
| } | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::CreateReRunCMakeFile( | |
| cmLocalGenerator* root, std::vector<cmLocalGenerator*> const& gens) | |
| { | |
| std::vector<std::string> lfiles; | |
| for (auto* gen : gens) { | |
| cm::append(lfiles, gen->GetMakefile()->GetListFiles()); | |
| } | |
| // sort the array | |
| std::sort(lfiles.begin(), lfiles.end()); | |
| lfiles.erase(std::unique(lfiles.begin(), lfiles.end()), lfiles.end()); | |
| cmake* cm = this->GetCMakeInstance(); | |
| if (cm->DoWriteGlobVerifyTarget()) { | |
| lfiles.emplace_back(cm->GetGlobVerifyStamp()); | |
| } | |
| this->CurrentReRunCMakeMakefile = | |
| cmStrCat(root->GetCurrentBinaryDirectory(), "/CMakeScripts"); | |
| cmSystemTools::MakeDirectory(this->CurrentReRunCMakeMakefile); | |
| this->CurrentReRunCMakeMakefile += "/ReRunCMake.make"; | |
| cmGeneratedFileStream makefileStream(this->CurrentReRunCMakeMakefile); | |
| makefileStream.SetCopyIfDifferent(true); | |
| makefileStream << "# Generated by CMake, DO NOT EDIT\n\n" | |
| "TARGETS:= \n" | |
| "empty:= \n" | |
| "space:= $(empty) $(empty)\n" | |
| "spaceplus:= $(empty)\\ $(empty)\n\n"; | |
| for (auto const& lfile : lfiles) { | |
| makefileStream << "TARGETS += $(subst $(space),$(spaceplus),$(wildcard " | |
| << ConvertToMakefilePath(lfile) << "))\n"; | |
| } | |
| makefileStream << '\n'; | |
| std::string checkCache = | |
| cmStrCat(root->GetBinaryDirectory(), "/CMakeFiles/cmake.check_cache"); | |
| if (cm->DoWriteGlobVerifyTarget()) { | |
| makefileStream << ".NOTPARALLEL:\n\n" | |
| ".PHONY: all VERIFY_GLOBS\n\n" | |
| "all: VERIFY_GLOBS " | |
| << ConvertToMakefilePath(checkCache) | |
| << "\n\n" | |
| "VERIFY_GLOBS:\n" | |
| "\t" | |
| << ConvertToMakefilePath(cmSystemTools::GetCMakeCommand()) | |
| << " -P " | |
| << ConvertToMakefilePath(cm->GetGlobVerifyScript()) | |
| << "\n\n"; | |
| } | |
| makefileStream << ConvertToMakefilePath(checkCache) << ": $(TARGETS)\n"; | |
| makefileStream | |
| << '\t' << ConvertToMakefilePath(cmSystemTools::GetCMakeCommand()) << " -S" | |
| << ConvertToMakefilePath(root->GetSourceDirectory()) << " -B" | |
| << ConvertToMakefilePath(root->GetBinaryDirectory()) | |
| << (cm->GetIgnoreCompileWarningAsError() ? " --compile-no-warning-as-error" | |
| : "") | |
| << (cm->GetIgnoreLinkWarningAsError() ? " --link-no-warning-as-error" : "") | |
| << '\n'; | |
| } | |
| static bool objectIdLessThan(std::unique_ptr<cmXCodeObject> const& l, | |
| std::unique_ptr<cmXCodeObject> const& r) | |
| { | |
| return l->GetId() < r->GetId(); | |
| } | |
| void cmGlobalXCodeGenerator::SortXCodeObjects() | |
| { | |
| std::sort(this->XCodeObjects.begin(), this->XCodeObjects.end(), | |
| objectIdLessThan); | |
| } | |
| void cmGlobalXCodeGenerator::ClearXCodeObjects() | |
| { | |
| this->TargetDoneSet.clear(); | |
| this->XCodeObjects.clear(); | |
| this->XCodeObjectIDs.clear(); | |
| this->XCodeObjectMap.clear(); | |
| this->GroupMap.clear(); | |
| this->GroupNameMap.clear(); | |
| this->TargetGroup.clear(); | |
| this->FileRefs.clear(); | |
| this->ExternalLibRefs.clear(); | |
| this->FileRefToBuildFileMap.clear(); | |
| this->FileRefToEmbedBuildFileMap.clear(); | |
| this->CommandsVisited.clear(); | |
| } | |
| void cmGlobalXCodeGenerator::addObject(std::unique_ptr<cmXCodeObject> obj) | |
| { | |
| if (obj->GetType() == cmXCodeObject::OBJECT) { | |
| std::string const& id = obj->GetId(); | |
| // If this is a duplicate id, it's an error: | |
| // | |
| if (this->XCodeObjectIDs.count(id)) { | |
| cmSystemTools::Error( | |
| "Xcode generator: duplicate object ids not allowed"); | |
| } | |
| this->XCodeObjectIDs.insert(id); | |
| } | |
| this->XCodeObjects.push_back(std::move(obj)); | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateObject( | |
| cmXCodeObject::PBXType ptype, cm::string_view key) | |
| { | |
| auto obj = cm::make_unique<cmXCode21Object>(ptype, cmXCodeObject::OBJECT, | |
| this->GetObjectId(ptype, key)); | |
| auto* ptr = obj.get(); | |
| this->addObject(std::move(obj)); | |
| return ptr; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateObject(cmXCodeObject::Type type) | |
| { | |
| auto obj = cm::make_unique<cmXCodeObject>( | |
| cmXCodeObject::None, type, | |
| "Temporary cmake object, should not be referred to in Xcode file"); | |
| auto* ptr = obj.get(); | |
| this->addObject(std::move(obj)); | |
| return ptr; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateString(std::string const& s) | |
| { | |
| cmXCodeObject* obj = this->CreateObject(cmXCodeObject::STRING); | |
| obj->SetString(s); | |
| return obj; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateObjectReference( | |
| cmXCodeObject* ref) | |
| { | |
| cmXCodeObject* obj = this->CreateObject(cmXCodeObject::OBJECT_REF); | |
| obj->SetObject(ref); | |
| return obj; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateFlatClone(cmXCodeObject* orig) | |
| { | |
| cmXCodeObject* obj = this->CreateObject(orig->GetType()); | |
| obj->CopyAttributes(orig); | |
| return obj; | |
| } | |
| static std::string GetGroupMapKeyFromPath(cmGeneratorTarget* target, | |
| std::string const& fullpath) | |
| { | |
| return cmStrCat(target->GetName(), '-', fullpath); | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeBuildFileFromPath( | |
| std::string const& fullpath, cmGeneratorTarget* target, | |
| std::string const& lang, cmSourceFile* sf) | |
| { | |
| // Using a map and the full path guarantees that we will always get the same | |
| // fileRef object for any given full path. Same goes for the buildFile | |
| // object. | |
| cmXCodeObject* fileRef = | |
| this->CreateXCodeFileReferenceFromPath(fullpath, target, lang, sf); | |
| if (fileRef) { | |
| auto it = this->FileRefToBuildFileMap.find(fileRef); | |
| if (it == this->FileRefToBuildFileMap.end()) { | |
| cmXCodeObject* buildFile = | |
| this->CreateObject(cmXCodeObject::PBXBuildFile); | |
| buildFile->SetComment(fileRef->GetComment()); | |
| buildFile->AddAttribute("fileRef", this->CreateObjectReference(fileRef)); | |
| this->FileRefToBuildFileMap[fileRef] = buildFile; | |
| return buildFile; | |
| } | |
| return it->second; | |
| } | |
| return nullptr; | |
| } | |
| class XCodeGeneratorExpressionInterpreter | |
| : public cmGeneratorExpressionInterpreter | |
| { | |
| public: | |
| XCodeGeneratorExpressionInterpreter(cmSourceFile* sourceFile, | |
| cmLocalGenerator* localGenerator, | |
| cmGeneratorTarget* headTarget, | |
| std::string const& lang) | |
| : cmGeneratorExpressionInterpreter( | |
| localGenerator, "NO-PER-CONFIG-SUPPORT-IN-XCODE", headTarget, lang) | |
| , SourceFile(sourceFile) | |
| { | |
| } | |
| XCodeGeneratorExpressionInterpreter( | |
| XCodeGeneratorExpressionInterpreter const&) = delete; | |
| XCodeGeneratorExpressionInterpreter& operator=( | |
| XCodeGeneratorExpressionInterpreter const&) = delete; | |
| std::string const& Evaluate(char const* expression, | |
| std::string const& property) | |
| { | |
| return this->Evaluate(std::string(expression ? expression : ""), property); | |
| } | |
| std::string const& Evaluate(std::string const& expression, | |
| std::string const& property) | |
| { | |
| std::string const& processed = | |
| this->cmGeneratorExpressionInterpreter::Evaluate(expression, property); | |
| if (this->CompiledGeneratorExpression->GetHadContextSensitiveCondition()) { | |
| std::ostringstream e; | |
| /* clang-format off */ | |
| e << | |
| "Xcode does not support per-config per-source " << property << ":\n" | |
| " " << expression << "\n" | |
| "specified for source:\n" | |
| " " << this->SourceFile->ResolveFullPath() << '\n'; | |
| /* clang-format on */ | |
| this->LocalGenerator->IssueMessage(MessageType::FATAL_ERROR, e.str()); | |
| } | |
| return processed; | |
| } | |
| private: | |
| cmSourceFile* SourceFile = nullptr; | |
| }; | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeSourceFile( | |
| cmLocalGenerator* lg, cmSourceFile* sf, cmGeneratorTarget* gtgt) | |
| { | |
| std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf); | |
| XCodeGeneratorExpressionInterpreter genexInterpreter(sf, lg, gtgt, lang); | |
| // Add flags from target and source file properties. | |
| std::string flags; | |
| std::string const& srcfmt = sf->GetSafeProperty("Fortran_FORMAT"); | |
| switch (cmOutputConverter::GetFortranFormat(srcfmt)) { | |
| case cmOutputConverter::FortranFormatFixed: | |
| flags = cmStrCat("-fixed ", flags); | |
| break; | |
| case cmOutputConverter::FortranFormatFree: | |
| flags = cmStrCat("-free ", flags); | |
| break; | |
| default: | |
| break; | |
| } | |
| // Explicitly add the explicit language flag before any other flag | |
| // so user flags can override it. | |
| gtgt->AddExplicitLanguageFlags(flags, *sf); | |
| std::string const COMPILE_FLAGS("COMPILE_FLAGS"); | |
| if (cmValue cflags = sf->GetProperty(COMPILE_FLAGS)) { | |
| lg->AppendFlags(flags, genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS)); | |
| } | |
| std::string const COMPILE_OPTIONS("COMPILE_OPTIONS"); | |
| if (cmValue coptions = sf->GetProperty(COMPILE_OPTIONS)) { | |
| lg->AppendCompileOptions( | |
| flags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS)); | |
| } | |
| // Add per-source definitions. | |
| BuildObjectListOrString flagsBuild(this, false); | |
| std::string const COMPILE_DEFINITIONS("COMPILE_DEFINITIONS"); | |
| if (cmValue compile_defs = sf->GetProperty(COMPILE_DEFINITIONS)) { | |
| this->AppendDefines( | |
| flagsBuild, | |
| genexInterpreter.Evaluate(*compile_defs, COMPILE_DEFINITIONS).c_str(), | |
| true); | |
| } | |
| if (sf->GetPropertyAsBool("SKIP_PRECOMPILE_HEADERS")) { | |
| this->AppendDefines(flagsBuild, "CMAKE_SKIP_PRECOMPILE_HEADERS", true); | |
| } | |
| if (!flagsBuild.IsEmpty()) { | |
| if (!flags.empty()) { | |
| flags += ' '; | |
| } | |
| flags += flagsBuild.GetString(); | |
| } | |
| // Add per-source include directories. | |
| std::vector<std::string> includes; | |
| std::string const INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES"); | |
| if (cmValue cincludes = sf->GetProperty(INCLUDE_DIRECTORIES)) { | |
| lg->AppendIncludeDirectories( | |
| includes, genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES), | |
| *sf); | |
| } | |
| lg->AppendFlags(flags, | |
| lg->GetIncludeFlags(includes, gtgt, lang, std::string())); | |
| cmXCodeObject* buildFile = | |
| this->CreateXCodeBuildFileFromPath(sf->ResolveFullPath(), gtgt, lang, sf); | |
| cmXCodeObject* settings = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| settings->AddAttributeIfNotEmpty("COMPILER_FLAGS", | |
| this->CreateString(flags)); | |
| cmGeneratorTarget::SourceFileFlags tsFlags = | |
| gtgt->GetTargetSourceFileFlags(sf); | |
| cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| // Is this a "private" or "public" framework header file? | |
| // Set the ATTRIBUTES attribute appropriately... | |
| // | |
| if (gtgt->IsFrameworkOnApple()) { | |
| if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePrivateHeader) { | |
| attrs->AddObject(this->CreateString("Private")); | |
| } else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypePublicHeader) { | |
| attrs->AddObject(this->CreateString("Public")); | |
| } | |
| } | |
| // Add user-specified file attributes. | |
| cmValue extraFileAttributes = sf->GetProperty("XCODE_FILE_ATTRIBUTES"); | |
| if (extraFileAttributes) { | |
| // Expand the list of attributes. | |
| cmList attributes{ *extraFileAttributes }; | |
| // Store the attributes. | |
| for (auto const& attribute : attributes) { | |
| attrs->AddObject(this->CreateString(attribute)); | |
| } | |
| } | |
| settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs); | |
| if (buildFile) { | |
| buildFile->AddAttributeIfNotEmpty("settings", settings); | |
| } | |
| return buildFile; | |
| } | |
| void cmGlobalXCodeGenerator::AddXCodeProjBuildRule( | |
| cmGeneratorTarget* target, std::vector<cmSourceFile*>& sources) const | |
| { | |
| std::string listfile = this->GetCMakeInstance()->GetCMakeListFile( | |
| target->GetLocalGenerator()->GetCurrentSourceDirectory()); | |
| cmSourceFile* srcCMakeLists = target->Makefile->GetOrCreateSource( | |
| listfile, false, cmSourceFileLocationKind::Known); | |
| if (!cm::contains(sources, srcCMakeLists)) { | |
| sources.push_back(srcCMakeLists); | |
| } | |
| } | |
| namespace { | |
| bool IsLinkPhaseLibraryExtension(std::string const& fileExt) | |
| { | |
| // Empty file extension is a special case for paths to framework's | |
| // internal binary which could be MyFw.framework/Versions/*/MyFw | |
| return (fileExt == ".framework"_s || fileExt == ".xcframework"_s || | |
| fileExt == ".a"_s || fileExt == ".o"_s || fileExt == ".dylib"_s || | |
| fileExt == ".tbd"_s || fileExt.empty()); | |
| } | |
| bool IsLibraryType(std::string const& fileType) | |
| { | |
| return (fileType == "wrapper.framework"_s || | |
| fileType == "wrapper.xcframework"_s || fileType == "archive.ar"_s || | |
| fileType == "compiled.mach-o.objfile"_s || | |
| fileType == "compiled.mach-o.dylib"_s || | |
| fileType == "compiled.mach-o.executable"_s || | |
| fileType == "sourcecode.text-based-dylib-definition"_s); | |
| } | |
| std::string GetDirectoryValueFromFileExtension(std::string const& dirExt) | |
| { | |
| std::string ext = cmSystemTools::LowerCase(dirExt); | |
| if (ext == "framework"_s) { | |
| return "wrapper.framework"; | |
| } | |
| if (ext == "xcframework"_s) { | |
| return "wrapper.xcframework"; | |
| } | |
| if (ext == "xcassets"_s) { | |
| return "folder.assetcatalog"; | |
| } | |
| return "folder"; | |
| } | |
| std::string GetSourcecodeValueFromFileExtension( | |
| std::string const& _ext, std::string const& lang, | |
| bool& keepLastKnownFileType, std::vector<std::string> const& enabled_langs) | |
| { | |
| std::string ext = cmSystemTools::LowerCase(_ext); | |
| std::string sourcecode = "default"; | |
| if (ext == "o"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "compiled.mach-o.objfile"; | |
| } else if (ext == "xctest"_s) { | |
| sourcecode = "wrapper.cfbundle"; | |
| } else if (ext == "xib"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "file.xib"; | |
| } else if (ext == "storyboard"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "file.storyboard"; | |
| // NOLINTNEXTLINE(bugprone-branch-clone) | |
| } else if (ext == "mm"_s && !cm::contains(enabled_langs, "OBJCXX")) { | |
| sourcecode = "sourcecode.cpp.objcpp"; | |
| // NOLINTNEXTLINE(bugprone-branch-clone) | |
| } else if (ext == "m"_s && !cm::contains(enabled_langs, "OBJC")) { | |
| sourcecode = "sourcecode.c.objc"; | |
| } else if (ext == "swift"_s) { | |
| sourcecode = "sourcecode.swift"; | |
| } else if (ext == "plist"_s) { | |
| sourcecode = "sourcecode.text.plist"; | |
| } else if (ext == "h"_s) { | |
| sourcecode = "sourcecode.c.h"; | |
| } else if (ext == "hxx"_s || ext == "hpp"_s || ext == "txx"_s || | |
| ext == "pch"_s || ext == "hh"_s || ext == "inl"_s) { | |
| sourcecode = "sourcecode.cpp.h"; | |
| } else if (ext == "png"_s || ext == "gif"_s || ext == "jpg"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "image"; | |
| } else if (ext == "txt"_s) { | |
| sourcecode = "sourcecode.text"; | |
| } else if (lang == "CXX"_s) { | |
| sourcecode = "sourcecode.cpp.cpp"; | |
| } else if (lang == "C"_s) { | |
| sourcecode = "sourcecode.c.c"; | |
| } else if (lang == "OBJCXX"_s) { | |
| sourcecode = "sourcecode.cpp.objcpp"; | |
| } else if (lang == "OBJC"_s) { | |
| sourcecode = "sourcecode.c.objc"; | |
| } else if (lang == "Fortran"_s) { | |
| sourcecode = "sourcecode.fortran.f90"; | |
| } else if (lang == "ASM"_s) { | |
| sourcecode = "sourcecode.asm"; | |
| } else if (ext == "metal"_s) { | |
| sourcecode = "sourcecode.metal"; | |
| } else if (ext == "mig"_s) { | |
| sourcecode = "sourcecode.mig"; | |
| } else if (ext == "tbd"_s) { | |
| sourcecode = "sourcecode.text-based-dylib-definition"; | |
| } else if (ext == "a"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "archive.ar"; | |
| } else if (ext == "dylib"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "compiled.mach-o.dylib"; | |
| } else if (ext == "framework"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "wrapper.framework"; | |
| } else if (ext == "xcassets"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "folder.assetcatalog"; | |
| } else if (ext == "xcconfig"_s) { | |
| keepLastKnownFileType = true; | |
| sourcecode = "text.xcconfig"; | |
| } | |
| // else | |
| // { | |
| // // Already specialized above or we leave sourcecode == "sourcecode" | |
| // // which is probably the most correct choice. Extensionless headers, | |
| // // for example... Or file types unknown to Xcode that do not map to a | |
| // // valid explicitFileType value. | |
| // } | |
| return sourcecode; | |
| } | |
| template <class T> | |
| std::string GetTargetObjectDirArch(T const& target, | |
| std::string const& defaultVal) | |
| { | |
| cmList archs{ target.GetSafeProperty("OSX_ARCHITECTURES") }; | |
| if (archs.size() > 1) { | |
| return "$(CURRENT_ARCH)"; | |
| } | |
| if (archs.size() == 1) { | |
| return archs.front(); | |
| } | |
| return defaultVal; | |
| } | |
| } // anonymous | |
| // Extracts the framework directory, if path matches the framework syntax | |
| // otherwise returns the path untouched | |
| std::string cmGlobalXCodeGenerator::GetLibraryOrFrameworkPath( | |
| std::string const& path) const | |
| { | |
| auto fwDescriptor = this->SplitFrameworkPath(path); | |
| if (fwDescriptor) { | |
| return fwDescriptor->GetFrameworkPath(); | |
| } | |
| return path; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReferenceFromPath( | |
| std::string const& fullpath, cmGeneratorTarget* target, | |
| std::string const& lang, cmSourceFile* sf) | |
| { | |
| bool useLastKnownFileType = false; | |
| std::string fileType; | |
| if (sf) { | |
| if (cmValue e = sf->GetProperty("XCODE_EXPLICIT_FILE_TYPE")) { | |
| fileType = *e; | |
| } else if (cmValue l = sf->GetProperty("XCODE_LAST_KNOWN_FILE_TYPE")) { | |
| useLastKnownFileType = true; | |
| fileType = *l; | |
| } | |
| } | |
| // Make a copy so that we can override it later | |
| std::string path = cmSystemTools::CollapseFullPath(fullpath); | |
| // Compute the extension without leading '.'. | |
| std::string ext = cmSystemTools::GetFilenameLastExtension(path); | |
| if (!ext.empty()) { | |
| ext = ext.substr(1); | |
| } | |
| if (fileType.empty()) { | |
| path = this->GetLibraryOrFrameworkPath(path); | |
| ext = cmSystemTools::GetFilenameLastExtension(path); | |
| if (!ext.empty()) { | |
| ext = ext.substr(1); | |
| } | |
| // If fullpath references a directory, then we need to specify | |
| // lastKnownFileType as folder in order for Xcode to be able to | |
| // open the contents of the folder. | |
| // (Xcode 4.6 does not like explicitFileType=folder). | |
| if (cmSystemTools::FileIsDirectory(path)) { | |
| fileType = GetDirectoryValueFromFileExtension(ext); | |
| useLastKnownFileType = true; | |
| } else { | |
| if (ext.empty() && !sf) { | |
| // Special case for executable or library without extension | |
| // that is not a source file. We can't tell which without reading | |
| // its Mach-O header, but the file might not exist yet, so we | |
| // have to pick one here. | |
| useLastKnownFileType = true; | |
| fileType = "compiled.mach-o.executable"; | |
| } else { | |
| fileType = GetSourcecodeValueFromFileExtension( | |
| ext, lang, useLastKnownFileType, this->EnabledLangs); | |
| } | |
| } | |
| } | |
| std::string key = GetGroupMapKeyFromPath(target, path); | |
| cmXCodeObject* fileRef = this->FileRefs[key]; | |
| if (!fileRef) { | |
| fileRef = this->CreateObject(cmXCodeObject::PBXFileReference); | |
| fileRef->SetComment(path); | |
| this->FileRefs[key] = fileRef; | |
| } | |
| fileRef->AddAttribute("fileEncoding", this->CreateString("4")); | |
| fileRef->AddAttribute(useLastKnownFileType ? "lastKnownFileType" | |
| : "explicitFileType", | |
| this->CreateString(fileType)); | |
| // Store the file path relative to the top of the source tree. | |
| if (!IsLibraryType(fileType)) { | |
| path = this->RelativeToSource(path); | |
| } | |
| std::string name = cmSystemTools::GetFilenameName(path); | |
| char const* sourceTree = | |
| cmSystemTools::FileIsFullPath(path) ? "<absolute>" : "SOURCE_ROOT"; | |
| fileRef->AddAttribute("name", this->CreateString(name)); | |
| fileRef->AddAttribute("path", this->CreateString(path)); | |
| fileRef->AddAttribute("sourceTree", this->CreateString(sourceTree)); | |
| cmXCodeObject* group = this->GroupMap[key]; | |
| if (!group) { | |
| if (IsLibraryType(fileType)) { | |
| group = this->FrameworkGroup; | |
| } else if (fileType == "folder") { | |
| group = this->ResourcesGroup; | |
| } | |
| if (group) | |
| this->GroupMap[key] = group; | |
| } | |
| if (!group) { | |
| cmSystemTools::Error(cmStrCat("Could not find a PBX group for ", key)); | |
| return nullptr; | |
| } | |
| cmXCodeObject* children = group->GetAttribute("children"); | |
| if (!children->HasObject(fileRef)) { | |
| children->AddObject(fileRef); | |
| } | |
| return fileRef; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeFileReference( | |
| cmSourceFile* sf, cmGeneratorTarget* target) | |
| { | |
| std::string lang = this->CurrentLocalGenerator->GetSourceFileLanguage(*sf); | |
| return this->CreateXCodeFileReferenceFromPath(sf->ResolveFullPath(), target, | |
| lang, sf); | |
| } | |
| bool cmGlobalXCodeGenerator::SpecialTargetEmitted(std::string const& tname) | |
| { | |
| if (tname == "ALL_BUILD"_s || tname == "install"_s || tname == "package"_s || | |
| tname == "RUN_TESTS"_s || tname == CMAKE_CHECK_BUILD_SYSTEM_TARGET) { | |
| if (this->TargetDoneSet.find(tname) != this->TargetDoneSet.end()) { | |
| return true; | |
| } | |
| this->TargetDoneSet.insert(tname); | |
| return false; | |
| } | |
| return false; | |
| } | |
| void cmGlobalXCodeGenerator::SetCurrentLocalGenerator(cmLocalGenerator* gen) | |
| { | |
| this->CurrentLocalGenerator = gen; | |
| this->CurrentMakefile = gen->GetMakefile(); | |
| // Select the current set of configuration types. | |
| this->CurrentConfigurationTypes = | |
| this->CurrentMakefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); | |
| } | |
| struct cmSourceFilePathCompare | |
| { | |
| bool operator()(cmSourceFile* l, cmSourceFile* r) | |
| { | |
| return l->ResolveFullPath() < r->ResolveFullPath(); | |
| } | |
| }; | |
| struct cmCompareTargets | |
| { | |
| bool operator()(cmXCodeObject* l, cmXCodeObject* r) const | |
| { | |
| std::string const& a = l->GetTarget()->GetName(); | |
| std::string const& b = r->GetTarget()->GetName(); | |
| if (a == b) { | |
| return false; | |
| } | |
| if (a == "ALL_BUILD"_s) { | |
| return true; | |
| } | |
| if (b == "ALL_BUILD"_s) { | |
| return false; | |
| } | |
| std::string a_low = cmSystemTools::LowerCase(l->GetTarget()->GetName()); | |
| std::string b_low = cmSystemTools::LowerCase(r->GetTarget()->GetName()); | |
| if (a_low != b_low) { | |
| return a_low < b_low; | |
| } | |
| return a < b; | |
| } | |
| }; | |
| bool cmGlobalXCodeGenerator::CreateXCodeTargets( | |
| cmLocalGenerator* gen, std::vector<cmXCodeObject*>& targets) | |
| { | |
| this->SetCurrentLocalGenerator(gen); | |
| std::vector<cmGeneratorTarget*> gts = | |
| this->GetLocalGeneratorTargetsInOrder(gen); | |
| for (auto* gtgt : gts) { | |
| if (!this->CreateXCodeTarget(gtgt, targets)) { | |
| return false; | |
| } | |
| } | |
| std::sort(targets.begin(), targets.end(), cmCompareTargets()); | |
| return true; | |
| } | |
| bool cmGlobalXCodeGenerator::CreateXCodeTarget( | |
| cmGeneratorTarget* gtgt, std::vector<cmXCodeObject*>& targets) | |
| { | |
| std::string targetName = gtgt->GetName(); | |
| // make sure ALL_BUILD, INSTALL, etc are only done once | |
| if (this->SpecialTargetEmitted(targetName)) { | |
| return true; | |
| } | |
| if (!gtgt->IsInBuildSystem()) { | |
| return true; | |
| } | |
| for (std::string const& configName : this->CurrentConfigurationTypes) { | |
| gtgt->CheckCxxModuleStatus(configName); | |
| } | |
| auto& gtgt_visited = this->CommandsVisited[gtgt]; | |
| auto const& deps = this->GetTargetDirectDepends(gtgt); | |
| for (auto const& d : deps) { | |
| // Take the union of visited source files of custom commands so far. | |
| // ComputeTargetOrder ensures our dependencies already visited their | |
| // custom commands and updated CommandsVisited. | |
| auto& dep_visited = this->CommandsVisited[d]; | |
| gtgt_visited.insert(dep_visited.begin(), dep_visited.end()); | |
| } | |
| if (gtgt->GetType() == cmStateEnums::UTILITY || | |
| gtgt->GetType() == cmStateEnums::INTERFACE_LIBRARY || | |
| gtgt->GetType() == cmStateEnums::GLOBAL_TARGET) { | |
| cmXCodeObject* t = this->CreateUtilityTarget(gtgt); | |
| if (!t) { | |
| return false; | |
| } | |
| targets.push_back(t); | |
| return true; | |
| } | |
| // organize the sources | |
| std::vector<cmSourceFile*> commonSourceFiles; | |
| if (!gtgt->GetConfigCommonSourceFilesForXcode(commonSourceFiles)) { | |
| return false; | |
| } | |
| // Add CMakeLists.txt file for user convenience. | |
| this->AddXCodeProjBuildRule(gtgt, commonSourceFiles); | |
| // Add the Info.plist we are about to generate for an App Bundle. | |
| if (gtgt->GetPropertyAsBool("MACOSX_BUNDLE")) { | |
| std::string plist = this->ComputeInfoPListLocation(gtgt); | |
| cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource( | |
| plist, true, cmSourceFileLocationKind::Known); | |
| commonSourceFiles.push_back(sf); | |
| } | |
| std::sort(commonSourceFiles.begin(), commonSourceFiles.end(), | |
| cmSourceFilePathCompare()); | |
| gtgt->ComputeObjectMapping(); | |
| std::vector<cmXCodeObject*> externalObjFiles; | |
| std::vector<cmXCodeObject*> headerFiles; | |
| std::vector<cmXCodeObject*> resourceFiles; | |
| std::vector<cmXCodeObject*> sourceFiles; | |
| for (auto* sourceFile : commonSourceFiles) { | |
| cmXCodeObject* xsf = this->CreateXCodeSourceFile( | |
| this->CurrentLocalGenerator, sourceFile, gtgt); | |
| cmXCodeObject* fr = xsf->GetAttribute("fileRef"); | |
| cmXCodeObject* filetype = | |
| fr->GetObject()->GetAttribute("explicitFileType"); | |
| if (!filetype) { | |
| filetype = fr->GetObject()->GetAttribute("lastKnownFileType"); | |
| } | |
| cmGeneratorTarget::SourceFileFlags tsFlags = | |
| gtgt->GetTargetSourceFileFlags(sourceFile); | |
| if (filetype && filetype->GetString() == "compiled.mach-o.objfile"_s) { | |
| if (sourceFile->GetObjectLibrary().empty()) { | |
| externalObjFiles.push_back(xsf); | |
| } | |
| } else if (this->IsHeaderFile(sourceFile) || | |
| (tsFlags.Type == | |
| cmGeneratorTarget::SourceFileTypePrivateHeader) || | |
| (tsFlags.Type == | |
| cmGeneratorTarget::SourceFileTypePublicHeader)) { | |
| headerFiles.push_back(xsf); | |
| } else if (tsFlags.Type == cmGeneratorTarget::SourceFileTypeResource) { | |
| resourceFiles.push_back(xsf); | |
| } else if (!sourceFile->GetPropertyAsBool("HEADER_FILE_ONLY") && | |
| !gtgt->IsSourceFilePartOfUnityBatch( | |
| sourceFile->ResolveFullPath())) { | |
| // Include this file in the build if it has a known language | |
| // and has not been listed as an ignored extension for this | |
| // generator. | |
| if (!this->CurrentLocalGenerator->GetSourceFileLanguage(*sourceFile) | |
| .empty() && | |
| !this->IgnoreFile(sourceFile->GetExtension().c_str())) { | |
| sourceFiles.push_back(xsf); | |
| } | |
| } | |
| } | |
| // some build phases only apply to bundles and/or frameworks | |
| bool isFrameworkTarget = gtgt->IsFrameworkOnApple(); | |
| bool isBundleTarget = gtgt->GetPropertyAsBool("MACOSX_BUNDLE"); | |
| bool isCFBundleTarget = gtgt->IsCFBundleOnApple(); | |
| cmXCodeObject* buildFiles = nullptr; | |
| // create source build phase | |
| cmXCodeObject* sourceBuildPhase = nullptr; | |
| if (!sourceFiles.empty()) { | |
| sourceBuildPhase = this->CreateObject(cmXCodeObject::PBXSourcesBuildPhase); | |
| sourceBuildPhase->SetComment("Sources"); | |
| sourceBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (auto& sourceFile : sourceFiles) { | |
| buildFiles->AddObject(sourceFile); | |
| } | |
| sourceBuildPhase->AddAttribute("files", buildFiles); | |
| sourceBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| } | |
| // create header build phase - only for framework targets | |
| cmXCodeObject* headerBuildPhase = nullptr; | |
| if (!headerFiles.empty() && isFrameworkTarget) { | |
| headerBuildPhase = this->CreateObject(cmXCodeObject::PBXHeadersBuildPhase); | |
| headerBuildPhase->SetComment("Headers"); | |
| headerBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (auto& headerFile : headerFiles) { | |
| buildFiles->AddObject(headerFile); | |
| } | |
| headerBuildPhase->AddAttribute("files", buildFiles); | |
| headerBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| } | |
| // create resource build phase - only for framework or bundle targets | |
| cmXCodeObject* resourceBuildPhase = nullptr; | |
| if (!resourceFiles.empty() && | |
| (isFrameworkTarget || isBundleTarget || isCFBundleTarget)) { | |
| resourceBuildPhase = | |
| this->CreateObject(cmXCodeObject::PBXResourcesBuildPhase); | |
| resourceBuildPhase->SetComment("Resources"); | |
| resourceBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (auto& resourceFile : resourceFiles) { | |
| buildFiles->AddObject(resourceFile); | |
| } | |
| resourceBuildPhase->AddAttribute("files", buildFiles); | |
| resourceBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| } | |
| // create vector of "non-resource content file" build phases - only for | |
| // framework or bundle targets | |
| std::vector<cmXCodeObject*> contentBuildPhases; | |
| if (isFrameworkTarget || isBundleTarget || isCFBundleTarget) { | |
| using mapOfVectorOfSourceFiles = | |
| std::map<std::string, std::vector<cmSourceFile*>>; | |
| mapOfVectorOfSourceFiles bundleFiles; | |
| for (auto* sourceFile : commonSourceFiles) { | |
| cmGeneratorTarget::SourceFileFlags tsFlags = | |
| gtgt->GetTargetSourceFileFlags(sourceFile); | |
| if (tsFlags.Type == cmGeneratorTarget::SourceFileTypeMacContent) { | |
| bundleFiles[tsFlags.MacFolder].push_back(sourceFile); | |
| } | |
| } | |
| for (auto const& keySources : bundleFiles) { | |
| cmXCodeObject* copyFilesBuildPhase = | |
| this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase); | |
| copyFilesBuildPhase->SetComment("Copy files"); | |
| copyFilesBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| copyFilesBuildPhase->AddAttribute("dstSubfolderSpec", | |
| this->CreateString("6")); | |
| std::ostringstream ostr; | |
| if (gtgt->IsFrameworkOnApple()) { | |
| // dstPath in frameworks is relative to Versions/<version> | |
| ostr << keySources.first; | |
| } else if (keySources.first != "MacOS"_s) { | |
| if (gtgt->Target->GetMakefile()->PlatformIsAppleEmbedded()) { | |
| ostr << keySources.first; | |
| } else { | |
| // dstPath in bundles is relative to Contents/MacOS | |
| ostr << "../" << keySources.first; | |
| } | |
| } | |
| copyFilesBuildPhase->AddAttribute("dstPath", | |
| this->CreateString(ostr.str())); | |
| copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| copyFilesBuildPhase->AddAttribute("files", buildFiles); | |
| for (auto* sourceFile : keySources.second) { | |
| cmXCodeObject* xsf = this->CreateXCodeSourceFile( | |
| this->CurrentLocalGenerator, sourceFile, gtgt); | |
| buildFiles->AddObject(xsf); | |
| } | |
| contentBuildPhases.push_back(copyFilesBuildPhase); | |
| } | |
| } | |
| // create vector of "resource content file" build phases - only for | |
| // framework or bundle targets | |
| if (isFrameworkTarget || isBundleTarget || isCFBundleTarget) { | |
| using mapOfVectorOfSourceFiles = | |
| std::map<std::string, std::vector<cmSourceFile*>>; | |
| mapOfVectorOfSourceFiles bundleFiles; | |
| for (auto* sourceFile : commonSourceFiles) { | |
| cmGeneratorTarget::SourceFileFlags tsFlags = | |
| gtgt->GetTargetSourceFileFlags(sourceFile); | |
| if (tsFlags.Type == cmGeneratorTarget::SourceFileTypeDeepResource) { | |
| bundleFiles[tsFlags.MacFolder].push_back(sourceFile); | |
| } | |
| } | |
| for (auto const& keySources : bundleFiles) { | |
| cmXCodeObject* copyFilesBuildPhase = | |
| this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase); | |
| copyFilesBuildPhase->SetComment("Copy files"); | |
| copyFilesBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| copyFilesBuildPhase->AddAttribute("dstSubfolderSpec", | |
| this->CreateString("7")); | |
| copyFilesBuildPhase->AddAttribute("dstPath", | |
| this->CreateString(keySources.first)); | |
| copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| copyFilesBuildPhase->AddAttribute("files", buildFiles); | |
| for (auto* sourceFile : keySources.second) { | |
| cmXCodeObject* xsf = this->CreateXCodeSourceFile( | |
| this->CurrentLocalGenerator, sourceFile, gtgt); | |
| buildFiles->AddObject(xsf); | |
| } | |
| contentBuildPhases.push_back(copyFilesBuildPhase); | |
| } | |
| } | |
| // Always create Link Binary With Libraries build phase | |
| cmXCodeObject* frameworkBuildPhase = nullptr; | |
| frameworkBuildPhase = | |
| this->CreateObject(cmXCodeObject::PBXFrameworksBuildPhase); | |
| frameworkBuildPhase->SetComment("Frameworks"); | |
| frameworkBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| frameworkBuildPhase->AddAttribute("files", buildFiles); | |
| // Add all collected .o files to this build phase | |
| for (auto& externalObjFile : externalObjFiles) { | |
| buildFiles->AddObject(externalObjFile); | |
| } | |
| frameworkBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| // create list of build phases and create the Xcode target | |
| cmXCodeObject* buildPhases = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| this->CreateCustomCommands(buildPhases, sourceBuildPhase, headerBuildPhase, | |
| resourceBuildPhase, contentBuildPhases, | |
| frameworkBuildPhase, gtgt); | |
| targets.push_back(this->CreateXCodeTarget(gtgt, buildPhases)); | |
| return true; | |
| } | |
| void cmGlobalXCodeGenerator::ForceLinkerLanguages() | |
| { | |
| for (auto const& localGenerator : this->LocalGenerators) { | |
| // All targets depend on the build-system check target. | |
| for (auto const& tgt : localGenerator->GetGeneratorTargets()) { | |
| // This makes sure all targets link using the proper language. | |
| this->ForceLinkerLanguage(tgt.get()); | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::ForceLinkerLanguage(cmGeneratorTarget* gtgt) | |
| { | |
| // This matters only for targets that link. | |
| if (gtgt->GetType() != cmStateEnums::EXECUTABLE && | |
| gtgt->GetType() != cmStateEnums::SHARED_LIBRARY && | |
| gtgt->GetType() != cmStateEnums::MODULE_LIBRARY) { | |
| return; | |
| } | |
| std::string llang = gtgt->GetLinkerLanguage("NOCONFIG"); | |
| if (llang.empty()) { | |
| return; | |
| } | |
| // If the language is compiled as a source trust Xcode to link with it. | |
| for (auto const& Language : | |
| gtgt->GetLinkImplementation("NOCONFIG", cmGeneratorTarget::UseTo::Link) | |
| ->Languages) { | |
| if (Language == llang) { | |
| return; | |
| } | |
| } | |
| // Allow empty source file list for iOS Sticker packs | |
| if (char const* productType = GetTargetProductType(gtgt)) { | |
| if (strcmp(productType, | |
| "com.apple.product-type.app-extension.messages-sticker-pack") == | |
| 0) { | |
| return; | |
| } | |
| } | |
| // Add an empty source file to the target that compiles with the | |
| // linker language. This should convince Xcode to choose the proper | |
| // language. | |
| cmMakefile* mf = gtgt->Target->GetMakefile(); | |
| std::string fname = cmStrCat( | |
| gtgt->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/CMakeFiles/", | |
| gtgt->GetName(), "-CMakeForceLinker.", cmSystemTools::LowerCase(llang)); | |
| { | |
| cmGeneratedFileStream fout(fname); | |
| fout << '\n'; | |
| } | |
| if (cmSourceFile* sf = mf->GetOrCreateSource(fname)) { | |
| sf->SetProperty("LANGUAGE", llang); | |
| sf->SetProperty("CXX_SCAN_FOR_MODULES", "0"); | |
| gtgt->AddSource(fname); | |
| } | |
| } | |
| bool cmGlobalXCodeGenerator::IsHeaderFile(cmSourceFile* sf) | |
| { | |
| return cm::contains(this->CMakeInstance->GetHeaderExtensions(), | |
| sf->GetExtension()); | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateLegacyRunScriptBuildPhase( | |
| char const* name, char const* name2, cmGeneratorTarget* target, | |
| std::vector<cmCustomCommand> const& commands) | |
| { | |
| if (commands.empty() && strcmp(name, "CMake ReRun") != 0) { | |
| return nullptr; | |
| } | |
| cmXCodeObject* buildPhase = | |
| this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase); | |
| buildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| buildPhase->AddAttribute("files", buildFiles); | |
| buildPhase->AddAttribute("name", this->CreateString(name)); | |
| buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh")); | |
| this->AddCommandsToBuildPhase(buildPhase, target, commands, name2); | |
| return buildPhase; | |
| } | |
| void cmGlobalXCodeGenerator::CreateCustomCommands( | |
| cmXCodeObject* buildPhases, cmXCodeObject* sourceBuildPhase, | |
| cmXCodeObject* headerBuildPhase, cmXCodeObject* resourceBuildPhase, | |
| std::vector<cmXCodeObject*> const& contentBuildPhases, | |
| cmXCodeObject* frameworkBuildPhase, cmGeneratorTarget* gtgt) | |
| { | |
| std::vector<cmCustomCommand> const& prebuild = gtgt->GetPreBuildCommands(); | |
| std::vector<cmCustomCommand> const& prelink = gtgt->GetPreLinkCommands(); | |
| std::vector<cmCustomCommand> postbuild = gtgt->GetPostBuildCommands(); | |
| if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY && | |
| !gtgt->IsFrameworkOnApple()) { | |
| std::string str_file = cmStrCat("$<TARGET_FILE:", gtgt->GetName(), '>'); | |
| std::string str_so_file = | |
| cmStrCat("$<TARGET_SONAME_FILE:", gtgt->GetName(), '>'); | |
| std::string str_link_file = | |
| cmStrCat("$<TARGET_LINKER_LIBRARY_FILE:", gtgt->GetName(), '>'); | |
| cmCustomCommandLines cmd = cmMakeSingleCommandLine( | |
| { cmSystemTools::GetCMakeCommand(), "-E", "cmake_symlink_library", | |
| str_file, str_so_file, str_link_file }); | |
| cmCustomCommand command; | |
| command.SetCommandLines(cmd); | |
| command.SetComment("Creating symlinks"); | |
| command.SetWorkingDirectory(""); | |
| command.SetBacktrace(this->CurrentMakefile->GetBacktrace()); | |
| command.SetStdPipesUTF8(true); | |
| postbuild.push_back(std::move(command)); | |
| } | |
| if (gtgt->HasImportLibrary("") && !gtgt->IsFrameworkOnApple()) { | |
| // create symbolic links for .tbd file | |
| std::string file = cmStrCat("$<TARGET_IMPORT_FILE:", gtgt->GetName(), '>'); | |
| std::string soFile = | |
| cmStrCat("$<TARGET_SONAME_IMPORT_FILE:", gtgt->GetName(), '>'); | |
| std::string linkFile = | |
| cmStrCat("$<TARGET_LINKER_IMPORT_FILE:", gtgt->GetName(), '>'); | |
| cmCustomCommandLines symlink_command = cmMakeSingleCommandLine( | |
| { cmSystemTools::GetCMakeCommand(), "-E", "cmake_symlink_library", file, | |
| soFile, linkFile }); | |
| cmCustomCommand command; | |
| command.SetCommandLines(symlink_command); | |
| command.SetComment("Creating import symlinks"); | |
| command.SetWorkingDirectory(""); | |
| command.SetBacktrace(this->CurrentMakefile->GetBacktrace()); | |
| command.SetStdPipesUTF8(true); | |
| postbuild.push_back(std::move(command)); | |
| } | |
| cmXCodeObject* legacyCustomCommandsBuildPhase = nullptr; | |
| cmXCodeObject* preBuildPhase = nullptr; | |
| cmXCodeObject* preLinkPhase = nullptr; | |
| cmXCodeObject* postBuildPhase = nullptr; | |
| if (this->XcodeBuildSystem >= BuildSystem::Twelve) { | |
| // create prebuild phase | |
| preBuildPhase = | |
| this->CreateRunScriptBuildPhase("CMake PreBuild Rules", gtgt, prebuild); | |
| // create prelink phase | |
| preLinkPhase = | |
| this->CreateRunScriptBuildPhase("CMake PreLink Rules", gtgt, prelink); | |
| // create postbuild phase | |
| postBuildPhase = this->CreateRunScriptBuildPhase("CMake PostBuild Rules", | |
| gtgt, postbuild); | |
| } else { | |
| std::vector<cmSourceFile*> classes; | |
| if (!gtgt->GetConfigCommonSourceFilesForXcode(classes)) { | |
| return; | |
| } | |
| // add all the sources | |
| std::vector<cmCustomCommand> commands; | |
| auto& visited = this->CommandsVisited[gtgt]; | |
| for (auto* sourceFile : classes) { | |
| if (sourceFile->GetCustomCommand() && | |
| visited.insert(sourceFile).second) { | |
| commands.push_back(*sourceFile->GetCustomCommand()); | |
| } | |
| } | |
| // create custom commands phase | |
| legacyCustomCommandsBuildPhase = this->CreateLegacyRunScriptBuildPhase( | |
| "CMake Rules", "cmakeRulesBuildPhase", gtgt, commands); | |
| // create prebuild phase | |
| preBuildPhase = this->CreateLegacyRunScriptBuildPhase( | |
| "CMake PreBuild Rules", "preBuildCommands", gtgt, prebuild); | |
| // create prelink phase | |
| preLinkPhase = this->CreateLegacyRunScriptBuildPhase( | |
| "CMake PreLink Rules", "preLinkCommands", gtgt, prelink); | |
| // create postbuild phase | |
| postBuildPhase = this->CreateLegacyRunScriptBuildPhase( | |
| "CMake PostBuild Rules", "postBuildPhase", gtgt, postbuild); | |
| } | |
| // The order here is the order they will be built in. | |
| // The order "headers, resources, sources" mimics a native project generated | |
| // from an xcode template... | |
| // | |
| if (preBuildPhase) { | |
| buildPhases->AddObject(preBuildPhase); | |
| } | |
| if (legacyCustomCommandsBuildPhase) { | |
| buildPhases->AddObject(legacyCustomCommandsBuildPhase); | |
| } | |
| if (this->XcodeBuildSystem >= BuildSystem::Twelve) { | |
| this->CreateRunScriptBuildPhases(buildPhases, gtgt); | |
| } | |
| if (headerBuildPhase) { | |
| buildPhases->AddObject(headerBuildPhase); | |
| } | |
| if (resourceBuildPhase) { | |
| buildPhases->AddObject(resourceBuildPhase); | |
| } | |
| for (auto* obj : contentBuildPhases) { | |
| buildPhases->AddObject(obj); | |
| } | |
| if (sourceBuildPhase) { | |
| buildPhases->AddObject(sourceBuildPhase); | |
| } | |
| if (preLinkPhase) { | |
| buildPhases->AddObject(preLinkPhase); | |
| } | |
| if (frameworkBuildPhase) { | |
| buildPhases->AddObject(frameworkBuildPhase); | |
| } | |
| // When this build phase is present, it must be last. More build phases may | |
| // be added later for embedding things and they will insert themselves just | |
| // before this last build phase. | |
| if (postBuildPhase) { | |
| buildPhases->AddObject(postBuildPhase); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases( | |
| cmXCodeObject* buildPhases, cmGeneratorTarget const* gt) | |
| { | |
| std::vector<cmSourceFile*> sources; | |
| if (!gt->GetConfigCommonSourceFilesForXcode(sources)) { | |
| return; | |
| } | |
| auto& visited = this->CommandsVisited[gt]; | |
| for (auto* sf : sources) { | |
| this->CreateRunScriptBuildPhases(buildPhases, sf, gt, visited); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::CreateRunScriptBuildPhases( | |
| cmXCodeObject* buildPhases, cmSourceFile const* sf, | |
| cmGeneratorTarget const* gt, std::set<cmSourceFile const*>& visited) | |
| { | |
| cmCustomCommand const* cc = sf->GetCustomCommand(); | |
| if (cc && visited.insert(sf).second) { | |
| this->CustomCommandRoots[sf].insert(gt); | |
| if (std::vector<cmSourceFile*> const* depends = gt->GetSourceDepends(sf)) { | |
| for (cmSourceFile const* di : *depends) { | |
| this->CreateRunScriptBuildPhases(buildPhases, di, gt, visited); | |
| } | |
| } | |
| cmXCodeObject* buildPhase = this->CreateRunScriptBuildPhase(sf, gt, *cc); | |
| buildPhases->AddObject(buildPhase); | |
| } | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase( | |
| cmSourceFile const* sf, cmGeneratorTarget const* gt, | |
| cmCustomCommand const& cc) | |
| { | |
| std::set<std::string> allConfigInputs; | |
| std::set<std::string> allConfigOutputs; | |
| cmXCodeObject* buildPhase = | |
| this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase, | |
| cmStrCat(gt->GetName(), ':', sf->GetFullPath())); | |
| auto depfilesDirectory = cmStrCat( | |
| gt->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/CMakeFiles/d/"); | |
| auto depfilesPrefix = cmStrCat(depfilesDirectory, buildPhase->GetId(), '.'); | |
| std::string shellScript = "set -e\n"; | |
| for (std::string const& configName : this->CurrentConfigurationTypes) { | |
| cmCustomCommandGenerator ccg( | |
| cc, configName, this->CurrentLocalGenerator, true, {}, | |
| [&depfilesPrefix](std::string const& config, std::string const&) | |
| -> std::string { return cmStrCat(depfilesPrefix, config, ".d"); }); | |
| std::vector<std::string> realDepends; | |
| realDepends.reserve(ccg.GetDepends().size()); | |
| for (auto const& d : ccg.GetDepends()) { | |
| std::string dep; | |
| if (this->CurrentLocalGenerator->GetRealDependency(d, configName, dep)) { | |
| realDepends.emplace_back(std::move(dep)); | |
| } | |
| } | |
| allConfigInputs.insert(realDepends.begin(), realDepends.end()); | |
| allConfigOutputs.insert(ccg.GetByproducts().begin(), | |
| ccg.GetByproducts().end()); | |
| allConfigOutputs.insert(ccg.GetOutputs().begin(), ccg.GetOutputs().end()); | |
| shellScript = | |
| cmStrCat(shellScript, R"(if test "$CONFIGURATION" = ")", configName, | |
| "\"; then :\n", this->ConstructScript(ccg), "fi\n"); | |
| } | |
| if (!cc.GetDepfile().empty()) { | |
| buildPhase->AddAttribute( | |
| "dependencyFile", | |
| this->CreateString(cmStrCat(depfilesDirectory, buildPhase->GetId(), | |
| ".$(CONFIGURATION).d"))); | |
| // to avoid spurious errors during first build, create empty dependency | |
| // files | |
| cmSystemTools::MakeDirectory(depfilesDirectory); | |
| for (std::string const& configName : this->CurrentConfigurationTypes) { | |
| auto file = cmStrCat(depfilesPrefix, configName, ".d"); | |
| if (!cmSystemTools::FileExists(file)) { | |
| cmSystemTools::Touch(file, true); | |
| } | |
| } | |
| } | |
| buildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| buildPhase->AddAttribute("files", buildFiles); | |
| { | |
| std::string name; | |
| if (!allConfigOutputs.empty()) { | |
| name = cmStrCat("Generate ", | |
| this->RelativeToBinary(*allConfigOutputs.begin())); | |
| } else { | |
| name = sf->GetLocation().GetName(); | |
| } | |
| buildPhase->AddAttribute("name", this->CreateString(name)); | |
| } | |
| buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh")); | |
| buildPhase->AddAttribute("shellScript", this->CreateString(shellScript)); | |
| buildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0")); | |
| bool symbolic = false; | |
| { | |
| cmXCodeObject* inputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (std::string const& i : allConfigInputs) { | |
| inputPaths->AddUniqueObject(this->CreateString(i)); | |
| if (!symbolic) { | |
| if (cmSourceFile* isf = | |
| gt->GetLocalGenerator()->GetMakefile()->GetSource( | |
| i, cmSourceFileLocationKind::Known)) { | |
| symbolic = isf->GetPropertyAsBool("SYMBOLIC"); | |
| } | |
| } | |
| } | |
| buildPhase->AddAttribute("inputPaths", inputPaths); | |
| } | |
| { | |
| cmXCodeObject* outputPaths = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (std::string const& o : allConfigOutputs) { | |
| outputPaths->AddUniqueObject(this->CreateString(o)); | |
| if (!symbolic) { | |
| if (cmSourceFile* osf = | |
| gt->GetLocalGenerator()->GetMakefile()->GetSource( | |
| o, cmSourceFileLocationKind::Known)) { | |
| symbolic = osf->GetPropertyAsBool("SYMBOLIC"); | |
| } | |
| } | |
| } | |
| buildPhase->AddAttribute("outputPaths", outputPaths); | |
| } | |
| if (symbolic) { | |
| buildPhase->AddAttribute("alwaysOutOfDate", this->CreateString("1")); | |
| } | |
| return buildPhase; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase( | |
| std::string const& name, cmGeneratorTarget const* gt, | |
| std::vector<cmCustomCommand> const& commands) | |
| { | |
| if (commands.empty()) { | |
| return nullptr; | |
| } | |
| std::set<std::string> allConfigOutputs; | |
| std::string shellScript = "set -e\n"; | |
| for (std::string const& configName : this->CurrentConfigurationTypes) { | |
| shellScript = cmStrCat(shellScript, R"(if test "$CONFIGURATION" = ")", | |
| configName, "\"; then :\n"); | |
| for (cmCustomCommand const& cc : commands) { | |
| cmCustomCommandGenerator ccg(cc, configName, | |
| this->CurrentLocalGenerator); | |
| shellScript = cmStrCat(shellScript, this->ConstructScript(ccg)); | |
| allConfigOutputs.insert(ccg.GetByproducts().begin(), | |
| ccg.GetByproducts().end()); | |
| } | |
| shellScript = cmStrCat(shellScript, "fi\n"); | |
| } | |
| cmXCodeObject* buildPhase = | |
| this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase, | |
| cmStrCat(gt->GetName(), ':', name)); | |
| buildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| buildPhase->AddAttribute("files", buildFiles); | |
| buildPhase->AddAttribute("name", this->CreateString(name)); | |
| buildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| buildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh")); | |
| buildPhase->AddAttribute("shellScript", this->CreateString(shellScript)); | |
| buildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0")); | |
| { | |
| cmXCodeObject* outputPaths = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (std::string const& o : allConfigOutputs) { | |
| outputPaths->AddUniqueObject(this->CreateString(o)); | |
| } | |
| buildPhase->AddAttribute("outputPaths", outputPaths); | |
| } | |
| buildPhase->AddAttribute("alwaysOutOfDate", this->CreateString("1")); | |
| return buildPhase; | |
| } | |
| namespace { | |
| void ReplaceScriptVars(std::string& cmd) | |
| { | |
| cmSystemTools::ReplaceString(cmd, "$(CONFIGURATION)", "$CONFIGURATION"); | |
| cmSystemTools::ReplaceString(cmd, "$(EFFECTIVE_PLATFORM_NAME)", | |
| "$EFFECTIVE_PLATFORM_NAME"); | |
| } | |
| } | |
| std::string cmGlobalXCodeGenerator::ConstructScript( | |
| cmCustomCommandGenerator const& ccg) | |
| { | |
| std::string script; | |
| cmLocalGenerator* lg = this->CurrentLocalGenerator; | |
| std::string wd = ccg.GetWorkingDirectory(); | |
| if (wd.empty()) { | |
| wd = lg->GetCurrentBinaryDirectory(); | |
| } | |
| wd = lg->ConvertToOutputFormat(wd, cmOutputConverter::SHELL); | |
| ReplaceScriptVars(wd); | |
| script = cmStrCat(script, " cd ", wd, '\n'); | |
| for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) { | |
| std::string cmd = ccg.GetCommand(c); | |
| if (cmd.empty()) { | |
| continue; | |
| } | |
| cmSystemTools::ReplaceString(cmd, "/./", "/"); | |
| cmd = lg->ConvertToOutputFormat(cmd, cmOutputConverter::SHELL); | |
| ccg.AppendArguments(c, cmd); | |
| ReplaceScriptVars(cmd); | |
| script = cmStrCat(script, " ", cmd, '\n'); | |
| } | |
| return script; | |
| } | |
| // This function removes each occurrence of the flag and returns the last one | |
| // (i.e., the dominant flag in GCC) | |
| std::string cmGlobalXCodeGenerator::ExtractFlag(char const* flag, | |
| std::string& flags) | |
| { | |
| std::string retFlag; | |
| std::string::size_type lastOccurrencePos = flags.rfind(flag); | |
| bool saved = false; | |
| while (lastOccurrencePos != std::string::npos) { | |
| // increment pos, we use lastOccurrencePos to reduce search space on next | |
| // inc | |
| std::string::size_type pos = lastOccurrencePos; | |
| if (pos == 0 || flags[pos - 1] == ' ') { | |
| while (pos < flags.size() && flags[pos] != ' ') { | |
| if (!saved) { | |
| retFlag += flags[pos]; | |
| } | |
| flags[pos] = ' '; | |
| pos++; | |
| } | |
| saved = true; | |
| } | |
| // decrement lastOccurrencePos while making sure we don't loop around | |
| // and become a very large positive number since size_type is unsigned | |
| lastOccurrencePos = lastOccurrencePos == 0 ? 0 : lastOccurrencePos - 1; | |
| lastOccurrencePos = flags.rfind(flag, lastOccurrencePos); | |
| } | |
| return retFlag; | |
| } | |
| // This function removes each matching occurrence of the expression and | |
| // returns the last one (i.e., the dominant flag in GCC) | |
| std::string cmGlobalXCodeGenerator::ExtractFlagRegex(char const* exp, | |
| int matchIndex, | |
| std::string& flags) | |
| { | |
| std::string retFlag; | |
| cmsys::RegularExpression regex(exp); | |
| assert(regex.is_valid()); | |
| if (!regex.is_valid()) { | |
| return retFlag; | |
| } | |
| std::string::size_type offset = 0; | |
| while (regex.find(&flags[offset])) { | |
| std::string::size_type const startPos = offset + regex.start(matchIndex); | |
| std::string::size_type const endPos = offset + regex.end(matchIndex); | |
| std::string::size_type const size = endPos - startPos; | |
| offset = startPos + 1; | |
| retFlag.assign(flags, startPos, size); | |
| flags.replace(startPos, size, size, ' '); | |
| } | |
| return retFlag; | |
| } | |
| //---------------------------------------------------------------------------- | |
| // This function strips off Xcode attributes that do not target the current | |
| // configuration | |
| void cmGlobalXCodeGenerator::FilterConfigurationAttribute( | |
| std::string const& configName, std::string& attribute) | |
| { | |
| // Handle [variant=<config>] condition explicitly here. | |
| std::string::size_type beginVariant = attribute.find("[variant="); | |
| if (beginVariant == std::string::npos) { | |
| // There is no variant in this attribute. | |
| return; | |
| } | |
| std::string::size_type endVariant = attribute.find(']', beginVariant + 9); | |
| if (endVariant == std::string::npos) { | |
| // There is no terminating bracket. | |
| return; | |
| } | |
| // Compare the variant to the configuration. | |
| std::string variant = | |
| attribute.substr(beginVariant + 9, endVariant - beginVariant - 9); | |
| if (variant == configName) { | |
| // The variant matches the configuration so use this | |
| // attribute but drop the [variant=<config>] condition. | |
| attribute.erase(beginVariant, endVariant - beginVariant + 1); | |
| } else { | |
| // The variant does not match the configuration so | |
| // do not use this attribute. | |
| attribute.clear(); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AddCommandsToBuildPhase( | |
| cmXCodeObject* buildphase, cmGeneratorTarget* target, | |
| std::vector<cmCustomCommand> const& commands, char const* name) | |
| { | |
| std::string dir = cmStrCat( | |
| this->CurrentLocalGenerator->GetCurrentBinaryDirectory(), "/CMakeScripts"); | |
| cmSystemTools::MakeDirectory(dir); | |
| std::string makefile = | |
| cmStrCat(dir, '/', target->GetName(), '_', name, ".make"); | |
| for (auto const& currentConfig : this->CurrentConfigurationTypes) { | |
| this->CreateCustomRulesMakefile(makefile.c_str(), target, commands, | |
| currentConfig); | |
| } | |
| std::string cdir = this->CurrentLocalGenerator->GetCurrentBinaryDirectory(); | |
| cdir = cmSystemTools::ConvertToOutputPath(cdir); | |
| std::string makecmd = cmStrCat( | |
| "make -C ", cdir, " -f ", cmSystemTools::ConvertToOutputPath(makefile), | |
| "$CONFIGURATION", " OBJDIR=$(basename \"$OBJECT_FILE_DIR_normal\") all"); | |
| buildphase->AddAttribute("shellScript", this->CreateString(makecmd)); | |
| buildphase->AddAttribute("showEnvVarsInLog", this->CreateString("0")); | |
| } | |
| void cmGlobalXCodeGenerator::CreateCustomRulesMakefile( | |
| char const* makefileBasename, cmGeneratorTarget* target, | |
| std::vector<cmCustomCommand> const& commands, std::string const& configName) | |
| { | |
| std::string makefileName = cmStrCat(makefileBasename, configName); | |
| cmGeneratedFileStream makefileStream(makefileName); | |
| if (!makefileStream) { | |
| return; | |
| } | |
| makefileStream.SetCopyIfDifferent(true); | |
| makefileStream << "# Generated by CMake, DO NOT EDIT\n" | |
| "# Custom rules for " | |
| << target->GetName() << '\n'; | |
| // disable the implicit rules | |
| makefileStream << ".SUFFIXES: " | |
| "\n"; | |
| // have all depend on all outputs | |
| makefileStream << "all: "; | |
| std::map<cmCustomCommand const*, std::string> tname; | |
| int count = 0; | |
| for (auto const& command : commands) { | |
| cmCustomCommandGenerator ccg(command, configName, | |
| this->CurrentLocalGenerator); | |
| if (ccg.GetNumberOfCommands() > 0) { | |
| std::vector<std::string> const& outputs = ccg.GetOutputs(); | |
| if (!outputs.empty()) { | |
| for (auto const& output : outputs) { | |
| makefileStream << "\\\n\t" << ConvertToMakefilePath(output); | |
| } | |
| } else { | |
| std::ostringstream str; | |
| str << "_buildpart_" << count++; | |
| tname[&ccg.GetCC()] = cmStrCat(target->GetName(), str.str()); | |
| makefileStream << "\\\n\t" << tname[&ccg.GetCC()]; | |
| } | |
| } | |
| } | |
| makefileStream << "\n\n"; | |
| auto depfilesDirectory = | |
| cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), | |
| "/CMakeFiles/d/"); | |
| for (auto const& command : commands) { | |
| cmCustomCommandGenerator ccg( | |
| command, configName, this->CurrentLocalGenerator, true, {}, | |
| [this, &depfilesDirectory](std::string const& config, | |
| std::string const& file) -> std::string { | |
| return cmStrCat( | |
| depfilesDirectory, | |
| this->GetObjectId(cmXCodeObject::PBXShellScriptBuildPhase, file), | |
| '.', config, ".d"); | |
| }); | |
| auto depfile = ccg.GetInternalDepfile(); | |
| if (!depfile.empty()) { | |
| makefileStream << "include " | |
| << cmSystemTools::ConvertToOutputPath(depfile) << "\n\n"; | |
| cmSystemTools::MakeDirectory(depfilesDirectory); | |
| if (!cmSystemTools::FileExists(depfile)) { | |
| cmSystemTools::Touch(depfile, true); | |
| } | |
| } | |
| std::vector<std::string> realDepends; | |
| realDepends.reserve(ccg.GetDepends().size()); | |
| for (auto const& d : ccg.GetDepends()) { | |
| std::string dep; | |
| if (this->CurrentLocalGenerator->GetRealDependency(d, configName, dep)) { | |
| realDepends.emplace_back(std::move(dep)); | |
| } | |
| } | |
| if (ccg.GetNumberOfCommands() > 0) { | |
| makefileStream << '\n'; | |
| std::vector<std::string> const& outputs = ccg.GetOutputs(); | |
| if (!outputs.empty()) { | |
| // There is at least one output, start the rule for it | |
| char const* sep = ""; | |
| for (auto const& output : outputs) { | |
| makefileStream << sep << ConvertToMakefilePath(output); | |
| sep = " "; | |
| } | |
| makefileStream << ": "; | |
| } else { | |
| // There are no outputs. Use the generated force rule name. | |
| makefileStream << tname[&ccg.GetCC()] << ": "; | |
| } | |
| for (auto const& dep : realDepends) { | |
| makefileStream << "\\\n" << ConvertToMakefilePath(dep); | |
| } | |
| makefileStream << '\n'; | |
| if (cm::optional<std::string> comment = ccg.GetComment()) { | |
| std::string echo_cmd = | |
| cmStrCat("echo ", | |
| (this->CurrentLocalGenerator->EscapeForShell( | |
| *comment, ccg.GetCC().GetEscapeAllowMakeVars()))); | |
| makefileStream << '\t' << echo_cmd << '\n'; | |
| } | |
| // Add each command line to the set of commands. | |
| for (unsigned int c = 0; c < ccg.GetNumberOfCommands(); ++c) { | |
| // Build the command line in a single string. | |
| std::string cmd2 = ccg.GetCommand(c); | |
| cmSystemTools::ReplaceString(cmd2, "/./", "/"); | |
| cmd2 = ConvertToMakefilePath(cmd2); | |
| std::string cmd; | |
| std::string wd = ccg.GetWorkingDirectory(); | |
| if (!wd.empty()) { | |
| cmd += "cd "; | |
| cmd += ConvertToMakefilePath(wd); | |
| cmd += " && "; | |
| } | |
| cmd += cmd2; | |
| ccg.AppendArguments(c, cmd); | |
| makefileStream << '\t' << cmd << '\n'; | |
| } | |
| // Symbolic inputs are not expected to exist, so add dummy rules. | |
| for (auto const& dep : realDepends) { | |
| if (cmSourceFile* dsf = | |
| target->GetLocalGenerator()->GetMakefile()->GetSource( | |
| dep, cmSourceFileLocationKind::Known)) { | |
| if (dsf->GetPropertyAsBool("SYMBOLIC")) { | |
| makefileStream << ConvertToMakefilePath(dep) << ":\n"; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AddPositionIndependentLinkAttribute( | |
| cmGeneratorTarget* target, cmXCodeObject* buildSettings, | |
| std::string const& configName) | |
| { | |
| // For now, only EXECUTABLE is concerned | |
| if (target->GetType() != cmStateEnums::EXECUTABLE) { | |
| return; | |
| } | |
| char const* PICValue = target->GetLinkPIEProperty(configName); | |
| if (PICValue == nullptr) { | |
| // POSITION_INDEPENDENT_CODE is not set | |
| return; | |
| } | |
| buildSettings->AddAttribute( | |
| "LD_NO_PIE", this->CreateString(cmIsOn(PICValue) ? "NO" : "YES")); | |
| } | |
| void cmGlobalXCodeGenerator::CreateBuildSettings(cmGeneratorTarget* gtgt, | |
| cmXCodeObject* buildSettings, | |
| std::string const& configName) | |
| { | |
| if (!gtgt->IsInBuildSystem()) { | |
| return; | |
| } | |
| std::string defFlags; | |
| bool shared = ((gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) || | |
| (gtgt->GetType() == cmStateEnums::MODULE_LIBRARY)); | |
| bool binary = ((gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY) || | |
| (gtgt->GetType() == cmStateEnums::STATIC_LIBRARY) || | |
| (gtgt->GetType() == cmStateEnums::EXECUTABLE) || shared); | |
| // Compute the compilation flags for each language. | |
| std::set<std::string> languages; | |
| gtgt->GetLanguages(languages, configName); | |
| std::map<std::string, std::string> cflags; | |
| for (auto const& lang : languages) { | |
| std::string& flags = cflags[lang]; | |
| // Add language-specific flags. | |
| this->CurrentLocalGenerator->AddLanguageFlags( | |
| flags, gtgt, cmBuildStep::Compile, lang, configName); | |
| if (gtgt->IsIPOEnabled(lang, configName)) { | |
| this->CurrentLocalGenerator->AppendFeatureOptions(flags, lang, "IPO"); | |
| } | |
| this->CurrentLocalGenerator->AddFeatureFlags(flags, gtgt, lang, | |
| configName); | |
| this->CurrentLocalGenerator->AddVisibilityPresetFlags(flags, gtgt, lang); | |
| this->CurrentLocalGenerator->AddCompileOptions(flags, gtgt, lang, | |
| configName); | |
| } | |
| std::string llang = gtgt->GetLinkerLanguage(configName); | |
| if (binary && llang.empty()) { | |
| cmSystemTools::Error( | |
| cmStrCat("CMake can not determine linker language for target: ", | |
| gtgt->GetName())); | |
| return; | |
| } | |
| // Choose a language to use for target-wide preprocessor definitions. | |
| static char const* ppLangs[] = { "CXX", "C", "OBJCXX", "OBJC" }; | |
| std::string langForPreprocessorDefinitions; | |
| if (cm::contains(ppLangs, llang)) { | |
| langForPreprocessorDefinitions = llang; | |
| } else { | |
| for (char const* l : ppLangs) { | |
| if (languages.count(l)) { | |
| langForPreprocessorDefinitions = l; | |
| break; | |
| } | |
| } | |
| } | |
| if (gtgt->IsIPOEnabled(llang, configName)) { | |
| char const* ltoValue = | |
| this->CurrentMakefile->IsOn("_CMAKE_LTO_THIN") ? "YES_THIN" : "YES"; | |
| buildSettings->AddAttribute("LLVM_LTO", this->CreateString(ltoValue)); | |
| } | |
| // Handle PIE linker configuration | |
| this->AddPositionIndependentLinkAttribute(gtgt, buildSettings, configName); | |
| // Add define flags | |
| this->CurrentLocalGenerator->AppendFlags( | |
| defFlags, this->CurrentMakefile->GetDefineFlags()); | |
| // Add preprocessor definitions for this target and configuration. | |
| BuildObjectListOrString ppDefs(this, true); | |
| this->AppendDefines( | |
| ppDefs, "CMAKE_INTDIR=\"$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)\""); | |
| if (std::string const* exportMacro = gtgt->GetExportMacro()) { | |
| // Add the export symbol definition for shared library objects. | |
| this->AppendDefines(ppDefs, exportMacro->c_str()); | |
| } | |
| std::vector<std::string> targetDefines; | |
| if (!langForPreprocessorDefinitions.empty()) { | |
| gtgt->GetCompileDefinitions(targetDefines, configName, | |
| langForPreprocessorDefinitions); | |
| } | |
| this->AppendDefines(ppDefs, targetDefines); | |
| buildSettings->AddAttribute("GCC_PREPROCESSOR_DEFINITIONS", | |
| ppDefs.CreateList()); | |
| if (languages.count("Swift")) { | |
| // Swift uses a separate attribute for definitions. | |
| std::vector<std::string> targetSwiftDefines; | |
| gtgt->GetCompileDefinitions(targetSwiftDefines, configName, "Swift"); | |
| // Remove the '=value' parts, as Swift does not support them. | |
| std::for_each(targetSwiftDefines.begin(), targetSwiftDefines.end(), | |
| [](std::string& def) { | |
| std::string::size_type pos = def.find('='); | |
| if (pos != std::string::npos) { | |
| def.erase(pos); | |
| } | |
| }); | |
| if (this->XcodeVersion < 80) { | |
| std::string defineString; | |
| std::set<std::string> defines(targetSwiftDefines.begin(), | |
| targetSwiftDefines.end()); | |
| this->CurrentLocalGenerator->JoinDefines(defines, defineString, "Swift"); | |
| cflags["Swift"] += cmStrCat(' ', defineString); | |
| } else { | |
| BuildObjectListOrString swiftDefs(this, true); | |
| this->AppendDefines(swiftDefs, targetSwiftDefines); | |
| buildSettings->AddAttribute("SWIFT_ACTIVE_COMPILATION_CONDITIONS", | |
| swiftDefs.CreateList()); | |
| } | |
| if (cm::optional<cmSwiftCompileMode> swiftCompileMode = | |
| this->CurrentLocalGenerator->GetSwiftCompileMode(gtgt, configName)) { | |
| switch (*swiftCompileMode) { | |
| case cmSwiftCompileMode::Wholemodule: | |
| buildSettings->AddAttribute("SWIFT_COMPILATION_MODE", | |
| this->CreateString("wholemodule")); | |
| break; | |
| case cmSwiftCompileMode::Incremental: | |
| case cmSwiftCompileMode::Singlefile: | |
| break; | |
| case cmSwiftCompileMode::Unknown: | |
| this->CurrentLocalGenerator->IssueMessage( | |
| MessageType::AUTHOR_WARNING, | |
| cmStrCat("Unknown Swift_COMPILATION_MODE on target '", | |
| gtgt->GetName(), "'")); | |
| break; | |
| } | |
| } | |
| } | |
| std::string extraLinkOptionsVar; | |
| std::string extraLinkOptions; | |
| if (gtgt->GetType() == cmStateEnums::EXECUTABLE) { | |
| extraLinkOptionsVar = "CMAKE_EXE_LINKER_FLAGS"; | |
| } else if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) { | |
| extraLinkOptionsVar = "CMAKE_SHARED_LINKER_FLAGS"; | |
| } else if (gtgt->GetType() == cmStateEnums::MODULE_LIBRARY) { | |
| extraLinkOptionsVar = "CMAKE_MODULE_LINKER_FLAGS"; | |
| } | |
| if (!extraLinkOptionsVar.empty()) { | |
| this->CurrentLocalGenerator->AddConfigVariableFlags( | |
| extraLinkOptions, extraLinkOptionsVar, gtgt, cmBuildStep::Link, llang, | |
| configName); | |
| } | |
| if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY || | |
| gtgt->GetType() == cmStateEnums::STATIC_LIBRARY) { | |
| this->CurrentLocalGenerator->GetStaticLibraryFlags( | |
| extraLinkOptions, configName, llang, gtgt); | |
| } else { | |
| this->CurrentLocalGenerator->AppendLinkerTypeFlags(extraLinkOptions, gtgt, | |
| configName, llang); | |
| this->CurrentLocalGenerator->AppendWarningAsErrorLinkerFlags( | |
| extraLinkOptions, gtgt, llang); | |
| cmValue targetLinkFlags = gtgt->GetProperty("LINK_FLAGS"); | |
| if (targetLinkFlags) { | |
| this->CurrentLocalGenerator->AppendFlags(extraLinkOptions, | |
| *targetLinkFlags); | |
| } | |
| if (!configName.empty()) { | |
| std::string linkFlagsVar = | |
| cmStrCat("LINK_FLAGS_", cmSystemTools::UpperCase(configName)); | |
| if (cmValue linkFlags = gtgt->GetProperty(linkFlagsVar)) { | |
| this->CurrentLocalGenerator->AppendFlags(extraLinkOptions, *linkFlags); | |
| } | |
| } | |
| std::vector<std::string> opts; | |
| gtgt->GetLinkOptions(opts, configName, llang); | |
| // LINK_OPTIONS are escaped. | |
| this->CurrentLocalGenerator->AppendCompileOptions(extraLinkOptions, opts); | |
| } | |
| // Set target-specific architectures. | |
| std::vector<std::string> archs = | |
| gtgt->GetAppleArchs(configName, cm::nullopt); | |
| if (!archs.empty()) { | |
| // Enable ARCHS attribute. | |
| buildSettings->AddAttribute("ONLY_ACTIVE_ARCH", this->CreateString("NO")); | |
| // Store ARCHS value. | |
| if (archs.size() == 1) { | |
| buildSettings->AddAttribute("ARCHS", this->CreateString(archs[0])); | |
| } else { | |
| cmXCodeObject* archObjects = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (auto& arch : archs) { | |
| archObjects->AddObject(this->CreateString(arch)); | |
| } | |
| buildSettings->AddAttribute("ARCHS", archObjects); | |
| } | |
| } | |
| // Get the product name components. | |
| cmGeneratorTarget::NameComponents const& components = | |
| gtgt->GetFullNameComponents(configName); | |
| cmValue version = gtgt->GetProperty("VERSION"); | |
| cmValue soversion = gtgt->GetProperty("SOVERSION"); | |
| if (!gtgt->HasSOName(configName) || gtgt->IsFrameworkOnApple()) { | |
| version = nullptr; | |
| soversion = nullptr; | |
| } | |
| if (version && !soversion) { | |
| soversion = version; | |
| } | |
| if (!version && soversion) { | |
| version = soversion; | |
| } | |
| std::string realName = components.base; | |
| std::string soName = components.base; | |
| if (version && soversion) { | |
| realName += '.'; | |
| realName += *version; | |
| soName += '.'; | |
| soName += *soversion; | |
| } | |
| if (gtgt->CanCompileSources()) { | |
| std::string const tmpDir = | |
| this->GetTargetTempDir(gtgt, this->GetCMakeCFGIntDir()); | |
| buildSettings->AddAttribute("TARGET_TEMP_DIR", this->CreateString(tmpDir)); | |
| std::string outDir; | |
| if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY) { | |
| // We cannot suppress the archive, so hide it with intermediate files. | |
| outDir = tmpDir; | |
| } else { | |
| outDir = gtgt->GetDirectory(configName); | |
| } | |
| buildSettings->AddAttribute("CONFIGURATION_BUILD_DIR", | |
| this->CreateString(outDir)); | |
| } | |
| // Set attributes to specify the proper name for the target. | |
| std::string pndir = this->CurrentLocalGenerator->GetCurrentBinaryDirectory(); | |
| if (gtgt->GetType() == cmStateEnums::STATIC_LIBRARY || | |
| gtgt->GetType() == cmStateEnums::SHARED_LIBRARY || | |
| gtgt->GetType() == cmStateEnums::MODULE_LIBRARY || | |
| gtgt->GetType() == cmStateEnums::EXECUTABLE) { | |
| std::string prefix = components.prefix; | |
| if (gtgt->IsFrameworkOnApple() || gtgt->IsCFBundleOnApple()) { | |
| prefix = ""; | |
| } | |
| buildSettings->AddAttribute("EXECUTABLE_PREFIX", | |
| this->CreateString(prefix)); | |
| buildSettings->AddAttribute("EXECUTABLE_SUFFIX", | |
| this->CreateString(components.suffix)); | |
| } | |
| // Store the product name for all target types. | |
| buildSettings->AddAttribute("PRODUCT_NAME", this->CreateString(realName)); | |
| // Handle settings for each target type. | |
| switch (gtgt->GetType()) { | |
| case cmStateEnums::STATIC_LIBRARY: | |
| if (gtgt->GetPropertyAsBool("FRAMEWORK")) { | |
| std::string fw_version = gtgt->GetFrameworkVersion(); | |
| buildSettings->AddAttribute("FRAMEWORK_VERSION", | |
| this->CreateString(fw_version)); | |
| cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION"); | |
| if (ext) { | |
| buildSettings->AddAttribute("WRAPPER_EXTENSION", | |
| this->CreateString(*ext)); | |
| } | |
| std::string plist = this->ComputeInfoPListLocation(gtgt); | |
| // Xcode will create the final version of Info.plist at build time, | |
| // so let it replace the framework name. This avoids creating | |
| // a per-configuration Info.plist file. | |
| this->CurrentLocalGenerator->GenerateFrameworkInfoPList( | |
| gtgt, "$(EXECUTABLE_NAME)", plist); | |
| buildSettings->AddAttribute("INFOPLIST_FILE", | |
| this->CreateString(plist)); | |
| buildSettings->AddAttribute("MACH_O_TYPE", | |
| this->CreateString("staticlib")); | |
| } else { | |
| buildSettings->AddAttribute("LIBRARY_STYLE", | |
| this->CreateString("STATIC")); | |
| } | |
| break; | |
| case cmStateEnums::OBJECT_LIBRARY: { | |
| buildSettings->AddAttribute("LIBRARY_STYLE", | |
| this->CreateString("STATIC")); | |
| break; | |
| } | |
| case cmStateEnums::MODULE_LIBRARY: { | |
| buildSettings->AddAttribute("LIBRARY_STYLE", | |
| this->CreateString("BUNDLE")); | |
| if (gtgt->IsCFBundleOnApple()) { | |
| // It turns out that a BUNDLE is basically the same | |
| // in many ways as an application bundle, as far as | |
| // link flags go | |
| std::string createFlags = this->LookupFlags( | |
| "CMAKE_SHARED_MODULE_CREATE_", llang, "_FLAGS", gtgt, "-bundle"); | |
| if (!createFlags.empty()) { | |
| extraLinkOptions += ' '; | |
| extraLinkOptions += createFlags; | |
| } | |
| cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION"); | |
| if (ext) { | |
| buildSettings->AddAttribute("WRAPPER_EXTENSION", | |
| this->CreateString(*ext)); | |
| } | |
| std::string plist = this->ComputeInfoPListLocation(gtgt); | |
| // Xcode will create the final version of Info.plist at build time, | |
| // so let it replace the cfbundle name. This avoids creating | |
| // a per-configuration Info.plist file. The cfbundle plist | |
| // is very similar to the application bundle plist | |
| this->CurrentLocalGenerator->GenerateAppleInfoPList( | |
| gtgt, "$(EXECUTABLE_NAME)", plist); | |
| buildSettings->AddAttribute("INFOPLIST_FILE", | |
| this->CreateString(plist)); | |
| } else { | |
| buildSettings->AddAttribute("MACH_O_TYPE", | |
| this->CreateString("mh_bundle")); | |
| buildSettings->AddAttribute("GCC_DYNAMIC_NO_PIC", | |
| this->CreateString("NO")); | |
| // Add the flags to create an executable. | |
| std::string createFlags = | |
| this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", gtgt, ""); | |
| if (!createFlags.empty()) { | |
| extraLinkOptions += ' '; | |
| extraLinkOptions += createFlags; | |
| } | |
| } | |
| break; | |
| } | |
| case cmStateEnums::SHARED_LIBRARY: { | |
| if (gtgt->GetPropertyAsBool("FRAMEWORK")) { | |
| std::string fw_version = gtgt->GetFrameworkVersion(); | |
| buildSettings->AddAttribute("FRAMEWORK_VERSION", | |
| this->CreateString(fw_version)); | |
| cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION"); | |
| if (ext) { | |
| buildSettings->AddAttribute("WRAPPER_EXTENSION", | |
| this->CreateString(*ext)); | |
| } | |
| std::string plist = this->ComputeInfoPListLocation(gtgt); | |
| // Xcode will create the final version of Info.plist at build time, | |
| // so let it replace the framework name. This avoids creating | |
| // a per-configuration Info.plist file. | |
| this->CurrentLocalGenerator->GenerateFrameworkInfoPList( | |
| gtgt, "$(EXECUTABLE_NAME)", plist); | |
| buildSettings->AddAttribute("INFOPLIST_FILE", | |
| this->CreateString(plist)); | |
| } else { | |
| // Add the flags to create a shared library. | |
| std::string createFlags = | |
| this->LookupFlags("CMAKE_SHARED_LIBRARY_CREATE_", llang, "_FLAGS", | |
| gtgt, "-dynamiclib"); | |
| if (!createFlags.empty()) { | |
| extraLinkOptions += ' '; | |
| extraLinkOptions += createFlags; | |
| } | |
| } | |
| buildSettings->AddAttribute("LIBRARY_STYLE", | |
| this->CreateString("DYNAMIC")); | |
| if (gtgt->HasImportLibrary(configName)) { | |
| // Request .tbd file generation | |
| buildSettings->AddAttribute("GENERATE_TEXT_BASED_STUBS", | |
| this->CreateString("YES")); | |
| } | |
| break; | |
| } | |
| case cmStateEnums::EXECUTABLE: { | |
| // Add the flags to create an executable. | |
| std::string createFlags = | |
| this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", gtgt, ""); | |
| if (!createFlags.empty()) { | |
| extraLinkOptions += ' '; | |
| extraLinkOptions += createFlags; | |
| } | |
| // Handle bundles and normal executables separately. | |
| if (gtgt->GetPropertyAsBool("MACOSX_BUNDLE")) { | |
| cmValue ext = gtgt->GetProperty("BUNDLE_EXTENSION"); | |
| if (ext) { | |
| buildSettings->AddAttribute("WRAPPER_EXTENSION", | |
| this->CreateString(*ext)); | |
| } | |
| std::string plist = this->ComputeInfoPListLocation(gtgt); | |
| // Xcode will create the final version of Info.plist at build time, | |
| // so let it replace the executable name. This avoids creating | |
| // a per-configuration Info.plist file. | |
| this->CurrentLocalGenerator->GenerateAppleInfoPList( | |
| gtgt, "$(EXECUTABLE_NAME)", plist); | |
| buildSettings->AddAttribute("INFOPLIST_FILE", | |
| this->CreateString(plist)); | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| BuildObjectListOrString dirs(this, true); | |
| BuildObjectListOrString fdirs(this, true); | |
| BuildObjectListOrString sysdirs(this, true); | |
| BuildObjectListOrString sysfdirs(this, true); | |
| bool const emitSystemIncludes = this->XcodeVersion >= 83; | |
| // Choose a language to use for target-wide include directories. | |
| std::string const& langForIncludes = llang; | |
| std::vector<std::string> includes; | |
| if (!langForIncludes.empty()) { | |
| this->CurrentLocalGenerator->GetIncludeDirectories( | |
| includes, gtgt, langForIncludes, configName); | |
| } | |
| std::set<std::string> emitted; | |
| emitted.insert("/System/Library/Frameworks"); | |
| for (auto& include : includes) { | |
| if (this->NameResolvesToFramework(include)) { | |
| std::string frameworkDir = cmStrCat(include, "/../"); | |
| frameworkDir = cmSystemTools::CollapseFullPath(frameworkDir); | |
| if (emitted.insert(frameworkDir).second) { | |
| std::string incpath = this->XCodeEscapePath(frameworkDir); | |
| if (emitSystemIncludes && | |
| gtgt->IsSystemIncludeDirectory(frameworkDir, configName, | |
| langForIncludes)) { | |
| sysfdirs.Add(incpath); | |
| } else { | |
| fdirs.Add(incpath); | |
| } | |
| } | |
| } else { | |
| std::string incpath = this->XCodeEscapePath(include); | |
| if (emitSystemIncludes && | |
| gtgt->IsSystemIncludeDirectory(include, configName, | |
| langForIncludes)) { | |
| sysdirs.Add(incpath); | |
| } else { | |
| dirs.Add(incpath); | |
| } | |
| } | |
| } | |
| // Add framework search paths needed for linking. | |
| if (cmComputeLinkInformation* cli = gtgt->GetLinkInformation(configName)) { | |
| for (auto const& fwDir : cli->GetFrameworkPaths()) { | |
| if (emitted.insert(fwDir).second) { | |
| std::string incpath = this->XCodeEscapePath(fwDir); | |
| if (emitSystemIncludes && | |
| gtgt->IsSystemIncludeDirectory(fwDir, configName, | |
| langForIncludes)) { | |
| sysfdirs.Add(incpath); | |
| } else { | |
| fdirs.Add(incpath); | |
| } | |
| } | |
| } | |
| } | |
| if (!fdirs.IsEmpty()) { | |
| buildSettings->AddAttribute("FRAMEWORK_SEARCH_PATHS", fdirs.CreateList()); | |
| } | |
| if (!dirs.IsEmpty()) { | |
| buildSettings->AddAttribute("HEADER_SEARCH_PATHS", dirs.CreateList()); | |
| if (languages.count("Swift")) { | |
| buildSettings->AddAttribute("SWIFT_INCLUDE_PATHS", dirs.CreateList()); | |
| } | |
| } | |
| if (!sysfdirs.IsEmpty()) { | |
| buildSettings->AddAttribute("SYSTEM_FRAMEWORK_SEARCH_PATHS", | |
| sysfdirs.CreateList()); | |
| } | |
| if (!sysdirs.IsEmpty()) { | |
| buildSettings->AddAttribute("SYSTEM_HEADER_SEARCH_PATHS", | |
| sysdirs.CreateList()); | |
| } | |
| if (this->XcodeVersion >= 60 && !emitSystemIncludes) { | |
| // Add those per-language flags in addition to HEADER_SEARCH_PATHS to gain | |
| // system include directory awareness. We need to also keep on setting | |
| // HEADER_SEARCH_PATHS to work around a missing compile options flag for | |
| // GNU assembly files (#16449) | |
| for (auto const& language : languages) { | |
| std::string includeFlags = this->CurrentLocalGenerator->GetIncludeFlags( | |
| includes, gtgt, language, configName); | |
| if (!includeFlags.empty()) { | |
| cflags[language] += cmStrCat(' ', includeFlags); | |
| } | |
| } | |
| } | |
| bool same_gflags = true; | |
| std::map<std::string, std::string> gflags; | |
| std::string const* last_gflag = nullptr; | |
| std::string optLevel = "0"; | |
| // Minimal map of flags to build settings. | |
| for (auto const& language : languages) { | |
| std::string& flags = cflags[language]; | |
| std::string& gflag = gflags[language]; | |
| std::string oflag = | |
| this->ExtractFlagRegex("(^| )(-Ofast|-Os|-O[0-9]*)( |$)", 2, flags); | |
| if (oflag.size() == 2) { | |
| optLevel = "1"; | |
| } else if (oflag.size() > 2) { | |
| optLevel = oflag.substr(2); | |
| } | |
| gflag = this->ExtractFlag("-g", flags); | |
| // put back gdwarf-2 if used since there is no way | |
| // to represent it in the gui, but we still want debug yes | |
| if (gflag == "-gdwarf-2"_s) { | |
| flags += ' '; | |
| flags += gflag; | |
| } | |
| if (last_gflag && *last_gflag != gflag) { | |
| same_gflags = false; | |
| } | |
| last_gflag = &gflag; | |
| } | |
| char const* debugStr = "YES"; | |
| if (!same_gflags) { | |
| // We can't set the Xcode flag differently depending on the language, | |
| // so put them back in this case. | |
| for (auto const& language : languages) { | |
| cflags[language] += ' '; | |
| cflags[language] += gflags[language]; | |
| } | |
| debugStr = "NO"; | |
| } else if (last_gflag && (last_gflag->empty() || *last_gflag == "-g0"_s)) { | |
| debugStr = "NO"; | |
| } | |
| // extract C++ stdlib | |
| for (auto const& language : languages) { | |
| if (language != "CXX"_s && language != "OBJCXX"_s) { | |
| continue; | |
| } | |
| std::string& flags = cflags[language]; | |
| auto stdlib = | |
| this->ExtractFlagRegex("(^| )(-stdlib=[^ ]+)( |$)", 2, flags); | |
| if (stdlib.size() > 8) { | |
| auto const cxxLibrary = stdlib.substr(8); | |
| if (language == "CXX"_s || | |
| !buildSettings->GetAttribute("CLANG_CXX_LIBRARY")) { | |
| buildSettings->AddAttribute("CLANG_CXX_LIBRARY", | |
| this->CreateString(cxxLibrary)); | |
| } | |
| } | |
| } | |
| buildSettings->AddAttribute("COMBINE_HIDPI_IMAGES", | |
| this->CreateString("YES")); | |
| buildSettings->AddAttribute("GCC_GENERATE_DEBUGGING_SYMBOLS", | |
| this->CreateString(debugStr)); | |
| buildSettings->AddAttribute("GCC_OPTIMIZATION_LEVEL", | |
| this->CreateString(optLevel)); | |
| buildSettings->AddAttribute("GCC_SYMBOLS_PRIVATE_EXTERN", | |
| this->CreateString("NO")); | |
| buildSettings->AddAttribute("GCC_INLINES_ARE_PRIVATE_EXTERN", | |
| this->CreateString("NO")); | |
| for (auto const& language : languages) { | |
| std::string flags = cmStrCat(cflags[language], ' ', defFlags); | |
| if (language == "CXX"_s || language == "OBJCXX"_s) { | |
| if (language == "CXX"_s || | |
| !buildSettings->GetAttribute("OTHER_CPLUSPLUSFLAGS")) { | |
| buildSettings->AddAttribute("OTHER_CPLUSPLUSFLAGS", | |
| this->CreateString(flags)); | |
| } | |
| } else if (language == "Fortran"_s) { | |
| buildSettings->AddAttribute("IFORT_OTHER_FLAGS", | |
| this->CreateString(flags)); | |
| } else if (language == "C"_s || language == "OBJC"_s) { | |
| if (language == "C"_s || !buildSettings->GetAttribute("OTHER_CFLAGS")) { | |
| buildSettings->AddAttribute("OTHER_CFLAGS", this->CreateString(flags)); | |
| } | |
| } else if (language == "Swift"_s) { | |
| buildSettings->AddAttribute("OTHER_SWIFT_FLAGS", | |
| this->CreateString(flags)); | |
| } | |
| } | |
| // Add Fortran source format attribute if property is set. | |
| char const* format = nullptr; | |
| std::string const& tgtfmt = gtgt->GetSafeProperty("Fortran_FORMAT"); | |
| switch (cmOutputConverter::GetFortranFormat(tgtfmt)) { | |
| case cmOutputConverter::FortranFormatFixed: | |
| format = "fixed"; | |
| break; | |
| case cmOutputConverter::FortranFormatFree: | |
| format = "free"; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (format) { | |
| buildSettings->AddAttribute("IFORT_LANG_SRCFMT", | |
| this->CreateString(format)); | |
| } | |
| // Create the INSTALL_PATH attribute. | |
| std::string install_name_dir; | |
| if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) { | |
| // Get the install_name directory for the build tree. | |
| install_name_dir = gtgt->GetInstallNameDirForBuildTree(configName); | |
| // Xcode doesn't create the correct install_name in some cases. | |
| // That is, if the INSTALL_PATH is empty, or if we have versioning | |
| // of dylib libraries, we want to specify the install_name. | |
| // This is done by adding a link flag to create an install_name | |
| // with just the library soname. | |
| std::string install_name; | |
| if (!install_name_dir.empty()) { | |
| // Convert to a path for the native build tool. | |
| cmSystemTools::ConvertToUnixSlashes(install_name_dir); | |
| install_name += install_name_dir; | |
| install_name += '/'; | |
| } | |
| install_name += gtgt->GetSOName(configName); | |
| if ((realName != soName) || install_name_dir.empty()) { | |
| install_name_dir = ""; | |
| extraLinkOptions += " -install_name "; | |
| extraLinkOptions += XCodeEscapePath(install_name); | |
| } | |
| } | |
| buildSettings->AddAttribute("INSTALL_PATH", | |
| this->CreateString(install_name_dir)); | |
| // Create the LD_RUNPATH_SEARCH_PATHS | |
| cmComputeLinkInformation* pcli = gtgt->GetLinkInformation(configName); | |
| if (pcli) { | |
| std::string search_paths; | |
| std::vector<std::string> runtimeDirs; | |
| pcli->GetRPath(runtimeDirs, false); | |
| // runpath dirs needs to be unique to prevent corruption | |
| std::set<std::string> unique_dirs; | |
| for (auto runpath : runtimeDirs) { | |
| runpath = this->ExpandCFGIntDir(runpath, configName); | |
| if (unique_dirs.find(runpath) == unique_dirs.end()) { | |
| unique_dirs.insert(runpath); | |
| if (!search_paths.empty()) { | |
| search_paths += ' '; | |
| } | |
| search_paths += this->XCodeEscapePath(runpath); | |
| } | |
| } | |
| if (!search_paths.empty()) { | |
| buildSettings->AddAttribute("LD_RUNPATH_SEARCH_PATHS", | |
| this->CreateString(search_paths)); | |
| } | |
| } | |
| buildSettings->AddAttribute(this->GetTargetLinkFlagsVar(gtgt), | |
| this->CreateString(extraLinkOptions)); | |
| buildSettings->AddAttribute("OTHER_REZFLAGS", this->CreateString("")); | |
| buildSettings->AddAttribute("SECTORDER_FLAGS", this->CreateString("")); | |
| buildSettings->AddAttribute("ALWAYS_SEARCH_USER_PATHS", | |
| this->CreateString("NO")); | |
| buildSettings->AddAttribute("USE_HEADERMAP", this->CreateString("NO")); | |
| cmXCodeObject* group = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| group->AddObject(this->CreateString("$(inherited)")); | |
| buildSettings->AddAttribute("WARNING_CFLAGS", group); | |
| // Runtime version information. | |
| if (gtgt->GetType() == cmStateEnums::SHARED_LIBRARY) { | |
| int major; | |
| int minor; | |
| int patch; | |
| // MACHO_CURRENT_VERSION or VERSION -> current_version | |
| gtgt->GetTargetVersionFallback("MACHO_CURRENT_VERSION", "VERSION", major, | |
| minor, patch); | |
| std::ostringstream v; | |
| // Xcode always wants at least 1.0.0 or nothing | |
| if (!(major == 0 && minor == 0 && patch == 0)) { | |
| v << major << '.' << minor << '.' << patch; | |
| } | |
| buildSettings->AddAttribute("DYLIB_CURRENT_VERSION", | |
| this->CreateString(v.str())); | |
| // MACHO_COMPATIBILITY_VERSION or SOVERSION -> compatibility_version | |
| gtgt->GetTargetVersionFallback("MACHO_COMPATIBILITY_VERSION", "SOVERSION", | |
| major, minor, patch); | |
| std::ostringstream vso; | |
| // Xcode always wants at least 1.0.0 or nothing | |
| if (!(major == 0 && minor == 0 && patch == 0)) { | |
| vso << major << '.' << minor << '.' << patch; | |
| } | |
| buildSettings->AddAttribute("DYLIB_COMPATIBILITY_VERSION", | |
| this->CreateString(vso.str())); | |
| } | |
| // Precompile Headers | |
| std::string pchHeader = | |
| gtgt->GetPchHeader(configName, langForPreprocessorDefinitions); | |
| if (!pchHeader.empty()) { | |
| buildSettings->AddAttribute("GCC_PREFIX_HEADER", | |
| this->CreateString(pchHeader)); | |
| buildSettings->AddAttribute("GCC_PRECOMPILE_PREFIX_HEADER", | |
| this->CreateString("YES")); | |
| } | |
| // put this last so it can override existing settings | |
| // Convert "XCODE_ATTRIBUTE_*" properties directly. | |
| { | |
| for (auto const& prop : gtgt->GetPropertyKeys()) { | |
| if (cmHasLiteralPrefix(prop, "XCODE_ATTRIBUTE_")) { | |
| std::string attribute = prop.substr(16); | |
| this->FilterConfigurationAttribute(configName, attribute); | |
| if (!attribute.empty()) { | |
| std::string const& pr = gtgt->GetSafeProperty(prop); | |
| std::string processed = cmGeneratorExpression::Evaluate( | |
| pr, this->CurrentLocalGenerator, configName); | |
| buildSettings->AddAttribute(attribute, | |
| this->CreateString(processed)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateUtilityTarget( | |
| cmGeneratorTarget* gtgt) | |
| { | |
| cmXCodeObject* shellBuildPhase = this->CreateObject( | |
| cmXCodeObject::PBXShellScriptBuildPhase, gtgt->GetName()); | |
| shellBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| shellBuildPhase->AddAttribute("files", buildFiles); | |
| cmXCodeObject* inputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| shellBuildPhase->AddAttribute("inputPaths", inputPaths); | |
| cmXCodeObject* outputPaths = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| shellBuildPhase->AddAttribute("outputPaths", outputPaths); | |
| shellBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| shellBuildPhase->AddAttribute("shellPath", this->CreateString("/bin/sh")); | |
| shellBuildPhase->AddAttribute( | |
| "shellScript", this->CreateString("# shell script goes here\nexit 0")); | |
| shellBuildPhase->AddAttribute("showEnvVarsInLog", this->CreateString("0")); | |
| std::string targetBinaryPath = cmStrCat( | |
| gtgt->Makefile->GetCurrentBinaryDirectory(), '/', gtgt->GetName()); | |
| cmXCodeObject* target = this->CreateObject( | |
| cmXCodeObject::PBXAggregateTarget, | |
| cmStrCat("PBXAggregateTarget:", gtgt->GetName(), ":", targetBinaryPath)); | |
| target->SetComment(gtgt->GetName()); | |
| cmXCodeObject* buildPhases = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| std::vector<cmXCodeObject*> emptyContentVector; | |
| this->CreateCustomCommands(buildPhases, nullptr, nullptr, nullptr, | |
| emptyContentVector, nullptr, gtgt); | |
| target->AddAttribute("buildPhases", buildPhases); | |
| this->AddConfigurations(target, gtgt); | |
| cmXCodeObject* dependencies = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| target->AddAttribute("dependencies", dependencies); | |
| target->AddAttribute("name", this->CreateString(gtgt->GetName())); | |
| target->AddAttribute("productName", this->CreateString(gtgt->GetName())); | |
| target->SetTarget(gtgt); | |
| this->XCodeObjectMap[gtgt] = target; | |
| // Add source files without build rules for editing convenience. | |
| if (gtgt->GetType() != cmStateEnums::GLOBAL_TARGET && | |
| gtgt->GetName() != CMAKE_CHECK_BUILD_SYSTEM_TARGET) { | |
| std::vector<cmSourceFile*> sources; | |
| if (!gtgt->GetConfigCommonSourceFilesForXcode(sources)) { | |
| return nullptr; | |
| } | |
| // Add CMakeLists.txt file for user convenience. | |
| this->AddXCodeProjBuildRule(gtgt, sources); | |
| for (auto* sourceFile : sources) { | |
| if (!sourceFile->GetIsGenerated()) { | |
| this->CreateXCodeFileReference(sourceFile, gtgt); | |
| } | |
| } | |
| } | |
| target->SetId(this->GetOrCreateId(gtgt->GetName(), target->GetId())); | |
| return target; | |
| } | |
| std::string cmGlobalXCodeGenerator::AddConfigurations(cmXCodeObject* target, | |
| cmGeneratorTarget* gtgt) | |
| { | |
| cmList const configList{ this->CurrentMakefile->GetRequiredDefinition( | |
| "CMAKE_CONFIGURATION_TYPES") }; | |
| cmXCodeObject* configlist = | |
| this->CreateObject(cmXCodeObject::XCConfigurationList); | |
| cmXCodeObject* buildConfigurations = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| configlist->AddAttribute("buildConfigurations", buildConfigurations); | |
| std::string comment = cmStrCat("Build configuration list for ", | |
| cmXCodeObject::PBXTypeNames[target->GetIsA()], | |
| " \"", gtgt->GetName(), '"'); | |
| configlist->SetComment(comment); | |
| target->AddAttribute("buildConfigurationList", | |
| this->CreateObjectReference(configlist)); | |
| for (auto const& i : configList) { | |
| cmXCodeObject* config = | |
| this->CreateObject(cmXCodeObject::XCBuildConfiguration); | |
| buildConfigurations->AddObject(config); | |
| cmXCodeObject* buildSettings = | |
| this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| this->CreateBuildSettings(gtgt, buildSettings, i); | |
| config->AddAttribute("name", this->CreateString(i)); | |
| config->SetComment(i); | |
| config->AddAttribute("buildSettings", buildSettings); | |
| this->CreateTargetXCConfigSettings(gtgt, config, i); | |
| } | |
| if (!configList.empty()) { | |
| configlist->AddAttribute("defaultConfigurationName", | |
| this->CreateString(configList[0])); | |
| configlist->AddAttribute("defaultConfigurationIsVisible", | |
| this->CreateString("0")); | |
| return configList[0]; | |
| } | |
| return ""; | |
| } | |
| void cmGlobalXCodeGenerator::CreateGlobalXCConfigSettings( | |
| cmLocalGenerator* root, cmXCodeObject* config, std::string const& configName) | |
| { | |
| auto xcconfig = cmGeneratorExpression::Evaluate( | |
| this->CurrentMakefile->GetSafeDefinition("CMAKE_XCODE_XCCONFIG"), | |
| this->CurrentLocalGenerator, configName); | |
| if (xcconfig.empty()) { | |
| return; | |
| } | |
| auto* sf = this->CurrentMakefile->GetSource(xcconfig); | |
| if (!sf) { | |
| cmSystemTools::Error( | |
| cmStrCat("sources for ALL_BUILD do not contain xcconfig file: '", | |
| xcconfig, "' (configuration: ", configName, ')')); | |
| return; | |
| } | |
| cmXCodeObject* fileRef = this->CreateXCodeFileReferenceFromPath( | |
| sf->ResolveFullPath(), root->FindGeneratorTargetToUse("ALL_BUILD"), "", | |
| sf); | |
| if (!fileRef) { | |
| // error is already reported by CreateXCodeFileReferenceFromPath | |
| return; | |
| } | |
| config->AddAttribute("baseConfigurationReference", | |
| this->CreateObjectReference(fileRef)); | |
| } | |
| void cmGlobalXCodeGenerator::CreateTargetXCConfigSettings( | |
| cmGeneratorTarget* target, cmXCodeObject* config, | |
| std::string const& configName) | |
| { | |
| auto xcconfig = | |
| cmGeneratorExpression::Evaluate(target->GetSafeProperty("XCODE_XCCONFIG"), | |
| this->CurrentLocalGenerator, configName); | |
| if (xcconfig.empty()) { | |
| return; | |
| } | |
| auto* sf = target->Makefile->GetSource(xcconfig); | |
| if (!sf) { | |
| cmSystemTools::Error(cmStrCat("target sources for target ", | |
| target->Target->GetName(), | |
| " do not contain xcconfig file: '", xcconfig, | |
| "' (configuration: ", configName, ')')); | |
| return; | |
| } | |
| cmXCodeObject* fileRef = this->CreateXCodeFileReferenceFromPath( | |
| sf->ResolveFullPath(), target, "", sf); | |
| if (!fileRef) { | |
| // error is already reported by CreateXCodeFileReferenceFromPath | |
| return; | |
| } | |
| config->AddAttribute("baseConfigurationReference", | |
| this->CreateObjectReference(fileRef)); | |
| } | |
| char const* cmGlobalXCodeGenerator::GetTargetLinkFlagsVar( | |
| cmGeneratorTarget const* target) const | |
| { | |
| if (this->XcodeVersion >= 60 && | |
| (target->GetType() == cmStateEnums::STATIC_LIBRARY || | |
| target->GetType() == cmStateEnums::OBJECT_LIBRARY)) { | |
| return "OTHER_LIBTOOLFLAGS"; | |
| } | |
| return "OTHER_LDFLAGS"; | |
| } | |
| char const* cmGlobalXCodeGenerator::GetTargetFileType( | |
| cmGeneratorTarget* target) | |
| { | |
| if (cmValue e = target->GetProperty("XCODE_EXPLICIT_FILE_TYPE")) { | |
| return e->c_str(); | |
| } | |
| switch (target->GetType()) { | |
| case cmStateEnums::OBJECT_LIBRARY: | |
| return "archive.ar"; | |
| case cmStateEnums::STATIC_LIBRARY: | |
| return (target->GetPropertyAsBool("FRAMEWORK") ? "wrapper.framework" | |
| : "archive.ar"); | |
| case cmStateEnums::MODULE_LIBRARY: | |
| if (target->IsXCTestOnApple()) { | |
| return "wrapper.cfbundle"; | |
| } | |
| if (target->IsCFBundleOnApple()) { | |
| return "wrapper.plug-in"; | |
| } | |
| return "compiled.mach-o.executable"; | |
| case cmStateEnums::SHARED_LIBRARY: | |
| return (target->GetPropertyAsBool("FRAMEWORK") | |
| ? "wrapper.framework" | |
| : "compiled.mach-o.dylib"); | |
| case cmStateEnums::EXECUTABLE: | |
| return "compiled.mach-o.executable"; | |
| default: | |
| break; | |
| } | |
| return nullptr; | |
| } | |
| char const* cmGlobalXCodeGenerator::GetTargetProductType( | |
| cmGeneratorTarget* target) | |
| { | |
| if (cmValue e = target->GetProperty("XCODE_PRODUCT_TYPE")) { | |
| return e->c_str(); | |
| } | |
| switch (target->GetType()) { | |
| case cmStateEnums::OBJECT_LIBRARY: | |
| return "com.apple.product-type.library.static"; | |
| case cmStateEnums::STATIC_LIBRARY: | |
| return (target->GetPropertyAsBool("FRAMEWORK") | |
| ? "com.apple.product-type.framework" | |
| : "com.apple.product-type.library.static"); | |
| case cmStateEnums::MODULE_LIBRARY: | |
| if (target->IsXCTestOnApple()) { | |
| return "com.apple.product-type.bundle.unit-test"; | |
| } else if (target->IsCFBundleOnApple()) { | |
| return "com.apple.product-type.bundle"; | |
| } else { | |
| return "com.apple.product-type.tool"; | |
| } | |
| case cmStateEnums::SHARED_LIBRARY: | |
| return (target->GetPropertyAsBool("FRAMEWORK") | |
| ? "com.apple.product-type.framework" | |
| : "com.apple.product-type.library.dynamic"); | |
| case cmStateEnums::EXECUTABLE: | |
| return (target->GetPropertyAsBool("MACOSX_BUNDLE") | |
| ? "com.apple.product-type.application" | |
| : "com.apple.product-type.tool"); | |
| default: | |
| break; | |
| } | |
| return nullptr; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateXCodeTarget( | |
| cmGeneratorTarget* gtgt, cmXCodeObject* buildPhases) | |
| { | |
| if (!gtgt->IsInBuildSystem()) { | |
| return nullptr; | |
| } | |
| std::string targetBinaryPath = this->RelativeToRootBinary(cmStrCat( | |
| gtgt->Makefile->GetCurrentBinaryDirectory(), '/', gtgt->GetName())); | |
| cmXCodeObject* target = this->CreateObject( | |
| cmXCodeObject::PBXNativeTarget, | |
| cmStrCat("PBXNativeTarget:", gtgt->GetName(), ":", targetBinaryPath)); | |
| target->AddAttribute("buildPhases", buildPhases); | |
| cmXCodeObject* buildRules = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| target->AddAttribute("buildRules", buildRules); | |
| std::string defConfig; | |
| defConfig = this->AddConfigurations(target, gtgt); | |
| cmXCodeObject* dependencies = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| target->AddAttribute("dependencies", dependencies); | |
| target->AddAttribute("name", this->CreateString(gtgt->GetName())); | |
| target->AddAttribute("productName", this->CreateString(gtgt->GetName())); | |
| cmXCodeObject* fileRef = this->CreateObject(cmXCodeObject::PBXFileReference); | |
| if (char const* fileType = this->GetTargetFileType(gtgt)) { | |
| fileRef->AddAttribute("explicitFileType", this->CreateString(fileType)); | |
| } | |
| std::string fullName; | |
| if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY) { | |
| fullName = cmStrCat("lib", gtgt->GetName(), ".a"); | |
| } else { | |
| fullName = gtgt->GetFullName(defConfig); | |
| } | |
| fileRef->AddAttribute("path", this->CreateString(fullName)); | |
| fileRef->AddAttribute("sourceTree", | |
| this->CreateString("BUILT_PRODUCTS_DIR")); | |
| fileRef->SetComment(gtgt->GetName()); | |
| target->AddAttribute("productReference", | |
| this->CreateObjectReference(fileRef)); | |
| if (char const* productType = this->GetTargetProductType(gtgt)) { | |
| target->AddAttribute("productType", this->CreateString(productType)); | |
| } | |
| target->SetTarget(gtgt); | |
| this->XCodeObjectMap[gtgt] = target; | |
| target->SetId(this->GetOrCreateId(gtgt->GetName(), target->GetId())); | |
| return target; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::FindXCodeTarget( | |
| cmGeneratorTarget const* t) | |
| { | |
| if (!t) { | |
| return nullptr; | |
| } | |
| auto const i = this->XCodeObjectMap.find(t); | |
| if (i == this->XCodeObjectMap.end()) { | |
| return nullptr; | |
| } | |
| return i->second; | |
| } | |
| std::string cmGlobalXCodeGenerator::GetObjectId(cmXCodeObject::PBXType ptype, | |
| cm::string_view key) | |
| { | |
| std::string objectId; | |
| if (!key.empty()) { | |
| cmCryptoHash hash(cmCryptoHash::AlgoSHA256); | |
| hash.Initialize(); | |
| hash.Append(&ptype, sizeof(ptype)); | |
| hash.Append(key); | |
| objectId = cmSystemTools::UpperCase(hash.FinalizeHex().substr(0, 24)); | |
| } else { | |
| char cUuid[40] = { 0 }; | |
| CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); | |
| CFStringRef s = CFUUIDCreateString(kCFAllocatorDefault, uuid); | |
| CFStringGetCString(s, cUuid, sizeof(cUuid), kCFStringEncodingUTF8); | |
| objectId = cUuid; | |
| CFRelease(s); | |
| CFRelease(uuid); | |
| cmSystemTools::ReplaceString(objectId, "-", ""); | |
| if (objectId.size() > 24) { | |
| objectId = objectId.substr(0, 24); | |
| } | |
| } | |
| return objectId; | |
| } | |
| std::string cmGlobalXCodeGenerator::GetOrCreateId(std::string const& name, | |
| std::string const& id) | |
| { | |
| std::string guidStoreName = cmStrCat(name, "_GUID_CMAKE"); | |
| cmValue storedGUID = this->CMakeInstance->GetCacheDefinition(guidStoreName); | |
| if (storedGUID) { | |
| return *storedGUID; | |
| } | |
| this->CMakeInstance->AddCacheEntry( | |
| guidStoreName, id, "Stored Xcode object GUID", cmStateEnums::INTERNAL); | |
| return id; | |
| } | |
| void cmGlobalXCodeGenerator::AddDependTarget(cmXCodeObject* target, | |
| cmXCodeObject* dependTarget) | |
| { | |
| // This is called once for every edge in the target dependency graph. | |
| cmXCodeObject* container = | |
| this->CreateObject(cmXCodeObject::PBXContainerItemProxy); | |
| container->SetComment("PBXContainerItemProxy"); | |
| container->AddAttribute("containerPortal", | |
| this->CreateObjectReference(this->RootObject)); | |
| container->AddAttribute("proxyType", this->CreateString("1")); | |
| container->AddAttribute("remoteGlobalIDString", | |
| this->CreateObjectReference(dependTarget)); | |
| container->AddAttribute( | |
| "remoteInfo", this->CreateString(dependTarget->GetTarget()->GetName())); | |
| cmXCodeObject* targetdep = | |
| this->CreateObject(cmXCodeObject::PBXTargetDependency); | |
| targetdep->SetComment("PBXTargetDependency"); | |
| targetdep->AddAttribute("target", this->CreateObjectReference(dependTarget)); | |
| targetdep->AddAttribute("targetProxy", | |
| this->CreateObjectReference(container)); | |
| cmXCodeObject* depends = target->GetAttribute("dependencies"); | |
| if (!depends) { | |
| cmSystemTools::Error( | |
| "target does not have dependencies attribute error.."); | |
| } else { | |
| depends->AddUniqueObject(targetdep); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AppendOrAddBuildSetting(cmXCodeObject* settings, | |
| char const* attribute, | |
| cmXCodeObject* value) | |
| { | |
| if (settings) { | |
| cmXCodeObject* attr = settings->GetAttribute(attribute); | |
| if (!attr) { | |
| settings->AddAttribute(attribute, value); | |
| } else { | |
| this->AppendBuildSettingAttribute(settings, attribute, attr, value); | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AppendBuildSettingAttribute( | |
| cmXCodeObject* settings, char const* attribute, cmXCodeObject* attr, | |
| cmXCodeObject* value) | |
| { | |
| if (value->GetType() != cmXCodeObject::OBJECT_LIST && | |
| value->GetType() != cmXCodeObject::STRING) { | |
| cmSystemTools::Error( | |
| cmStrCat("Unsupported value type for appending: ", attribute)); | |
| return; | |
| } | |
| if (attr->GetType() == cmXCodeObject::OBJECT_LIST) { | |
| if (value->GetType() == cmXCodeObject::OBJECT_LIST) { | |
| for (auto* obj : value->GetObjectList()) { | |
| attr->AddObject(obj); | |
| } | |
| } else { | |
| attr->AddObject(value); | |
| } | |
| } else if (attr->GetType() == cmXCodeObject::STRING) { | |
| if (value->GetType() == cmXCodeObject::OBJECT_LIST) { | |
| // Add old value as a list item to new object list | |
| // and replace the attribute with the new list | |
| value->PrependObject(attr); | |
| settings->AddAttribute(attribute, value); | |
| } else { | |
| std::string newValue = | |
| cmStrCat(attr->GetString(), ' ', value->GetString()); | |
| attr->SetString(newValue); | |
| } | |
| } else { | |
| cmSystemTools::Error( | |
| cmStrCat("Unsupported attribute type for appending: ", attribute)); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AppendBuildSettingAttribute( | |
| cmXCodeObject* target, char const* attribute, cmXCodeObject* value, | |
| std::string const& configName) | |
| { | |
| // There are multiple configurations. Add the setting to the | |
| // buildSettings of the configuration name given. | |
| cmXCodeObject* configurationList = | |
| target->GetAttribute("buildConfigurationList")->GetObject(); | |
| cmXCodeObject* buildConfigs = | |
| configurationList->GetAttribute("buildConfigurations"); | |
| for (auto* obj : buildConfigs->GetObjectList()) { | |
| if (configName.empty() || | |
| obj->GetAttribute("name")->GetString() == configName) { | |
| cmXCodeObject* settings = obj->GetAttribute("buildSettings"); | |
| this->AppendOrAddBuildSetting(settings, attribute, value); | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::InheritBuildSettingAttribute( | |
| cmXCodeObject* target, char const* attribute) | |
| { | |
| cmXCodeObject* configurationList = | |
| target->GetAttribute("buildConfigurationList")->GetObject(); | |
| cmXCodeObject* buildConfigs = | |
| configurationList->GetAttribute("buildConfigurations"); | |
| for (auto* obj : buildConfigs->GetObjectList()) { | |
| cmXCodeObject* settings = obj->GetAttribute("buildSettings"); | |
| if (cmXCodeObject* attr = settings->GetAttribute(attribute)) { | |
| BuildObjectListOrString inherited(this, true); | |
| inherited.Add("$(inherited)"); | |
| this->AppendBuildSettingAttribute(settings, attribute, attr, | |
| inherited.CreateList()); | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AddDependAndLinkInformation(cmXCodeObject* target) | |
| { | |
| cmGeneratorTarget* gt = target->GetTarget(); | |
| if (!gt) { | |
| cmSystemTools::Error("Error no target on xobject\n"); | |
| return; | |
| } | |
| if (!gt->IsInBuildSystem()) { | |
| return; | |
| } | |
| // Add dependencies on other CMake targets. | |
| for (auto const& dep : this->GetTargetDirectDepends(gt)) { | |
| if (cmXCodeObject* dptarget = this->FindXCodeTarget(dep)) { | |
| this->AddDependTarget(target, dptarget); | |
| } | |
| } | |
| // Separate libraries into ones that can be linked using "Link Binary With | |
| // Libraries" build phase and the ones that can't. Only targets that build | |
| // Apple bundles (.app, .framework, .bundle), executables and dylibs can use | |
| // this feature and only targets that represent actual libraries (object, | |
| // static, dynamic or bundle, excluding executables) will be used. These are | |
| // limitations imposed by CMake use-cases - otherwise a lot of things break. | |
| // The rest will be linked using linker flags (OTHER_LDFLAGS setting in Xcode | |
| // project). | |
| std::map<std::string, std::vector<cmComputeLinkInformation::Item const*>> | |
| configItemMap; | |
| auto addToLinkerArguments = | |
| [&configItemMap](std::string const& configName, | |
| cmComputeLinkInformation::Item const* libItemPtr) { | |
| auto& linkVector = configItemMap[configName]; | |
| if (std::find_if(linkVector.begin(), linkVector.end(), | |
| [libItemPtr](cmComputeLinkInformation::Item const* p) { | |
| return p == libItemPtr; | |
| }) == linkVector.end()) { | |
| linkVector.push_back(libItemPtr); | |
| } | |
| }; | |
| std::vector<cmComputeLinkInformation::Item const*> linkPhaseTargetVector; | |
| std::map<std::string, std::vector<std::string>> targetConfigMap; | |
| using ConfigItemPair = | |
| std::pair<std::string, cmComputeLinkInformation::Item const*>; | |
| std::map<std::string, std::vector<ConfigItemPair>> targetItemMap; | |
| std::map<std::string, std::vector<std::string>> targetProductNameMap; | |
| bool useLinkPhase = false; | |
| bool forceLinkPhase = false; | |
| cmValue prop = | |
| target->GetTarget()->GetProperty("XCODE_LINK_BUILD_PHASE_MODE"); | |
| if (prop) { | |
| if (*prop == "BUILT_ONLY"_s) { | |
| useLinkPhase = true; | |
| } else if (*prop == "KNOWN_LOCATION"_s) { | |
| useLinkPhase = true; | |
| forceLinkPhase = true; | |
| } else if (*prop != "NONE"_s) { | |
| cmSystemTools::Error( | |
| cmStrCat("Invalid value for XCODE_LINK_BUILD_PHASE_MODE: ", *prop)); | |
| return; | |
| } | |
| } | |
| for (auto const& configName : this->CurrentConfigurationTypes) { | |
| cmComputeLinkInformation* cli = gt->GetLinkInformation(configName); | |
| if (!cli) { | |
| continue; | |
| } | |
| for (auto const& libItem : cli->GetItems()) { | |
| // Explicitly ignore OBJECT libraries as Xcode emulates them as static | |
| // libraries without an artifact. Avoid exposing this to the rest of | |
| // CMake's compilation model. | |
| if (libItem.Target && | |
| libItem.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) { | |
| continue; | |
| } | |
| // We want to put only static libraries, dynamic libraries, frameworks | |
| // and bundles that are built from targets that are not imported in "Link | |
| // Binary With Libraries" build phase. Except if the target property | |
| // XCODE_LINK_BUILD_PHASE_MODE is KNOWN_LOCATION then all imported and | |
| // non-target libraries will be added as well. | |
| if (useLinkPhase && | |
| (gt->GetType() == cmStateEnums::EXECUTABLE || | |
| gt->GetType() == cmStateEnums::SHARED_LIBRARY || | |
| gt->GetType() == cmStateEnums::MODULE_LIBRARY) && | |
| ((libItem.Target && | |
| (!libItem.Target->IsImported() || forceLinkPhase) && | |
| (libItem.Target->GetType() == cmStateEnums::STATIC_LIBRARY || | |
| libItem.Target->GetType() == cmStateEnums::SHARED_LIBRARY || | |
| libItem.Target->GetType() == cmStateEnums::MODULE_LIBRARY || | |
| libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY)) || | |
| (!libItem.Target && | |
| libItem.IsPath == cmComputeLinkInformation::ItemIsPath::Yes && | |
| forceLinkPhase))) { | |
| std::string libName; | |
| bool canUseLinkPhase = !libItem.HasFeature() || | |
| libItem.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s || | |
| libItem.GetFeatureName() == "FRAMEWORK"_s || | |
| libItem.GetFeatureName() == "__CMAKE_LINK_XCFRAMEWORK"_s || | |
| libItem.GetFeatureName() == "XCFRAMEWORK"_s || | |
| libItem.GetFeatureName() == "WEAK_FRAMEWORK"_s || | |
| libItem.GetFeatureName() == "WEAK_LIBRARY"_s; | |
| if (canUseLinkPhase) { | |
| if (libItem.Target) { | |
| if (libItem.Target->GetType() == cmStateEnums::UNKNOWN_LIBRARY) { | |
| canUseLinkPhase = canUseLinkPhase && forceLinkPhase; | |
| } else { | |
| // If a library target uses custom build output directory Xcode | |
| // won't pick it up so we have to resort back to linker flags, | |
| // but that's OK as long as the custom output dir is absolute | |
| // path. | |
| for (auto const& libConfigName : | |
| this->CurrentConfigurationTypes) { | |
| canUseLinkPhase = canUseLinkPhase && | |
| libItem.Target->UsesDefaultOutputDir( | |
| libConfigName, cmStateEnums::RuntimeBinaryArtifact); | |
| } | |
| } | |
| libName = libItem.Target->GetName(); | |
| } else { | |
| libName = cmSystemTools::GetFilenameName(libItem.Value.Value); | |
| // We don't want all the possible files here, just standard | |
| // libraries | |
| auto const libExt = cmSystemTools::GetFilenameExtension(libName); | |
| if (!IsLinkPhaseLibraryExtension(libExt)) { | |
| canUseLinkPhase = false; | |
| } | |
| } | |
| } | |
| if (canUseLinkPhase) { | |
| // Add unique configuration name to target-config map for later | |
| // checks | |
| auto& configVector = targetConfigMap[libName]; | |
| if (std::find(configVector.begin(), configVector.end(), | |
| configName) == configVector.end()) { | |
| configVector.push_back(configName); | |
| } | |
| // Add a pair of config and item to target-item map | |
| auto& itemVector = targetItemMap[libName]; | |
| itemVector.emplace_back(configName, &libItem); | |
| // Add product file-name to a lib-product map | |
| auto productName = | |
| cmSystemTools::GetFilenameName(libItem.Value.Value); | |
| auto& productVector = targetProductNameMap[libName]; | |
| if (std::find(productVector.begin(), productVector.end(), | |
| productName) == productVector.end()) { | |
| productVector.push_back(productName); | |
| } | |
| continue; | |
| } | |
| } | |
| // Add this library item to a regular linker flag list | |
| addToLinkerArguments(configName, &libItem); | |
| } | |
| } | |
| // Go through target library map and separate libraries that are linked | |
| // in all configurations and produce only single product, from the rest. | |
| // Only these will be linked through "Link Binary With Libraries" build | |
| // phase. | |
| for (auto const& targetLibConfigs : targetConfigMap) { | |
| // Add this library to "Link Binary With Libraries" build phase if it's | |
| // linked in all configurations and it has only one product name | |
| auto& itemVector = targetItemMap[targetLibConfigs.first]; | |
| auto& productVector = targetProductNameMap[targetLibConfigs.first]; | |
| if (targetLibConfigs.second == this->CurrentConfigurationTypes && | |
| productVector.size() == 1) { | |
| // Add this library to "Link Binary With Libraries" list | |
| linkPhaseTargetVector.push_back(itemVector[0].second); | |
| } else { | |
| for (auto const& libItem : targetItemMap[targetLibConfigs.first]) { | |
| // Add this library item to a regular linker flag list | |
| addToLinkerArguments(libItem.first, libItem.second); | |
| } | |
| } | |
| } | |
| // Add libraries to "Link Binary With Libraries" build phase and collect | |
| // their search paths. Xcode does not support per-configuration linking | |
| // in this build phase so we don't have to do this for each configuration | |
| // separately. | |
| std::vector<std::string> linkSearchPaths; | |
| std::vector<std::string> frameworkSearchPaths; | |
| std::set<std::pair<cmXCodeObject*, std::string>> linkBuildFileSet; | |
| for (auto const& libItem : linkPhaseTargetVector) { | |
| // Add target output directory as a library search path | |
| std::string linkDir; | |
| if (libItem->Target) { | |
| linkDir = libItem->Target->GetLocationForBuild(); | |
| } else { | |
| linkDir = libItem->Value.Value; | |
| } | |
| if (cmHasSuffix(libItem->GetFeatureName(), "FRAMEWORK"_s)) { | |
| auto fwDescriptor = this->SplitFrameworkPath( | |
| linkDir, cmGlobalGenerator::FrameworkFormat::Extended); | |
| if (fwDescriptor && !fwDescriptor->Directory.empty()) { | |
| linkDir = fwDescriptor->Directory; | |
| if (std::find(frameworkSearchPaths.begin(), frameworkSearchPaths.end(), | |
| linkDir) == frameworkSearchPaths.end()) { | |
| frameworkSearchPaths.push_back(linkDir); | |
| } | |
| } | |
| } else { | |
| linkDir = cmSystemTools::GetParentDirectory(linkDir); | |
| if (std::find(linkSearchPaths.begin(), linkSearchPaths.end(), linkDir) == | |
| linkSearchPaths.end()) { | |
| linkSearchPaths.push_back(linkDir); | |
| } | |
| } | |
| if (libItem->Target && !libItem->Target->IsImported()) { | |
| for (auto const& configName : this->CurrentConfigurationTypes) { | |
| target->AddDependTarget(configName, libItem->Target->GetName()); | |
| } | |
| } | |
| // Get the library target | |
| auto* libTarget = FindXCodeTarget(libItem->Target); | |
| cmXCodeObject* buildFile; | |
| if (!libTarget) { | |
| if (libItem->IsPath == cmComputeLinkInformation::ItemIsPath::Yes) { | |
| // Get or create a direct file ref in the root project | |
| auto cleanPath = libItem->Value.Value; | |
| if (cmSystemTools::FileIsFullPath(cleanPath)) { | |
| // Some arguments are reported as paths, but they are actually not, | |
| // so we can't collapse them, and neither can we collapse relative | |
| // paths | |
| cleanPath = cmSystemTools::CollapseFullPath(cleanPath); | |
| } | |
| auto it = this->ExternalLibRefs.find(cleanPath); | |
| if (it == this->ExternalLibRefs.end()) { | |
| buildFile = CreateXCodeBuildFileFromPath(cleanPath, gt, "", nullptr); | |
| if (!buildFile) { | |
| // Add this library item back to a regular linker flag list | |
| for (auto const& conf : configItemMap) { | |
| addToLinkerArguments(conf.first, libItem); | |
| } | |
| continue; | |
| } | |
| this->ExternalLibRefs.emplace(cleanPath, buildFile); | |
| } else { | |
| buildFile = it->second; | |
| } | |
| } else { | |
| // Add this library item back to a regular linker flag list | |
| for (auto const& conf : configItemMap) { | |
| addToLinkerArguments(conf.first, libItem); | |
| } | |
| continue; | |
| } | |
| } else { | |
| // Add the target output file as a build reference for other targets | |
| // to link against | |
| auto* fileRefObject = libTarget->GetAttribute("productReference"); | |
| if (!fileRefObject) { | |
| // Add this library item back to a regular linker flag list | |
| for (auto const& conf : configItemMap) { | |
| addToLinkerArguments(conf.first, libItem); | |
| } | |
| continue; | |
| } | |
| auto it = FileRefToBuildFileMap.find(fileRefObject); | |
| if (it == FileRefToBuildFileMap.end()) { | |
| buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile); | |
| buildFile->AddAttribute("fileRef", fileRefObject); | |
| FileRefToBuildFileMap[fileRefObject] = buildFile; | |
| } else { | |
| buildFile = it->second; | |
| } | |
| } | |
| // Add this reference to current target | |
| auto* buildPhases = target->GetAttribute("buildPhases"); | |
| if (!buildPhases) { | |
| cmSystemTools::Error("Missing buildPhase of target"); | |
| continue; | |
| } | |
| auto* frameworkBuildPhase = | |
| buildPhases->GetObject(cmXCodeObject::PBXFrameworksBuildPhase); | |
| if (!frameworkBuildPhase) { | |
| cmSystemTools::Error("Missing PBXFrameworksBuildPhase of buildPhase"); | |
| continue; | |
| } | |
| auto* buildFiles = frameworkBuildPhase->GetAttribute("files"); | |
| if (!buildFiles) { | |
| cmSystemTools::Error("Missing files of PBXFrameworksBuildPhase"); | |
| continue; | |
| } | |
| if (buildFile) { | |
| if (cmHasPrefix(libItem->GetFeatureName(), "WEAK_"_s)) { | |
| auto key = std::make_pair(buildFile->GetAttribute("fileRef"), | |
| libItem->GetFeatureName()); | |
| if (linkBuildFileSet.find(key) != linkBuildFileSet.end()) { | |
| continue; | |
| } | |
| linkBuildFileSet.insert(key); | |
| cmXCodeObject* buildObject = | |
| this->CreateObject(cmXCodeObject::PBXBuildFile); | |
| buildObject->AddAttribute("fileRef", key.first); | |
| // Add settings, ATTRIBUTES, Weak flag | |
| cmXCodeObject* settings = | |
| this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| attrs->AddObject(this->CreateString("Weak")); | |
| settings->AddAttribute("ATTRIBUTES", attrs); | |
| buildObject->AddAttribute("settings", settings); | |
| buildFile = buildObject; | |
| } | |
| if (!buildFiles->HasObject(buildFile)) { | |
| buildFiles->AddObject(buildFile); | |
| } | |
| } | |
| } | |
| // Loop over configuration types and set per-configuration info. | |
| for (auto const& configName : this->CurrentConfigurationTypes) { | |
| { | |
| // Add object library contents as link flags. | |
| BuildObjectListOrString libSearchPaths(this, true); | |
| std::vector<cmSourceFile const*> objs; | |
| gt->GetExternalObjects(objs, configName); | |
| for (auto const* sourceFile : objs) { | |
| if (sourceFile->GetObjectLibrary().empty()) { | |
| continue; | |
| } | |
| libSearchPaths.Add(this->XCodeEscapePath(sourceFile->GetFullPath())); | |
| } | |
| this->AppendBuildSettingAttribute( | |
| target, this->GetTargetLinkFlagsVar(gt), libSearchPaths.CreateList(), | |
| configName); | |
| } | |
| // Compute the link library and directory information. | |
| cmComputeLinkInformation* cli = gt->GetLinkInformation(configName); | |
| if (!cli) { | |
| continue; | |
| } | |
| // add .xcframework include paths | |
| { | |
| // Keep track of framework search paths we've already added or that are | |
| // part of the set of implicit search paths. We don't want to repeat | |
| // them and we also need to avoid hard-coding any SDK-specific paths. | |
| // This is essential for getting device-and-simulator builds to work, | |
| // otherwise we end up hard-coding a path to the wrong SDK for | |
| // SDK-provided frameworks that are added by their full path. | |
| std::set<std::string> emitted(cli->GetFrameworkPathsEmitted()); | |
| BuildObjectListOrString includePaths(this, true); | |
| BuildObjectListOrString fwSearchPaths(this, true); | |
| for (auto const& libItem : configItemMap[configName]) { | |
| auto const& libName = *libItem; | |
| if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) { | |
| auto cleanPath = libName.Value.Value; | |
| if (cmSystemTools::FileIsFullPath(cleanPath)) { | |
| cleanPath = cmSystemTools::CollapseFullPath(cleanPath); | |
| } | |
| bool isXcFramework = | |
| cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s); | |
| if (isXcFramework) { | |
| auto plist = cmParseXcFrameworkPlist( | |
| cleanPath, *this->Makefiles.front(), libName.Value.Backtrace); | |
| if (!plist) { | |
| return; | |
| } | |
| if (auto const* library = plist->SelectSuitableLibrary( | |
| *this->Makefiles.front(), libName.Value.Backtrace)) { | |
| auto libraryPath = | |
| cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/', | |
| library->LibraryPath); | |
| if (auto const fwDescriptor = this->SplitFrameworkPath( | |
| libraryPath, | |
| cmGlobalGenerator::FrameworkFormat::Relaxed)) { | |
| if (!fwDescriptor->Directory.empty() && | |
| emitted.insert(fwDescriptor->Directory).second) { | |
| // This is a search path we had not added before and it | |
| // isn't an implicit search path, so we need it | |
| fwSearchPaths.Add( | |
| this->XCodeEscapePath(fwDescriptor->Directory)); | |
| } | |
| } else { | |
| if (!library->HeadersPath.empty()) { | |
| includePaths.Add(this->XCodeEscapePath( | |
| cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/', | |
| library->HeadersPath))); | |
| } | |
| } | |
| } else { | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| if (!includePaths.IsEmpty()) { | |
| this->AppendBuildSettingAttribute(target, "HEADER_SEARCH_PATHS", | |
| includePaths.CreateList(), | |
| configName); | |
| } | |
| if (!fwSearchPaths.IsEmpty()) { | |
| this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS", | |
| fwSearchPaths.CreateList(), | |
| configName); | |
| } | |
| } | |
| // Skip link information for object libraries. | |
| if (gt->GetType() == cmStateEnums::OBJECT_LIBRARY || | |
| gt->GetType() == cmStateEnums::STATIC_LIBRARY) { | |
| continue; | |
| } | |
| // Add dependencies directly on library files. | |
| for (auto const& libDep : cli->GetDepends()) { | |
| target->AddDependLibrary(configName, libDep); | |
| } | |
| // add the library search paths | |
| { | |
| BuildObjectListOrString libSearchPaths(this, true); | |
| for (auto const& libDir : cli->GetDirectories()) { | |
| if (!libDir.empty() && libDir != "/usr/lib"_s) { | |
| cmPolicies::PolicyStatus cmp0142 = | |
| target->GetTarget()->GetPolicyStatusCMP0142(); | |
| if (cmp0142 == cmPolicies::OLD || cmp0142 == cmPolicies::WARN) { | |
| libSearchPaths.Add(this->XCodeEscapePath(cmStrCat( | |
| libDir, "/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"))); | |
| } | |
| libSearchPaths.Add(this->XCodeEscapePath(libDir)); | |
| } | |
| } | |
| // Add previously collected paths where to look for libraries | |
| // that were added to "Link Binary With Libraries" | |
| for (auto& libDir : linkSearchPaths) { | |
| libSearchPaths.Add(this->XCodeEscapePath(libDir)); | |
| } | |
| // Add toolchain specified language link directories | |
| std::string const& linkDirsString = | |
| this->Makefiles.front()->GetSafeDefinition(cmStrCat( | |
| "CMAKE_", cli->GetLinkLanguage(), "_STANDARD_LINK_DIRECTORIES")); | |
| for (auto const& libDir : cmList(linkDirsString)) { | |
| libSearchPaths.Add(this->XCodeEscapePath(libDir)); | |
| } | |
| if (!libSearchPaths.IsEmpty()) { | |
| this->AppendBuildSettingAttribute(target, "LIBRARY_SEARCH_PATHS", | |
| libSearchPaths.CreateList(), | |
| configName); | |
| } | |
| } | |
| // add framework search paths | |
| { | |
| BuildObjectListOrString fwSearchPaths(this, true); | |
| // Add previously collected paths where to look for frameworks | |
| // that were added to "Link Binary With Libraries" | |
| for (auto& fwDir : frameworkSearchPaths) { | |
| fwSearchPaths.Add(this->XCodeEscapePath(fwDir)); | |
| } | |
| if (!fwSearchPaths.IsEmpty()) { | |
| this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS", | |
| fwSearchPaths.CreateList(), | |
| configName); | |
| } | |
| } | |
| // now add the left-over link libraries | |
| { | |
| // Keep track of framework search paths we've already added or that are | |
| // part of the set of implicit search paths. We don't want to repeat | |
| // them and we also need to avoid hard-coding any SDK-specific paths. | |
| // This is essential for getting device-and-simulator builds to work, | |
| // otherwise we end up hard-coding a path to the wrong SDK for | |
| // SDK-provided frameworks that are added by their full path. | |
| std::set<std::string> emitted(cli->GetFrameworkPathsEmitted()); | |
| BuildObjectListOrString libPaths(this, true); | |
| BuildObjectListOrString fwSearchPaths(this, true); | |
| for (auto const& libItem : configItemMap[configName]) { | |
| auto const& libName = *libItem; | |
| if (libName.IsPath == cmComputeLinkInformation::ItemIsPath::Yes) { | |
| auto cleanPath = libName.Value.Value; | |
| if (cmSystemTools::FileIsFullPath(cleanPath)) { | |
| cleanPath = cmSystemTools::CollapseFullPath(cleanPath); | |
| } | |
| bool isXcFramework = | |
| cmHasSuffix(libName.GetFeatureName(), "XCFRAMEWORK"_s); | |
| bool isFramework = !isXcFramework && | |
| cmHasSuffix(libName.GetFeatureName(), "FRAMEWORK"_s); | |
| if (isFramework) { | |
| auto const fwDescriptor = this->SplitFrameworkPath( | |
| cleanPath, cmGlobalGenerator::FrameworkFormat::Extended); | |
| if (isFramework && !fwDescriptor->Directory.empty() && | |
| emitted.insert(fwDescriptor->Directory).second) { | |
| // This is a search path we had not added before and it isn't | |
| // an implicit search path, so we need it | |
| fwSearchPaths.Add( | |
| this->XCodeEscapePath(fwDescriptor->Directory)); | |
| } | |
| if (libName.GetFeatureName() == "__CMAKE_LINK_FRAMEWORK"_s) { | |
| // use the full path | |
| libPaths.Add( | |
| libName.GetFormattedItem(this->XCodeEscapePath(cleanPath)) | |
| .Value); | |
| } else { | |
| libPaths.Add(libName | |
| .GetFormattedItem(this->XCodeEscapePath( | |
| fwDescriptor->GetLinkName())) | |
| .Value); | |
| } | |
| } else if (isXcFramework) { | |
| auto plist = cmParseXcFrameworkPlist( | |
| cleanPath, *this->Makefiles.front(), libName.Value.Backtrace); | |
| if (!plist) { | |
| return; | |
| } | |
| if (auto const* library = plist->SelectSuitableLibrary( | |
| *this->Makefiles.front(), libName.Value.Backtrace)) { | |
| auto libraryPath = | |
| cmStrCat(cleanPath, '/', library->LibraryIdentifier, '/', | |
| library->LibraryPath); | |
| if (auto const fwDescriptor = this->SplitFrameworkPath( | |
| libraryPath, | |
| cmGlobalGenerator::FrameworkFormat::Relaxed)) { | |
| libPaths.Add(cmStrCat( | |
| "-framework ", | |
| this->XCodeEscapePath(fwDescriptor->GetLinkName()))); | |
| } else { | |
| libPaths.Add( | |
| libName.GetFormattedItem(this->XCodeEscapePath(libraryPath)) | |
| .Value); | |
| } | |
| } else { | |
| return; | |
| } | |
| } else { | |
| libPaths.Add( | |
| libName.GetFormattedItem(this->XCodeEscapePath(cleanPath)) | |
| .Value); | |
| } | |
| if ((!libName.Target || libName.Target->IsImported()) && | |
| (isFramework || isXcFramework || | |
| IsLinkPhaseLibraryExtension(cleanPath))) { | |
| // Create file reference for embedding | |
| auto it = this->ExternalLibRefs.find(cleanPath); | |
| if (it == this->ExternalLibRefs.end()) { | |
| auto* buildFile = | |
| this->CreateXCodeBuildFileFromPath(cleanPath, gt, "", nullptr); | |
| if (buildFile) { | |
| this->ExternalLibRefs.emplace(cleanPath, buildFile); | |
| } | |
| } | |
| } | |
| } else if (!libName.Target || | |
| libName.Target->GetType() != | |
| cmStateEnums::INTERFACE_LIBRARY) { | |
| libPaths.Add(libName.Value.Value); | |
| } | |
| if (libName.Target && !libName.Target->IsImported()) { | |
| target->AddDependTarget(configName, libName.Target->GetName()); | |
| } | |
| } | |
| if (!libPaths.IsEmpty()) { | |
| this->AppendBuildSettingAttribute(target, | |
| this->GetTargetLinkFlagsVar(gt), | |
| libPaths.CreateList(), configName); | |
| } | |
| if (!fwSearchPaths.IsEmpty()) { | |
| this->AppendBuildSettingAttribute(target, "FRAMEWORK_SEARCH_PATHS", | |
| fwSearchPaths.CreateList(), | |
| configName); | |
| } | |
| } | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedObjects( | |
| cmXCodeObject* target, std::string const& copyFilesBuildPhaseName, | |
| std::string const& embedPropertyName, std::string const& dstSubfolderSpec, | |
| int actionsOnByDefault, std::string const& defaultDstPath) | |
| { | |
| cmGeneratorTarget* gt = target->GetTarget(); | |
| if (!gt) { | |
| cmSystemTools::Error("Error no target on xobject\n"); | |
| return; | |
| } | |
| if (!gt->IsInBuildSystem()) { | |
| return; | |
| } | |
| bool isFrameworkTarget = gt->IsFrameworkOnApple(); | |
| bool isBundleTarget = gt->GetPropertyAsBool("MACOSX_BUNDLE"); | |
| bool isCFBundleTarget = gt->IsCFBundleOnApple(); | |
| if (!(isFrameworkTarget || isBundleTarget || isCFBundleTarget)) { | |
| return; | |
| } | |
| cmValue files = gt->GetProperty(embedPropertyName); | |
| if (!files) { | |
| return; | |
| } | |
| // Create an "Embedded Frameworks" build phase | |
| auto* copyFilesBuildPhase = | |
| this->CreateObject(cmXCodeObject::PBXCopyFilesBuildPhase); | |
| copyFilesBuildPhase->SetComment(copyFilesBuildPhaseName); | |
| copyFilesBuildPhase->AddAttribute("buildActionMask", | |
| this->CreateString("2147483647")); | |
| copyFilesBuildPhase->AddAttribute("dstSubfolderSpec", | |
| this->CreateString(dstSubfolderSpec)); | |
| copyFilesBuildPhase->AddAttribute( | |
| "name", this->CreateString(copyFilesBuildPhaseName)); | |
| if (cmValue fwEmbedPath = | |
| gt->GetProperty(cmStrCat(embedPropertyName, "_PATH"))) { | |
| copyFilesBuildPhase->AddAttribute("dstPath", | |
| this->CreateString(*fwEmbedPath)); | |
| } else { | |
| copyFilesBuildPhase->AddAttribute("dstPath", | |
| this->CreateString(defaultDstPath)); | |
| } | |
| copyFilesBuildPhase->AddAttribute("runOnlyForDeploymentPostprocessing", | |
| this->CreateString("0")); | |
| cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| // Collect all embedded frameworks and dylibs and add them to build phase | |
| cmList relFiles{ *files }; | |
| for (std::string const& relFile : relFiles) { | |
| cmXCodeObject* buildFile{ nullptr }; | |
| std::string filePath = relFile; | |
| auto* genTarget = this->FindGeneratorTarget(relFile); | |
| if (genTarget) { | |
| // This is a target - get it's product path reference | |
| auto* xcTarget = this->FindXCodeTarget(genTarget); | |
| if (!xcTarget) { | |
| cmSystemTools::Error( | |
| cmStrCat("Can not find a target for ", genTarget->GetName())); | |
| continue; | |
| } | |
| // Add the target output file as a build reference for other targets | |
| // to link against | |
| auto* fileRefObject = xcTarget->GetAttribute("productReference"); | |
| if (!fileRefObject) { | |
| cmSystemTools::Error(cmStrCat("Target ", genTarget->GetName(), | |
| " is missing product reference")); | |
| continue; | |
| } | |
| auto it = this->FileRefToEmbedBuildFileMap.find(fileRefObject); | |
| if (it == this->FileRefToEmbedBuildFileMap.end()) { | |
| buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile); | |
| buildFile->AddAttribute("fileRef", fileRefObject); | |
| this->FileRefToEmbedBuildFileMap[fileRefObject] = buildFile; | |
| } else { | |
| buildFile = it->second; | |
| } | |
| } else if (cmSystemTools::IsPathToFramework(relFile) || | |
| cmSystemTools::IsPathToMacOSSharedLibrary(relFile) || | |
| cmSystemTools::FileIsDirectory(filePath)) { | |
| // This is a regular string path - create file reference | |
| cmXCodeObject* fileRef = | |
| this->CreateXCodeFileReferenceFromPath(relFile, gt, "", nullptr); | |
| if (fileRef) { | |
| buildFile = this->CreateObject(cmXCodeObject::PBXBuildFile); | |
| buildFile->SetComment(fileRef->GetComment()); | |
| buildFile->AddAttribute("fileRef", | |
| this->CreateObjectReference(fileRef)); | |
| } | |
| if (!buildFile) { | |
| cmSystemTools::Error( | |
| cmStrCat("Can't create build file for ", relFile)); | |
| continue; | |
| } | |
| } | |
| if (!buildFile) { | |
| cmSystemTools::Error(cmStrCat("Can't find a build file for ", relFile)); | |
| continue; | |
| } | |
| // Set build file configuration | |
| cmXCodeObject* settings = | |
| this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| cmXCodeObject* attrs = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| bool removeHeaders = actionsOnByDefault & RemoveHeadersOnCopyByDefault; | |
| if (cmValue prop = gt->GetProperty( | |
| cmStrCat(embedPropertyName, "_REMOVE_HEADERS_ON_COPY"))) { | |
| removeHeaders = prop.IsOn(); | |
| } | |
| if (removeHeaders) { | |
| attrs->AddObject(this->CreateString("RemoveHeadersOnCopy")); | |
| } | |
| bool codeSign = actionsOnByDefault & CodeSignOnCopyByDefault; | |
| if (cmValue prop = | |
| gt->GetProperty(cmStrCat(embedPropertyName, "_CODE_SIGN_ON_COPY"))) { | |
| codeSign = prop.IsOn(); | |
| } | |
| if (codeSign) { | |
| attrs->AddObject(this->CreateString("CodeSignOnCopy")); | |
| } | |
| settings->AddAttributeIfNotEmpty("ATTRIBUTES", attrs); | |
| buildFile->AddAttributeIfNotEmpty("settings", settings); | |
| if (!buildFiles->HasObject(buildFile)) { | |
| buildFiles->AddObject(buildFile); | |
| } | |
| } | |
| copyFilesBuildPhase->AddAttribute("files", buildFiles); | |
| auto* buildPhases = target->GetAttribute("buildPhases"); | |
| // Embed-something build phases must be inserted before the post-build | |
| // command because that command is expected to be last | |
| buildPhases->InsertObject(buildPhases->GetObjectCount() - 1, | |
| copyFilesBuildPhase); | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedFrameworks(cmXCodeObject* target) | |
| { | |
| static auto const* const dstSubfolderSpec = "10"; | |
| // Despite the name, by default Xcode uses "Embed Frameworks" build phase | |
| // for both frameworks and dynamic libraries | |
| this->AddEmbeddedObjects(target, "Embed Frameworks", | |
| "XCODE_EMBED_FRAMEWORKS", dstSubfolderSpec, | |
| NoActionOnCopyByDefault); | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedPlugIns(cmXCodeObject* target) | |
| { | |
| static auto const* const dstSubfolderSpec = "13"; | |
| this->AddEmbeddedObjects(target, "Embed PlugIns", "XCODE_EMBED_PLUGINS", | |
| dstSubfolderSpec, NoActionOnCopyByDefault); | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedAppExtensions(cmXCodeObject* target) | |
| { | |
| static auto const* const dstSubfolderSpec = "13"; | |
| this->AddEmbeddedObjects(target, "Embed App Extensions", | |
| "XCODE_EMBED_APP_EXTENSIONS", dstSubfolderSpec, | |
| RemoveHeadersOnCopyByDefault); | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedExtensionKitExtensions( | |
| cmXCodeObject* target) | |
| { | |
| static auto const* const dstSubfolderSpec = "16"; | |
| this->AddEmbeddedObjects(target, "Embed App Extensions", | |
| "XCODE_EMBED_EXTENSIONKIT_EXTENSIONS", | |
| dstSubfolderSpec, RemoveHeadersOnCopyByDefault, | |
| "$(EXTENSIONS_FOLDER_PATH)"); | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedResources(cmXCodeObject* target) | |
| { | |
| static auto const dstSubfolderSpec = "7"; | |
| this->AddEmbeddedObjects(target, "Embed Resources", "XCODE_EMBED_RESOURCES", | |
| dstSubfolderSpec, NoActionOnCopyByDefault); | |
| } | |
| void cmGlobalXCodeGenerator::AddEmbeddedXPCServices(cmXCodeObject* target) | |
| { | |
| static auto const dstSubfolderSpec = "16"; | |
| this->AddEmbeddedObjects( | |
| target, "Embed XPC Services", "XCODE_EMBED_XPC_SERVICES", dstSubfolderSpec, | |
| NoActionOnCopyByDefault, "$(CONTENTS_FOLDER_PATH)/XPCServices"); | |
| } | |
| bool cmGlobalXCodeGenerator::CreateGroups( | |
| std::vector<cmLocalGenerator*>& generators) | |
| { | |
| for (auto& generator : generators) { | |
| cmMakefile* mf = generator->GetMakefile(); | |
| std::vector<cmSourceGroup> sourceGroups = mf->GetSourceGroups(); | |
| for (auto const& gtgt : generator->GetGeneratorTargets()) { | |
| // Same skipping logic here as in CreateXCodeTargets so that we do not | |
| // end up with (empty anyhow) ZERO_CHECK, install, or test source | |
| // groups: | |
| // | |
| if (!gtgt->IsInBuildSystem() || | |
| gtgt->GetType() == cmStateEnums::GLOBAL_TARGET || | |
| gtgt->GetName() == CMAKE_CHECK_BUILD_SYSTEM_TARGET) { | |
| continue; | |
| } | |
| auto addSourceToGroup = [this, mf, >gt, | |
| &sourceGroups](std::string const& source) { | |
| cmSourceGroup* sourceGroup = mf->FindSourceGroup(source, sourceGroups); | |
| cmXCodeObject* pbxgroup = | |
| this->CreateOrGetPBXGroup(gtgt.get(), sourceGroup); | |
| std::string key = GetGroupMapKeyFromPath(gtgt.get(), source); | |
| this->GroupMap[key] = pbxgroup; | |
| }; | |
| // Put cmSourceFile instances in proper groups: | |
| for (auto const& si : gtgt->GetAllConfigSources()) { | |
| cmSourceFile const* sf = si.Source; | |
| if (!sf->GetObjectLibrary().empty()) { | |
| // Object library files go on the link line instead. | |
| continue; | |
| } | |
| addSourceToGroup(sf->GetFullPath()); | |
| } | |
| // Add CMakeLists.txt file for user convenience. | |
| { | |
| std::string listfile = this->GetCMakeInstance()->GetCMakeListFile( | |
| gtgt->GetLocalGenerator()->GetCurrentSourceDirectory()); | |
| cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource( | |
| listfile, false, cmSourceFileLocationKind::Known); | |
| addSourceToGroup(sf->ResolveFullPath()); | |
| } | |
| // Add the Info.plist we are about to generate for an App Bundle. | |
| if (gtgt->GetPropertyAsBool("MACOSX_BUNDLE")) { | |
| std::string plist = this->ComputeInfoPListLocation(gtgt.get()); | |
| cmSourceFile* sf = gtgt->Makefile->GetOrCreateSource( | |
| plist, true, cmSourceFileLocationKind::Known); | |
| addSourceToGroup(sf->ResolveFullPath()); | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreatePBXGroup(cmXCodeObject* parent, | |
| std::string const& name) | |
| { | |
| cmXCodeObject* parentChildren = nullptr; | |
| if (parent) { | |
| parentChildren = parent->GetAttribute("children"); | |
| } | |
| cmXCodeObject* group = this->CreateObject(cmXCodeObject::PBXGroup); | |
| cmXCodeObject* groupChildren = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| group->AddAttribute("name", this->CreateString(name)); | |
| group->AddAttribute("children", groupChildren); | |
| group->AddAttribute("sourceTree", this->CreateString("<group>")); | |
| if (parentChildren) { | |
| parentChildren->AddObject(group); | |
| } | |
| return group; | |
| } | |
| cmXCodeObject* cmGlobalXCodeGenerator::CreateOrGetPBXGroup( | |
| cmGeneratorTarget* gtgt, cmSourceGroup* sg) | |
| { | |
| std::string s; | |
| std::string target; | |
| std::string const targetFolder = gtgt->GetEffectiveFolderName(); | |
| if (!targetFolder.empty()) { | |
| target = cmStrCat(targetFolder, '/'); | |
| } | |
| target += gtgt->GetName(); | |
| s = cmStrCat(target, '/', sg->GetFullName()); | |
| auto it = this->GroupNameMap.find(s); | |
| if (it != this->GroupNameMap.end()) { | |
| return it->second; | |
| } | |
| it = this->TargetGroup.find(target); | |
| cmXCodeObject* tgroup = nullptr; | |
| if (it != this->TargetGroup.end()) { | |
| tgroup = it->second; | |
| } else { | |
| std::vector<std::string> const tgt_folders = cmTokenize(target, '/'); | |
| std::string curr_tgt_folder; | |
| for (std::vector<std::string>::size_type i = 0; i < tgt_folders.size(); | |
| i++) { | |
| if (i != 0) { | |
| curr_tgt_folder += '/'; | |
| } | |
| curr_tgt_folder += tgt_folders[i]; | |
| it = this->TargetGroup.find(curr_tgt_folder); | |
| if (it != this->TargetGroup.end()) { | |
| tgroup = it->second; | |
| continue; | |
| } | |
| tgroup = this->CreatePBXGroup(tgroup, tgt_folders[i]); | |
| this->TargetGroup[curr_tgt_folder] = tgroup; | |
| if (i == 0) { | |
| this->MainGroupChildren->AddObject(tgroup); | |
| } | |
| } | |
| } | |
| this->TargetGroup[target] = tgroup; | |
| // If it's the default source group (empty name) then put the source file | |
| // directly in the tgroup... | |
| // | |
| if (sg->GetFullName().empty()) { | |
| this->GroupNameMap[s] = tgroup; | |
| return tgroup; | |
| } | |
| // It's a recursive folder structure, let's find the real parent group | |
| if (sg->GetFullName() != sg->GetName()) { | |
| std::string curr_folder = cmStrCat(target, '/'); | |
| for (auto const& folder : cmTokenize(sg->GetFullName(), '\\')) { | |
| curr_folder += folder; | |
| auto const i_folder = this->GroupNameMap.find(curr_folder); | |
| // Create new folder | |
| if (i_folder == this->GroupNameMap.end()) { | |
| cmXCodeObject* group = this->CreatePBXGroup(tgroup, folder); | |
| this->GroupNameMap[curr_folder] = group; | |
| tgroup = group; | |
| } else { | |
| tgroup = i_folder->second; | |
| } | |
| curr_folder += '\\'; | |
| } | |
| return tgroup; | |
| } | |
| cmXCodeObject* group = this->CreatePBXGroup(tgroup, sg->GetName()); | |
| this->GroupNameMap[s] = group; | |
| return group; | |
| } | |
| bool cmGlobalXCodeGenerator::CreateXCodeObjects( | |
| cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators) | |
| { | |
| this->ClearXCodeObjects(); | |
| this->RootObject = nullptr; | |
| this->MainGroupChildren = nullptr; | |
| this->FrameworkGroup = nullptr; | |
| cmXCodeObject* group = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| group->AddAttribute("COPY_PHASE_STRIP", this->CreateString("NO")); | |
| cmXCodeObject* listObjs = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (std::string const& CurrentConfigurationType : | |
| this->CurrentConfigurationTypes) { | |
| cmXCodeObject* buildStyle = | |
| this->CreateObject(cmXCodeObject::PBXBuildStyle); | |
| std::string const& name = CurrentConfigurationType; | |
| buildStyle->AddAttribute("name", this->CreateString(name)); | |
| buildStyle->SetComment(name); | |
| cmXCodeObject* sgroup = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| sgroup->AddAttribute("COPY_PHASE_STRIP", this->CreateString("NO")); | |
| buildStyle->AddAttribute("buildSettings", sgroup); | |
| listObjs->AddObject(buildStyle); | |
| } | |
| cmXCodeObject* mainGroup = this->CreateObject(cmXCodeObject::PBXGroup); | |
| this->MainGroupChildren = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| mainGroup->AddAttribute("children", this->MainGroupChildren); | |
| mainGroup->AddAttribute("sourceTree", this->CreateString("<group>")); | |
| // now create the cmake groups | |
| if (!this->CreateGroups(generators)) { | |
| return false; | |
| } | |
| cmXCodeObject* productGroup = this->CreateObject(cmXCodeObject::PBXGroup); | |
| productGroup->AddAttribute("name", this->CreateString("Products")); | |
| productGroup->AddAttribute("sourceTree", this->CreateString("<group>")); | |
| cmXCodeObject* productGroupChildren = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| productGroup->AddAttribute("children", productGroupChildren); | |
| this->MainGroupChildren->AddObject(productGroup); | |
| this->FrameworkGroup = this->CreateObject(cmXCodeObject::PBXGroup); | |
| this->FrameworkGroup->AddAttribute("name", this->CreateString("Frameworks")); | |
| this->FrameworkGroup->AddAttribute("sourceTree", | |
| this->CreateString("<group>")); | |
| cmXCodeObject* frameworkGroupChildren = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| this->FrameworkGroup->AddAttribute("children", frameworkGroupChildren); | |
| this->MainGroupChildren->AddObject(this->FrameworkGroup); | |
| this->ResourcesGroup = this->CreateObject(cmXCodeObject::PBXGroup); | |
| this->ResourcesGroup->AddAttribute("name", this->CreateString("Resources")); | |
| this->ResourcesGroup->AddAttribute("sourceTree", | |
| this->CreateString("<group>")); | |
| cmXCodeObject* ResourcesGroupChildren = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| this->ResourcesGroup->AddAttribute("children", ResourcesGroupChildren); | |
| this->MainGroupChildren->AddObject(this->ResourcesGroup); | |
| this->RootObject = this->CreateObject(cmXCodeObject::PBXProject); | |
| this->RootObject->SetComment("Project object"); | |
| std::string project_id = cmStrCat("PROJECT_", root->GetProjectName()); | |
| this->RootObject->SetId( | |
| this->GetOrCreateId(project_id, this->RootObject->GetId())); | |
| group = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| this->RootObject->AddAttribute("mainGroup", | |
| this->CreateObjectReference(mainGroup)); | |
| this->RootObject->AddAttribute("buildSettings", group); | |
| this->RootObject->AddAttribute("buildStyles", listObjs); | |
| this->RootObject->AddAttribute("hasScannedForEncodings", | |
| this->CreateString("0")); | |
| group = this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| group->AddAttribute("BuildIndependentTargetsInParallel", | |
| this->CreateString("YES")); | |
| std::ostringstream v; | |
| v << std::setfill('0') << std::setw(4) << XcodeVersion * 10; | |
| group->AddAttribute("LastUpgradeCheck", this->CreateString(v.str())); | |
| this->RootObject->AddAttribute("attributes", group); | |
| this->RootObject->AddAttribute("compatibilityVersion", | |
| this->CreateString("Xcode 3.2")); | |
| // Point Xcode at the top of the source tree. | |
| { | |
| std::string pdir = | |
| this->RelativeToBinary(root->GetCurrentSourceDirectory()); | |
| this->RootObject->AddAttribute("projectDirPath", this->CreateString(pdir)); | |
| this->RootObject->AddAttribute("projectRoot", this->CreateString("")); | |
| } | |
| cmXCodeObject* configlist = | |
| this->CreateObject(cmXCodeObject::XCConfigurationList); | |
| cmXCodeObject* buildConfigurations = | |
| this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| using Configs = std::vector<std::pair<std::string, cmXCodeObject*>>; | |
| Configs configs; | |
| std::string defaultConfigName; | |
| for (auto const& name : this->CurrentConfigurationTypes) { | |
| if (defaultConfigName.empty()) { | |
| defaultConfigName = name; | |
| } | |
| cmXCodeObject* config = | |
| this->CreateObject(cmXCodeObject::XCBuildConfiguration); | |
| config->AddAttribute("name", this->CreateString(name)); | |
| configs.emplace_back(name, config); | |
| } | |
| if (defaultConfigName.empty()) { | |
| defaultConfigName = "Debug"; | |
| } | |
| for (auto& config : configs) { | |
| buildConfigurations->AddObject(config.second); | |
| } | |
| configlist->AddAttribute("buildConfigurations", buildConfigurations); | |
| std::string comment = cmStrCat("Build configuration list for PBXProject \"", | |
| this->CurrentProject, '"'); | |
| configlist->SetComment(comment); | |
| configlist->AddAttribute("defaultConfigurationIsVisible", | |
| this->CreateString("0")); | |
| configlist->AddAttribute("defaultConfigurationName", | |
| this->CreateString(defaultConfigName)); | |
| cmXCodeObject* buildSettings = | |
| this->CreateObject(cmXCodeObject::ATTRIBUTE_GROUP); | |
| cmValue sysroot = this->CurrentMakefile->GetDefinition("CMAKE_OSX_SYSROOT"); | |
| cmValue deploymentTarget = | |
| this->CurrentMakefile->GetDefinition("CMAKE_OSX_DEPLOYMENT_TARGET"); | |
| if (sysroot) { | |
| buildSettings->AddAttribute("SDKROOT", this->CreateString(*sysroot)); | |
| } | |
| // recompute this as it may have been changed since enable language | |
| this->ComputeArchitectures(this->CurrentMakefile); | |
| std::string const archs = cmJoin(this->Architectures, " "); | |
| if (archs.empty()) { | |
| // Tell Xcode to use NATIVE_ARCH instead of ARCHS. | |
| buildSettings->AddAttribute("ONLY_ACTIVE_ARCH", this->CreateString("YES")); | |
| // When targeting macOS, use only the host architecture. | |
| if (this->SystemName == "Darwin"_s && | |
| (!cmNonempty(sysroot) || | |
| cmSystemTools::LowerCase(*sysroot).find("macos") != | |
| std::string::npos)) { | |
| buildSettings->AddAttribute("ARCHS", | |
| this->CreateString("$(NATIVE_ARCH_ACTUAL)")); | |
| } | |
| } else { | |
| // Tell Xcode to use ARCHS (ONLY_ACTIVE_ARCH defaults to NO). | |
| buildSettings->AddAttribute("ARCHS", this->CreateString(archs)); | |
| } | |
| if (cmNonempty(deploymentTarget)) { | |
| buildSettings->AddAttribute(GetDeploymentPlatform(root->GetMakefile()), | |
| this->CreateString(*deploymentTarget)); | |
| } | |
| if (!this->GeneratorToolset.empty()) { | |
| buildSettings->AddAttribute("GCC_VERSION", | |
| this->CreateString(this->GeneratorToolset)); | |
| } | |
| if (this->GetLanguageEnabled("Swift")) { | |
| std::string swiftVersion; | |
| if (cmValue vers = this->CurrentMakefile->GetDefinition( | |
| "CMAKE_Swift_LANGUAGE_VERSION")) { | |
| swiftVersion = *vers; | |
| } else if (this->XcodeVersion >= 102) { | |
| swiftVersion = "4.0"; | |
| } else if (this->XcodeVersion >= 83) { | |
| swiftVersion = "3.0"; | |
| } else { | |
| swiftVersion = "2.3"; | |
| } | |
| buildSettings->AddAttribute("SWIFT_VERSION", | |
| this->CreateString(swiftVersion)); | |
| } | |
| std::string const symroot = this->GetSymrootDir(); | |
| buildSettings->AddAttribute("SYMROOT", this->CreateString(symroot)); | |
| // Inside a try_compile project, do not require signing on any platform. | |
| if (this->CMakeInstance->GetIsInTryCompile()) { | |
| buildSettings->AddAttribute("CODE_SIGNING_ALLOWED", | |
| this->CreateString("NO")); | |
| } | |
| // This code supports the OLD behavior of CMP0157. We should be able to | |
| // remove computing the debug configuration set once the old behavior is | |
| // removed. | |
| auto debugConfigs = this->GetCMakeInstance()->GetDebugConfigs(); | |
| std::set<std::string> debugConfigSet(debugConfigs.begin(), | |
| debugConfigs.end()); | |
| for (auto& config : configs) { | |
| CreateGlobalXCConfigSettings(root, config.second, config.first); | |
| cmXCodeObject* buildSettingsForCfg = this->CreateFlatClone(buildSettings); | |
| // Supports the OLD behavior of CMP0157. CMP0157 OLD behavior globally set | |
| // wholemodule compilation for all non-debug configurations, for all | |
| // targets. | |
| if (this->CurrentMakefile | |
| ->GetDefinition("CMAKE_Swift_COMPILATION_MODE_DEFAULT") | |
| .IsEmpty()) { | |
| if (debugConfigSet.count(cmSystemTools::UpperCase(config.first)) == 0) { | |
| buildSettingsForCfg->AddAttribute("SWIFT_COMPILATION_MODE", | |
| this->CreateString("wholemodule")); | |
| } | |
| } | |
| // Put this last so it can override existing settings | |
| // Convert "CMAKE_XCODE_ATTRIBUTE_*" variables directly. | |
| for (auto const& var : this->CurrentMakefile->GetDefinitions()) { | |
| if (cmHasLiteralPrefix(var, "CMAKE_XCODE_ATTRIBUTE_")) { | |
| std::string attribute = var.substr(22); | |
| this->FilterConfigurationAttribute(config.first, attribute); | |
| if (!attribute.empty()) { | |
| std::string processed = cmGeneratorExpression::Evaluate( | |
| this->CurrentMakefile->GetSafeDefinition(var), | |
| this->CurrentLocalGenerator, config.first); | |
| buildSettingsForCfg->AddAttribute(attribute, | |
| this->CreateString(processed)); | |
| } | |
| } | |
| } | |
| // store per-config buildSettings into configuration object | |
| config.second->AddAttribute("buildSettings", buildSettingsForCfg); | |
| } | |
| this->RootObject->AddAttribute("buildConfigurationList", | |
| this->CreateObjectReference(configlist)); | |
| std::vector<cmXCodeObject*> targets; | |
| for (auto& generator : generators) { | |
| if (!this->CreateXCodeTargets(generator, targets)) { | |
| return false; | |
| } | |
| for (auto const& ccRoot : this->CustomCommandRoots) { | |
| if (ccRoot.second.size() > 1) { | |
| std::string e = "The custom command "; | |
| std::vector<std::string> const& outputs = | |
| ccRoot.first->GetCustomCommand()->GetOutputs(); | |
| if (!outputs.empty()) { | |
| e = cmStrCat(e, "generating\n ", outputs[0]); | |
| } else { | |
| e = cmStrCat(e, "driven by\n ", ccRoot.first->GetFullPath()); | |
| } | |
| e = cmStrCat(e, "\nis attached to multiple targets:"); | |
| for (cmGeneratorTarget const* gt : ccRoot.second) { | |
| e = cmStrCat(e, "\n ", gt->GetName()); | |
| } | |
| e = cmStrCat( | |
| e, | |
| "\nbut none of these is a common dependency of the other(s). " | |
| "This is not allowed by the Xcode \"new build system\"."); | |
| generator->IssueMessage(MessageType::FATAL_ERROR, e); | |
| return false; | |
| } | |
| } | |
| this->CustomCommandRoots.clear(); | |
| } | |
| // loop over all targets and add link and depend info | |
| for (auto* t : targets) { | |
| this->AddDependAndLinkInformation(t); | |
| this->AddEmbeddedFrameworks(t); | |
| this->AddEmbeddedPlugIns(t); | |
| this->AddEmbeddedAppExtensions(t); | |
| this->AddEmbeddedExtensionKitExtensions(t); | |
| this->AddEmbeddedResources(t); | |
| this->AddEmbeddedXPCServices(t); | |
| // Inherit project-wide values for any target-specific search paths. | |
| this->InheritBuildSettingAttribute(t, "HEADER_SEARCH_PATHS"); | |
| this->InheritBuildSettingAttribute(t, "SYSTEM_HEADER_SEARCH_PATHS"); | |
| this->InheritBuildSettingAttribute(t, "FRAMEWORK_SEARCH_PATHS"); | |
| this->InheritBuildSettingAttribute(t, "SYSTEM_FRAMEWORK_SEARCH_PATHS"); | |
| this->InheritBuildSettingAttribute(t, "LIBRARY_SEARCH_PATHS"); | |
| this->InheritBuildSettingAttribute(t, "LD_RUNPATH_SEARCH_PATHS"); | |
| this->InheritBuildSettingAttribute(t, "GCC_PREPROCESSOR_DEFINITIONS"); | |
| this->InheritBuildSettingAttribute(t, "OTHER_CFLAGS"); | |
| this->InheritBuildSettingAttribute(t, "OTHER_LDFLAGS"); | |
| this->InheritBuildSettingAttribute(t, "OTHER_SWIFT_FLAGS"); | |
| this->InheritBuildSettingAttribute(t, | |
| "SWIFT_ACTIVE_COMPILATION_CONDITIONS"); | |
| } | |
| if (this->XcodeBuildSystem == BuildSystem::One) { | |
| this->CreateXCodeDependHackMakefile(targets); | |
| } | |
| // now add all targets to the root object | |
| cmXCodeObject* allTargets = this->CreateObject(cmXCodeObject::OBJECT_LIST); | |
| for (auto* t : targets) { | |
| allTargets->AddObject(t); | |
| cmXCodeObject* productRef = t->GetAttribute("productReference"); | |
| if (productRef) { | |
| productGroupChildren->AddObject(productRef->GetObject()); | |
| } | |
| } | |
| this->RootObject->AddAttribute("targets", allTargets); | |
| return true; | |
| } | |
| std::string cmGlobalXCodeGenerator::GetSymrootDir() const | |
| { | |
| return cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(), "/build"); | |
| } | |
| std::string cmGlobalXCodeGenerator::GetTargetTempDir( | |
| cmGeneratorTarget const* gt, std::string const& configName) const | |
| { | |
| // Use a path inside the SYMROOT. | |
| return cmStrCat(this->GetSymrootDir(), '/', gt->GetName(), ".build/", | |
| configName); | |
| } | |
| void cmGlobalXCodeGenerator::ComputeArchitectures(cmMakefile* mf) | |
| { | |
| this->Architectures.clear(); | |
| cmList::append(this->Architectures, | |
| mf->GetDefinition("CMAKE_OSX_ARCHITECTURES")); | |
| if (this->Architectures.empty()) { | |
| cmList::append(this->Architectures, | |
| mf->GetDefinition("_CMAKE_APPLE_ARCHS_DEFAULT")); | |
| } | |
| if (this->Architectures.empty()) { | |
| // With no ARCHS we use ONLY_ACTIVE_ARCH and possibly a | |
| // platform-specific default ARCHS placeholder value. | |
| // Look up the arch that Xcode chooses in this case. | |
| if (cmValue arch = mf->GetDefinition("CMAKE_XCODE_ARCHS")) { | |
| this->ObjectDirArchDefault = *arch; | |
| // We expect only one arch but choose the first just in case. | |
| std::string::size_type pos = this->ObjectDirArchDefault.find(';'); | |
| if (pos != std::string::npos) { | |
| this->ObjectDirArchDefault = this->ObjectDirArchDefault.substr(0, pos); | |
| } | |
| } | |
| } | |
| this->ComputeObjectDirArch(mf); | |
| } | |
| void cmGlobalXCodeGenerator::ComputeObjectDirArch(cmMakefile* mf) | |
| { | |
| if (this->Architectures.size() > 1 || this->UseEffectivePlatformName(mf)) { | |
| this->ObjectDirArch = "$(CURRENT_ARCH)"; | |
| } else if (!this->Architectures.empty()) { | |
| this->ObjectDirArch = this->Architectures[0]; | |
| } else { | |
| this->ObjectDirArch = this->ObjectDirArchDefault; | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::CreateXCodeDependHackMakefile( | |
| std::vector<cmXCodeObject*>& targets) | |
| { | |
| cmGeneratedFileStream makefileStream(this->CurrentXCodeHackMakefile); | |
| if (!makefileStream) { | |
| cmSystemTools::Error( | |
| cmStrCat("Could not create ", this->CurrentXCodeHackMakefile)); | |
| return; | |
| } | |
| makefileStream.SetCopyIfDifferent(true); | |
| // one more pass for external depend information not handled | |
| // correctly by xcode | |
| /* clang-format off */ | |
| makefileStream << "# DO NOT EDIT\n" | |
| "# This makefile makes sure all linkable targets are\n" | |
| "# up-to-date with anything they link to\n" | |
| "default:\n" | |
| "\techo \"Do not invoke directly\"\n" | |
| "\n"; | |
| /* clang-format on */ | |
| std::set<std::string> dummyRules; | |
| // Write rules to help Xcode relink things at the right time. | |
| /* clang-format off */ | |
| makefileStream << | |
| "# Rules to remove targets that are older than anything to which they\n" | |
| "# link. This forces Xcode to relink the targets from scratch. It\n" | |
| "# does not seem to check these dependencies itself.\n"; | |
| /* clang-format on */ | |
| for (auto const& configName : this->CurrentConfigurationTypes) { | |
| for (auto* target : targets) { | |
| cmGeneratorTarget* gt = target->GetTarget(); | |
| if (gt->GetType() == cmStateEnums::EXECUTABLE || | |
| gt->GetType() == cmStateEnums::OBJECT_LIBRARY || | |
| gt->GetType() == cmStateEnums::STATIC_LIBRARY || | |
| gt->GetType() == cmStateEnums::SHARED_LIBRARY || | |
| gt->GetType() == cmStateEnums::MODULE_LIBRARY) { | |
| // Declare an entry point for the target post-build phase. | |
| makefileStream << this->PostBuildMakeTarget(gt->GetName(), configName) | |
| << ":\n"; | |
| } | |
| if (gt->GetType() == cmStateEnums::EXECUTABLE || | |
| gt->GetType() == cmStateEnums::STATIC_LIBRARY || | |
| gt->GetType() == cmStateEnums::SHARED_LIBRARY || | |
| gt->GetType() == cmStateEnums::MODULE_LIBRARY) { | |
| std::string tfull = gt->GetFullPath(configName); | |
| std::string trel = ConvertToMakefilePath(tfull); | |
| // Add this target to the post-build phases of its dependencies. | |
| auto const y = target->GetDependTargets().find(configName); | |
| if (y != target->GetDependTargets().end()) { | |
| for (auto const& deptgt : y->second) { | |
| makefileStream << this->PostBuildMakeTarget(deptgt, configName) | |
| << ": " << trel << '\n'; | |
| } | |
| } | |
| std::vector<cmGeneratorTarget*> objlibs; | |
| gt->GetObjectLibrariesInSources(objlibs); | |
| for (auto* objLib : objlibs) { | |
| makefileStream << this->PostBuildMakeTarget(objLib->GetName(), | |
| configName) | |
| << ": " << trel << '\n'; | |
| } | |
| // Create a rule for this target. | |
| makefileStream << trel << ':'; | |
| // List dependencies if any exist. | |
| auto const x = target->GetDependLibraries().find(configName); | |
| if (x != target->GetDependLibraries().end()) { | |
| for (auto const& deplib : x->second) { | |
| std::string file = ConvertToMakefilePath(deplib); | |
| makefileStream << "\\\n\t" << file; | |
| dummyRules.insert(file); | |
| } | |
| } | |
| for (auto* objLib : objlibs) { | |
| std::string const objLibName = objLib->GetName(); | |
| std::string d = cmStrCat(this->GetTargetTempDir(gt, configName), | |
| "/lib", objLibName, ".a"); | |
| std::string dependency = ConvertToMakefilePath(d); | |
| makefileStream << "\\\n\t" << dependency; | |
| dummyRules.insert(dependency); | |
| } | |
| // Write the action to remove the target if it is out of date. | |
| makefileStream << "\n" | |
| "\t/bin/rm -f " | |
| << ConvertToMakefilePath(tfull) << '\n'; | |
| // if building for more than one architecture | |
| // then remove those executables as well | |
| if (this->Architectures.size() > 1) { | |
| std::string universal = | |
| cmStrCat(this->GetTargetTempDir(gt, configName), "/$(OBJDIR)/"); | |
| for (auto const& architecture : this->Architectures) { | |
| std::string universalFile = cmStrCat(universal, architecture, '/', | |
| gt->GetFullName(configName)); | |
| makefileStream << "\t/bin/rm -f " | |
| << ConvertToMakefilePath(universalFile) << '\n'; | |
| } | |
| } | |
| makefileStream << "\n\n"; | |
| } | |
| } | |
| } | |
| makefileStream << "\n\n" | |
| "# For each target create a dummy rule" | |
| "so the target does not have to exist\n"; | |
| for (auto const& dummyRule : dummyRules) { | |
| makefileStream << dummyRule << ":\n"; | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::OutputXCodeProject( | |
| cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators) | |
| { | |
| if (generators.empty()) { | |
| return; | |
| } | |
| if (!this->CreateXCodeObjects(root, generators)) { | |
| return; | |
| } | |
| std::string xcodeDir = cmStrCat(root->GetCurrentBinaryDirectory(), '/', | |
| root->GetProjectName(), ".xcodeproj"); | |
| cmSystemTools::MakeDirectory(xcodeDir); | |
| std::string xcodeProjFile = cmStrCat(xcodeDir, "/project.pbxproj"); | |
| cmGeneratedFileStream fout(xcodeProjFile); | |
| fout.SetCopyIfDifferent(true); | |
| if (!fout) { | |
| return; | |
| } | |
| this->WriteXCodePBXProj(fout, root, generators); | |
| bool hasGeneratedSchemes = this->OutputXCodeSharedSchemes(xcodeDir, root); | |
| this->OutputXCodeWorkspaceSettings(xcodeDir, hasGeneratedSchemes); | |
| this->ClearXCodeObjects(); | |
| // Since this call may have created new cache entries, save the cache: | |
| // | |
| root->GetMakefile()->GetCMakeInstance()->SaveCache( | |
| root->GetBinaryDirectory()); | |
| } | |
| bool cmGlobalXCodeGenerator::OutputXCodeSharedSchemes( | |
| std::string const& xcProjDir, cmLocalGenerator* root) | |
| { | |
| // collect all tests for the targets | |
| std::map<std::string, cmXCodeScheme::TestObjects> testables; | |
| for (auto const& obj : this->XCodeObjects) { | |
| if (obj->GetType() != cmXCodeObject::OBJECT || | |
| obj->GetIsA() != cmXCodeObject::PBXNativeTarget) { | |
| continue; | |
| } | |
| if (!obj->GetTarget()->IsXCTestOnApple()) { | |
| continue; | |
| } | |
| cmValue testee = obj->GetTarget()->GetProperty("XCTEST_TESTEE"); | |
| if (!testee) { | |
| continue; | |
| } | |
| testables[*testee].push_back(obj.get()); | |
| } | |
| // generate scheme | |
| bool ret = false; | |
| // Since the lowest available Xcode version for testing was 6.4, | |
| // I'm setting this as a limit then | |
| if (this->XcodeVersion >= 64) { | |
| for (auto const& obj : this->XCodeObjects) { | |
| if (obj->GetType() == cmXCodeObject::OBJECT && | |
| (obj->GetIsA() == cmXCodeObject::PBXNativeTarget || | |
| obj->GetIsA() == cmXCodeObject::PBXAggregateTarget) && | |
| (root->GetMakefile()->GetCMakeInstance()->GetIsInTryCompile() || | |
| obj->GetTarget()->GetPropertyAsBool("XCODE_GENERATE_SCHEME"))) { | |
| std::string const& targetName = obj->GetTarget()->GetName(); | |
| cmXCodeScheme schm(root, obj.get(), testables[targetName], | |
| this->CurrentConfigurationTypes, | |
| this->XcodeVersion); | |
| schm.WriteXCodeSharedScheme(xcProjDir, | |
| this->RelativeToSource(xcProjDir)); | |
| ret = true; | |
| } | |
| } | |
| } | |
| return ret; | |
| } | |
| void cmGlobalXCodeGenerator::OutputXCodeWorkspaceSettings( | |
| std::string const& xcProjDir, bool hasGeneratedSchemes) | |
| { | |
| std::string xcodeSharedDataDir = | |
| cmStrCat(xcProjDir, "/project.xcworkspace/xcshareddata"); | |
| cmSystemTools::MakeDirectory(xcodeSharedDataDir); | |
| std::string workspaceSettingsFile = | |
| cmStrCat(xcodeSharedDataDir, "/WorkspaceSettings.xcsettings"); | |
| cmGeneratedFileStream fout(workspaceSettingsFile); | |
| fout.SetCopyIfDifferent(true); | |
| if (!fout) { | |
| return; | |
| } | |
| cmXMLWriter xout(fout); | |
| xout.StartDocument(); | |
| xout.Doctype("plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"" | |
| "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\""); | |
| xout.StartElement("plist"); | |
| xout.Attribute("version", "1.0"); | |
| xout.StartElement("dict"); | |
| if (this->XcodeVersion >= 100) { | |
| xout.Element("key", "BuildSystemType"); | |
| switch (this->XcodeBuildSystem) { | |
| case BuildSystem::One: | |
| xout.Element("string", "Original"); | |
| if (this->XcodeVersion >= 130) { | |
| xout.Element("key", "DisableBuildSystemDeprecationDiagnostic"); | |
| } else { | |
| xout.Element("key", "DisableBuildSystemDeprecationWarning"); | |
| } | |
| xout.Element("true"); | |
| break; | |
| case BuildSystem::Twelve: | |
| xout.Element("string", "Latest"); | |
| break; | |
| } | |
| } | |
| if (hasGeneratedSchemes) { | |
| xout.Element("key", | |
| "IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded"); | |
| xout.Element("false"); | |
| } | |
| xout.EndElement(); // dict | |
| xout.EndElement(); // plist | |
| xout.EndDocument(); | |
| } | |
| void cmGlobalXCodeGenerator::WriteXCodePBXProj(std::ostream& fout, | |
| cmLocalGenerator*, | |
| std::vector<cmLocalGenerator*>&) | |
| { | |
| SortXCodeObjects(); | |
| fout << "// !$*UTF8*$!\n" | |
| "{\n"; | |
| cmXCodeObject::Indent(1, fout); | |
| fout << "archiveVersion = 1;\n"; | |
| cmXCodeObject::Indent(1, fout); | |
| fout << "classes = {\n"; | |
| cmXCodeObject::Indent(1, fout); | |
| fout << "};\n"; | |
| cmXCodeObject::Indent(1, fout); | |
| fout << "objectVersion = 46;\n"; | |
| cmXCode21Object::PrintList(this->XCodeObjects, fout); | |
| cmXCodeObject::Indent(1, fout); | |
| fout << "rootObject = " << this->RootObject->GetId() | |
| << " /* Project object */;\n" | |
| "}\n"; | |
| } | |
| char const* cmGlobalXCodeGenerator::GetCMakeCFGIntDir() const | |
| { | |
| return "$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; | |
| } | |
| std::string cmGlobalXCodeGenerator::ExpandCFGIntDir( | |
| std::string const& str, std::string const& config) const | |
| { | |
| std::string replace1 = "$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; | |
| std::string replace2 = "$(CONFIGURATION)"; | |
| std::string tmp = str; | |
| for (std::string::size_type i = tmp.find(replace1); i != std::string::npos; | |
| i = tmp.find(replace1, i)) { | |
| tmp.replace(i, replace1.size(), config); | |
| i += config.size(); | |
| } | |
| for (std::string::size_type i = tmp.find(replace2); i != std::string::npos; | |
| i = tmp.find(replace2, i)) { | |
| tmp.replace(i, replace2.size(), config); | |
| i += config.size(); | |
| } | |
| return tmp; | |
| } | |
| cmDocumentationEntry cmGlobalXCodeGenerator::GetDocumentation() | |
| { | |
| return { cmGlobalXCodeGenerator::GetActualName(), | |
| "Generate Xcode project files." }; | |
| } | |
| std::string cmGlobalXCodeGenerator::RelativeToSource(std::string const& p) | |
| { | |
| std::string const& rootSrc = | |
| this->CurrentRootGenerator->GetCurrentSourceDirectory(); | |
| if (cmSystemTools::IsSubDirectory(p, rootSrc)) { | |
| return cmSystemTools::ForceToRelativePath(rootSrc, p); | |
| } | |
| return p; | |
| } | |
| std::string cmGlobalXCodeGenerator::RelativeToRootBinary(std::string const& p) | |
| { | |
| std::string binaryDirectory = | |
| this->CurrentRootGenerator->GetCurrentBinaryDirectory(); | |
| if (cmSystemTools::IsSubDirectory(p, binaryDirectory)) { | |
| binaryDirectory = cmSystemTools::ForceToRelativePath(binaryDirectory, p); | |
| } | |
| return binaryDirectory; | |
| } | |
| std::string cmGlobalXCodeGenerator::RelativeToBinary(std::string const& p) | |
| { | |
| return this->CurrentRootGenerator->MaybeRelativeToCurBinDir(p); | |
| } | |
| std::string cmGlobalXCodeGenerator::XCodeEscapePath(std::string const& p) | |
| { | |
| if (p.find_first_of(" []") != std::string::npos) { | |
| std::string t = cmStrCat('"', p, '"'); | |
| return t; | |
| } | |
| return p; | |
| } | |
| void cmGlobalXCodeGenerator::AppendDirectoryForConfig( | |
| std::string const& prefix, std::string const& config, | |
| std::string const& suffix, std::string& dir) | |
| { | |
| if (!config.empty()) { | |
| dir += prefix; | |
| dir += config; | |
| dir += suffix; | |
| } | |
| } | |
| std::string cmGlobalXCodeGenerator::LookupFlags( | |
| std::string const& varNamePrefix, std::string const& varNameLang, | |
| std::string const& varNameSuffix, cmGeneratorTarget const* gt, | |
| std::string const& default_flags) | |
| { | |
| if (!varNameLang.empty()) { | |
| std::string varName = cmStrCat(varNamePrefix, varNameLang, varNameSuffix); | |
| if (cmValue varValue = this->CurrentMakefile->GetDefinition(varName)) { | |
| if (!varValue->empty()) { | |
| std::string flags; | |
| this->CurrentLocalGenerator->AppendFlags( | |
| flags, *varValue, varName, gt, cmBuildStep::Link, varNameLang); | |
| return flags; | |
| } | |
| } | |
| } | |
| return default_flags; | |
| } | |
| void cmGlobalXCodeGenerator::AppendDefines(BuildObjectListOrString& defs, | |
| char const* defines_list, | |
| bool dflag) | |
| { | |
| // Skip this if there are no definitions. | |
| if (!defines_list) { | |
| return; | |
| } | |
| // Expand the list of definitions. | |
| cmList defines{ defines_list }; | |
| // Store the definitions in the string. | |
| this->AppendDefines(defs, defines, dflag); | |
| } | |
| void cmGlobalXCodeGenerator::AppendDefines( | |
| BuildObjectListOrString& defs, std::vector<std::string> const& defines, | |
| bool dflag) | |
| { | |
| // GCC_PREPROCESSOR_DEFINITIONS is a space-separated list of definitions. | |
| std::string def; | |
| for (auto const& define : defines) { | |
| // Start with -D if requested. | |
| if (dflag && !cmHasLiteralPrefix(define, "-D")) { | |
| def = cmStrCat("-D", define); | |
| } else if (!dflag && cmHasLiteralPrefix(define, "-D")) { | |
| def = define.substr(2); | |
| } else { | |
| def = define; | |
| } | |
| // Append the flag with needed escapes. | |
| std::string tmp; | |
| this->AppendFlag(tmp, def); | |
| defs.Add(tmp); | |
| } | |
| } | |
| void cmGlobalXCodeGenerator::AppendFlag(std::string& flags, | |
| std::string const& flag) const | |
| { | |
| // Short-circuit for an empty flag. | |
| if (flag.empty()) { | |
| return; | |
| } | |
| // Separate from previous flags. | |
| if (!flags.empty()) { | |
| flags += ' '; | |
| } | |
| // Check if the flag needs quoting. | |
| bool quoteFlag = | |
| flag.find_first_of("`~!@#$%^&*()+={}[]|:;\"'<>,.? ") != std::string::npos; | |
| // We escape a flag as follows: | |
| // - Place each flag in single quotes '' | |
| // - Escape a single quote as \' | |
| // - Escape a backslash as \\ since it itself is an escape | |
| // Note that in the code below we need one more level of escapes for | |
| // C string syntax in this source file. | |
| // | |
| // The final level of escaping is done when the string is stored | |
| // into the project file by cmXCodeObject::PrintString. | |
| if (quoteFlag) { | |
| // Open single quote. | |
| flags += '\''; | |
| } | |
| // Flag value with escaped quotes and backslashes. | |
| for (auto c : flag) { | |
| if (c == '\'') { | |
| flags += "'\\''"; | |
| } else if (c == '\\') { | |
| flags += "\\\\"; | |
| } else { | |
| flags += c; | |
| } | |
| } | |
| if (quoteFlag) { | |
| // Close single quote. | |
| flags += '\''; | |
| } | |
| } | |
| std::string cmGlobalXCodeGenerator::ComputeInfoPListLocation( | |
| cmGeneratorTarget* target) | |
| { | |
| std::string plist = | |
| cmStrCat(target->GetLocalGenerator()->GetCurrentBinaryDirectory(), | |
| "/CMakeFiles/", target->GetName(), ".dir/Info.plist"); | |
| return plist; | |
| } | |
| // Return true if the generated build tree may contain multiple builds. | |
| // i.e. "Can I build Debug and Release in the same tree?" | |
| bool cmGlobalXCodeGenerator::IsMultiConfig() const | |
| { | |
| // Newer Xcode versions are multi config: | |
| return true; | |
| } | |
| bool cmGlobalXCodeGenerator::HasKnownObjectFileLocation( | |
| cmTarget const& target, std::string* reason) const | |
| { | |
| auto objectDirArch = GetTargetObjectDirArch(target, this->ObjectDirArch); | |
| if (objectDirArch.find('$') != std::string::npos) { | |
| if (reason != nullptr) { | |
| *reason = " under Xcode with multiple architectures"; | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool cmGlobalXCodeGenerator::UseEffectivePlatformName(cmMakefile* mf) const | |
| { | |
| cmValue epnValue = this->GetCMakeInstance()->GetState()->GetGlobalProperty( | |
| "XCODE_EMIT_EFFECTIVE_PLATFORM_NAME"); | |
| if (!epnValue) { | |
| return mf->PlatformIsAppleEmbedded(); | |
| } | |
| return epnValue.IsOn(); | |
| } | |
| bool cmGlobalXCodeGenerator::ShouldStripResourcePath(cmMakefile*) const | |
| { | |
| // Xcode determines Resource location itself | |
| return true; | |
| } | |
| void cmGlobalXCodeGenerator::ComputeTargetObjectDirectory( | |
| cmGeneratorTarget* gt) const | |
| { | |
| auto objectDirArch = GetTargetObjectDirArch(*gt, this->ObjectDirArch); | |
| gt->ObjectDirectory = | |
| cmStrCat(this->GetTargetTempDir(gt, this->GetCMakeCFGIntDir()), | |
| "/$(OBJECT_FILE_DIR_normal:base)/", objectDirArch, '/'); | |
| } | |
| std::string cmGlobalXCodeGenerator::GetDeploymentPlatform(cmMakefile const* mf) | |
| { | |
| switch (mf->GetAppleSDKType()) { | |
| case cmMakefile::AppleSDK::AppleTVOS: | |
| case cmMakefile::AppleSDK::AppleTVSimulator: | |
| return "TVOS_DEPLOYMENT_TARGET"; | |
| case cmMakefile::AppleSDK::IPhoneOS: | |
| case cmMakefile::AppleSDK::IPhoneSimulator: | |
| return "IPHONEOS_DEPLOYMENT_TARGET"; | |
| case cmMakefile::AppleSDK::WatchOS: | |
| case cmMakefile::AppleSDK::WatchSimulator: | |
| return "WATCHOS_DEPLOYMENT_TARGET"; | |
| case cmMakefile::AppleSDK::XROS: | |
| case cmMakefile::AppleSDK::XRSimulator: | |
| return "XROS_DEPLOYMENT_TARGET"; | |
| case cmMakefile::AppleSDK::MacOS: | |
| default: | |
| return "MACOSX_DEPLOYMENT_TARGET"; | |
| } | |
| } |