bool Phase::ModuleMapResolver:: resolve(Phase::Environment const &phaseEnvironment, Phase::Context *phaseContext) const { pbxsetting::Environment const &environment = phaseEnvironment.targetEnvironment().environment(); std::unique_ptr<Tool::ModuleMapResolver> moduleMapResolver = Tool::ModuleMapResolver::Create(); if (moduleMapResolver == nullptr) { return false; } Tool::CopyResolver const *copyResolver = phaseContext->copyResolver(phaseEnvironment); if (copyResolver == nullptr) { fprintf(stderr, "warning: failed to get copy tool for module map\n"); return false; } /* * Create the module map info. Note this is required even if a module is not defined, in order to * have the module map info populated for other consumers of it. */ moduleMapResolver->resolve(&phaseContext->toolContext(), environment, phaseEnvironment.target()); /* * Check if creating module maps is even requested. */ if (pbxsetting::Type::ParseBoolean(environment.resolve("DEFINES_MODULE"))) { /* * Write the module maps as auxiliary temporary files, then copy them to the product.. */ Tool::ModuleMapInfo const &moduleMapInfo = phaseContext->toolContext().moduleMapInfo(); if (ext::optional<Tool::ModuleMapInfo::Entry> const &entry = moduleMapInfo.moduleMap()) { ProcessModuleMap(&phaseContext->toolContext(), environment, copyResolver, *entry); } else { fprintf(stderr, "warning: target defines module, but has no umbrella header\n"); } if (ext::optional<Tool::ModuleMapInfo::Entry> const &entry = moduleMapInfo.privateModuleMap()) { ProcessModuleMap(&phaseContext->toolContext(), environment, copyResolver, *entry); } } return true; }
static bool ResolveFrameworkStructure(Phase::Environment const &phaseEnvironment, Phase::Context *phaseContext) { pbxproj::PBX::Target::shared_ptr const &target = phaseEnvironment.target(); Target::Environment const &targetEnvironment = phaseEnvironment.targetEnvironment(); pbxsetting::Environment const &environment = targetEnvironment.environment(); /* Shallow bundles don't need any contents created. */ if (pbxsetting::Type::ParseBoolean(environment.resolve("SHALLOW_BUNDLE"))) { return true; } std::string targetBuildDirectory = environment.resolve("TARGET_BUILD_DIR"); std::string wrapperName = environment.resolve("WRAPPER_NAME"); /* * Define the possible symlinks that might need to be created, and where they should point to. */ enum Symlink { PublicHeaders, PrivateHeaders, XPCServices, Resources, Plugins, Modules, InfoPlist, Executable, }; /* Note the custom std::hash<int>. Needed as Symlink is an enum; also why Symlink is not an enum class. */ std::unordered_map<Symlink, pbxsetting::Value, std::hash<int>> symlinkDirectories = { { Symlink::PublicHeaders, pbxsetting::Value::Variable("PUBLIC_HEADERS_FOLDER_PATH") }, { Symlink::PrivateHeaders, pbxsetting::Value::Variable("PRIVATE_HEADERS_FOLDER_PATH") }, { Symlink::XPCServices, pbxsetting::Value::Variable("XPCSERVICES_FOLDER_PATH") }, { Symlink::Resources, pbxsetting::Value::Variable("UNLOCALIZED_RESOURCES_FOLDER_PATH") }, { Symlink::Plugins, pbxsetting::Value::Variable("PLUGINS_FOLDER_PATH") }, { Symlink::Modules, pbxsetting::Value::Parse("$(CONTENTS_FOLDER_PATH)/Modules") }, { Symlink::InfoPlist, pbxsetting::Value::Variable("INFOPLIST_PATH") }, { Symlink::Executable, pbxsetting::Value::Variable("EXECUTABLE_PATH") }, }; /* * Determine which symlinks are actually needed, based on what's in the target. */ std::unordered_set<Symlink, std::hash<int>> symlinks; /* Defines module: needs module symlink. */ if (pbxsetting::Type::ParseBoolean(environment.resolve("DEFINES_MODULE"))) { symlinks.insert(Symlink::Modules); } /* Has info plist: needs info plist & resources symlinks. */ std::string infoPlistFile = environment.resolve("INFOPLIST_FILE"); if (!infoPlistFile.empty()) { symlinks.insert(Symlink::InfoPlist); symlinks.insert(Symlink::Resources); } /* Has any outputs in the plugins directory: needs plugins. */ std::vector<Tool::Invocation> const &invocations = phaseContext->toolContext().invocations(); std::string pluginsDirectory = targetBuildDirectory + "/" + environment.resolve("PLUGINS_FOLDER_PATH"); if (!DirectoriesContainingOutputs(invocations, { pluginsDirectory }).empty()) { symlinks.insert(Symlink::Plugins); } /* Build phase contents affect other symlinks. */ for (pbxproj::PBX::BuildPhase::shared_ptr const &buildPhase : target->buildPhases()) { if (buildPhase->type() == pbxproj::PBX::BuildPhase::kTypeCopyFiles) { auto copyFiles = std::static_pointer_cast<pbxproj::PBX::CopyFilesBuildPhase>(buildPhase); /* Has copy files into XPC services: needs XPC services. */ std::string XPCServices = "XPCServices"; if (copyFiles->dstSubfolderSpec() == pbxproj::PBX::CopyFilesBuildPhase::kDestinationProducts && copyFiles->dstPath() == pbxsetting::Value::Parse("$(CONTENTS_FOLDER_PATH)/" + XPCServices)) { symlinks.insert(Symlink::XPCServices); } } else if (buildPhase->type() == pbxproj::PBX::BuildPhase::kTypeSources) { /* Has sources: needs executable. */ if (!buildPhase->files().empty()) { symlinks.insert(Symlink::Executable); } } else if (buildPhase->type() == pbxproj::PBX::BuildPhase::kTypeResources) { /* Has copy resources: needs resources. */ if (!buildPhase->files().empty()) { symlinks.insert(Symlink::Resources); } } else if (buildPhase->type() == pbxproj::PBX::BuildPhase::kTypeHeaders) { if (symlinks.find(Symlink::PublicHeaders) == symlinks.end() && symlinks.find(Symlink::PrivateHeaders) == symlinks.end()) { for (pbxproj::PBX::BuildFile::shared_ptr const &buildFile : buildPhase->files()) { std::vector<std::string> const &attributes = buildFile->attributes(); bool isPublic = std::find(attributes.begin(), attributes.end(), "Public") != attributes.end(); bool isPrivate = std::find(attributes.begin(), attributes.end(), "Private") != attributes.end(); /* Has a public header: needs public headers. */ if (isPublic) { symlinks.insert(Symlink::PublicHeaders); } /* Has a private header: needs private headers. */ if (isPrivate) { symlinks.insert(Symlink::PrivateHeaders); } } } } } if (Tool::SymlinkResolver const *symlinkResolver = phaseContext->symlinkResolver(phaseEnvironment)) { std::string versions = environment.resolve("VERSIONS_FOLDER_PATH"); std::string currentVersion = environment.resolve("CURRENT_VERSION"); std::string frameworkVersion = environment.resolve("FRAMEWORK_VERSION"); std::string frameworkDirectory = targetBuildDirectory + "/" + wrapperName; std::string versionsDirectory = targetBuildDirectory + "/" + versions; std::string currentVersionDirectory = versionsDirectory + "/" + currentVersion; std::string frameworkVersionDirectory = versionsDirectory + "/" + frameworkVersion; /* * The symlinks to create are now determined. Add the symlinks. */ for (Symlink const &symlink : symlinks) { pbxsetting::Value const &value = symlinkDirectories.at(symlink); std::string valueDirectory = targetBuildDirectory + "/" + environment.expand(value); std::string currentDirectory = currentVersionDirectory + "/" + FSUtil::GetRelativePath(valueDirectory, frameworkVersionDirectory); std::string rootDirectory = frameworkDirectory + "/" + FSUtil::GetBaseName(valueDirectory); /* Symlink /path/to.framework/Resources -> Versions/Current/Resources. */ std::string relativeCurrentDirectory = FSUtil::GetRelativePath(currentDirectory, frameworkDirectory); symlinkResolver->resolve(&phaseContext->toolContext(), frameworkDirectory, rootDirectory, relativeCurrentDirectory, true); } /* * Symlink the version directory from "Current". */ if (Tool::SymlinkResolver const *symlinkResolver = phaseContext->symlinkResolver(phaseEnvironment)) { /* Symlink Versions/Current -> Versions/A. */ std::string relativeFrameworkVersionDirectory = FSUtil::GetRelativePath(frameworkVersionDirectory, versionsDirectory); symlinkResolver->resolve(&phaseContext->toolContext(), versionsDirectory, currentVersionDirectory, relativeFrameworkVersionDirectory, true); } } else { return false; } return true; }