static bool ResolveBundleStructure(Phase::Environment const &phaseEnvironment, Phase::Context *phaseContext) { Target::Environment const &targetEnvironment = phaseEnvironment.targetEnvironment(); pbxsetting::Environment const &environment = targetEnvironment.environment(); /* * The setting names for directories to create if they will have contents. */ std::unordered_set<std::string> subdirectories = { "CONTENTS_FOLDER_PATH", "EXECUTABLE_FOLDER_PATH", "PUBLIC_HEADERS_FOLDER_PATH", "PRIVATE_HEADERS_FOLDER_PATH", "UNLOCALIZED_RESOURCES_FOLDER_PATH", "PLUGINS_FOLDER_PATH", }; std::string targetBuildDirectory = environment.resolve("TARGET_BUILD_DIR"); std::string wrapperName = environment.resolve("WRAPPER_NAME"); /* * Resolve the real path for each of the subdirectories. */ std::unordered_set<std::string> possibleDirectories; for (std::string const &subdirectory : subdirectories) { std::string directory = targetBuildDirectory + "/" + environment.resolve(subdirectory); possibleDirectories.insert(directory); } /* * Check which directories contain files from other invocations. */ std::vector<Tool::Invocation> const &invocations = phaseContext->toolContext().invocations(); std::unordered_set<std::string> populatedDirectories = DirectoriesContainingOutputs(invocations, possibleDirectories); /* * Create the directories that have contents. */ for (std::string const &directory : populatedDirectories) { if (directory == targetBuildDirectory + "/" + wrapperName) { // TODO(grp): This output will conflict with the 'Touch', since it's just creating the bundle root. continue; } if (Tool::MakeDirectoryResolver const *mkdirResolver = phaseContext->makeDirectoryResolver(phaseEnvironment)) { mkdirResolver->resolve(&phaseContext->toolContext(), directory, true); } } return true; }
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; }
bool Phase::ProductTypeResolver:: resolve(Phase::Environment const &phaseEnvironment, Phase::Context *phaseContext) const { Target::Environment const &targetEnvironment = phaseEnvironment.targetEnvironment(); pbxsetting::Environment const &environment = targetEnvironment.environment(); /* * Create the product structure. */ bool isBundle = false; bool isFramework = false; for (pbxspec::PBX::ProductType::shared_ptr productType = _productType; productType != nullptr; productType = productType->base()) { if (productType->identifier() == "com.apple.product-type.framework") { isFramework = true; } else if (productType->identifier() == "com.apple.product-type.bundle") { isBundle = true; } } if (isBundle) { if (!ResolveBundleStructure(phaseEnvironment, phaseContext)) { return false; } if (isFramework) { if (!ResolveFrameworkStructure(phaseEnvironment, phaseContext)) { return false; } } } /* * Copy and compose the info plist. */ if (_productType->hasInfoPlist()) { /* Note that INFOPLIST_FILE is the input, and INFOPLIST_PATH is the output. */ std::string infoPlistFile = environment.resolve("INFOPLIST_FILE"); if (!infoPlistFile.empty()) { if (pbxsetting::Type::ParseBoolean(environment.resolve("INFOPLIST_PREPROCESS"))) { // TODO(grp): Preprocess Info.plist using configuration from other build settings. } if (Tool::InfoPlistResolver const *infoPlistResolver = phaseContext->infoPlistResolver(phaseEnvironment)) { infoPlistResolver->resolve(&phaseContext->toolContext(), environment, infoPlistFile); } else { fprintf(stderr, "warning: could not find info plist tool\n"); } } } /* * Validate the product; specific checks are in the validation tool. */ if (pbxsetting::Type::ParseBoolean(environment.resolve("VALIDATE_PRODUCT"))) { if (_productType->validation() && _productType->validation()->validationToolSpec()) { std::string const &validationToolIdentifier = *_productType->validation()->validationToolSpec(); if (Tool::ToolResolver const *toolResolver = phaseContext->toolResolver(phaseEnvironment, validationToolIdentifier)) { // TODO(grp): Run validation tool. (void)toolResolver; } else { fprintf(stderr, "warning: could not find validation tool %s\n", validationToolIdentifier.c_str()); } } } /* * Touch the final product to note the build's ultimate creation time. */ if (_productType->isWrapper()) { std::string wrapperPath = environment.resolve("TARGET_BUILD_DIR") + "/" + environment.resolve("WRAPPER_NAME"); /* * Collect all existing tool outputs that end up inside the bundle. */ std::vector<std::string> outputs; for (Tool::Invocation const &invocation : phaseContext->toolContext().invocations()) { for (std::string const &output : invocation.outputs()) { if (output.compare(0, wrapperPath.size(), wrapperPath) == 0) { outputs.push_back(output); } } } if (Tool::TouchResolver const *touchResolver = phaseContext->touchResolver(phaseEnvironment)) { touchResolver->resolve(&phaseContext->toolContext(), wrapperPath, outputs); } else { fprintf(stderr, "warning: could not find touch tool\n"); } } /* * Register with launch services. This is only relevant for OS X. */ if (_productType->identifier() == "com.apple.product-type.application" && targetEnvironment.sdk()->platform()->name() == "macosx") { if (Tool::ToolResolver const *launchServicesResolver = phaseContext->toolResolver(phaseEnvironment, "com.apple.build-tasks.ls-register-url")) { // TODO(grp): Register with launch services. Note this needs the same dependencies as touch. (void)launchServicesResolver; } else { fprintf(stderr, "warning: could not find register tool\n"); } } return true; }
bool Phase::FrameworksResolver:: resolve(Phase::Environment const &phaseEnvironment, Phase::Context *phaseContext) { Target::Environment const &targetEnvironment = phaseEnvironment.targetEnvironment(); Tool::CompilationInfo const &compilationInfo = phaseContext->toolContext().compilationInfo(); std::unique_ptr<Tool::LinkerResolver> ldResolver = Tool::LinkerResolver::Create(phaseEnvironment, Tool::LinkerResolver::LinkerToolIdentifier()); std::unique_ptr<Tool::LinkerResolver> libtoolResolver = Tool::LinkerResolver::Create(phaseEnvironment, Tool::LinkerResolver::LibtoolToolIdentifier()); std::unique_ptr<Tool::LinkerResolver> lipoResolver = Tool::LinkerResolver::Create(phaseEnvironment, Tool::LinkerResolver::LipoToolIdentifier()); std::unique_ptr<Tool::ToolResolver> dsymutilResolver = Tool::ToolResolver::Create(phaseEnvironment, "com.apple.tools.dsymutil"); if (ldResolver == nullptr || libtoolResolver == nullptr || lipoResolver == nullptr || dsymutilResolver == nullptr) { fprintf(stderr, "error: couldn't get linker tools\n"); return false; } std::string binaryType = targetEnvironment.environment().resolve("MACH_O_TYPE"); Tool::LinkerResolver *linkerResolver = nullptr; std::string linkerExecutable; std::vector<std::string> linkerArguments; if (binaryType == "staticlib") { linkerResolver = libtoolResolver.get(); } else { linkerResolver = ldResolver.get(); linkerExecutable = compilationInfo.linkerDriver(); linkerArguments.insert(linkerArguments.end(), compilationInfo.linkerArguments().begin(), compilationInfo.linkerArguments().end()); } std::string workingDirectory = targetEnvironment.workingDirectory(); std::string productsDirectory = targetEnvironment.environment().resolve("BUILT_PRODUCTS_DIR"); std::vector<Phase::File> files = Phase::File::ResolveBuildFiles(phaseEnvironment, targetEnvironment.environment(), _buildPhase->files()); for (std::string const &variant : targetEnvironment.variants()) { pbxsetting::Environment variantEnvironment = targetEnvironment.environment(); variantEnvironment.insertFront(Phase::Environment::VariantLevel(variant), false); std::string variantIntermediatesName = variantEnvironment.resolve("EXECUTABLE_NAME") + variantEnvironment.resolve("EXECUTABLE_VARIANT_SUFFIX"); std::string variantIntermediatesDirectory = variantEnvironment.resolve("OBJECT_FILE_DIR_" + variant); std::string variantProductsPath = variantEnvironment.resolve("EXECUTABLE_PATH") + variantEnvironment.resolve("EXECUTABLE_VARIANT_SUFFIX"); std::string variantProductsOutput = productsDirectory + "/" + variantProductsPath; bool createUniversalBinary = targetEnvironment.architectures().size() > 1; std::vector<std::string> universalBinaryInputs; for (std::string const &arch : targetEnvironment.architectures()) { pbxsetting::Environment archEnvironment = variantEnvironment; archEnvironment.insertFront(Phase::Environment::ArchitectureLevel(arch), false); std::vector<std::string> sourceOutputs; auto it = phaseContext->toolContext().variantArchitectureInvocations().find(std::make_pair(variant, arch)); if (it != phaseContext->toolContext().variantArchitectureInvocations().end()) { std::vector<Tool::Invocation> const &sourceInvocations = it->second; for (Tool::Invocation const &invocation : sourceInvocations) { for (std::string const &output : invocation.outputs()) { // TODO(grp): Is this the right set of source outputs to link? if (FSUtil::GetFileExtension(output) == "o") { sourceOutputs.push_back(output); } } } } if (createUniversalBinary) { std::string architectureIntermediatesDirectory = variantIntermediatesDirectory + "/" + arch; std::string architectureIntermediatesOutput = architectureIntermediatesDirectory + "/" + variantIntermediatesName; linkerResolver->resolve(&phaseContext->toolContext(), archEnvironment, sourceOutputs, files, architectureIntermediatesOutput, linkerArguments, linkerExecutable); universalBinaryInputs.push_back(architectureIntermediatesOutput); } else { linkerResolver->resolve(&phaseContext->toolContext(), archEnvironment, sourceOutputs, files, variantProductsOutput, linkerArguments, linkerExecutable); } } if (createUniversalBinary) { lipoResolver->resolve(&phaseContext->toolContext(), variantEnvironment, universalBinaryInputs, { }, variantProductsOutput, { }); } if (variantEnvironment.resolve("DEBUG_INFORMATION_FORMAT") == "dwarf-with-dsym" && (binaryType != "staticlib" && binaryType != "mh_object")) { std::string dsymfile = variantEnvironment.resolve("DWARF_DSYM_FOLDER_PATH") + "/" + variantEnvironment.resolve("DWARF_DSYM_FILE_NAME"); dsymutilResolver->resolve(&phaseContext->toolContext(), variantEnvironment, { variantProductsOutput }, { dsymfile }); } } return true; }