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
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;
}
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;
}