Example #1
0
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;
}