/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmInstallTargetGenerator.h"

#include <assert.h>
#include <map>
#include <set>
#include <sstream>
#include <utility>

#include "cmComputeLinkInformation.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmInstallType.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmake.h"

cmInstallTargetGenerator::cmInstallTargetGenerator(
    const std::string& targetName, const char* dest, bool implib,
    const char*                     file_permissions,
    std::vector<std::string> const& configurations, const char* component,
    MessageLevel message, bool exclude_from_all, bool optional)
: cmInstallGenerator(dest, configurations, component, message, exclude_from_all)
, TargetName(targetName)
, Target(nullptr)
, FilePermissions(file_permissions)
, ImportLibrary(implib)
, Optional(optional)
{
    this->ActionsPerConfig = true;
    this->NamelinkMode     = NamelinkModeNone;
}

cmInstallTargetGenerator::~cmInstallTargetGenerator() {}

void
cmInstallTargetGenerator::GenerateScript(std::ostream& os)
{
    // Warn if installing an exclude-from-all target.
    if(this->Target->GetPropertyAsBool("EXCLUDE_FROM_ALL"))
    {
        std::ostringstream msg;
        msg << "WARNING: Target \"" << this->Target->GetName()
            << "\" has EXCLUDE_FROM_ALL set and will not be built by default "
            << "but an install rule has been provided for it.  CMake does "
            << "not define behavior for this case.";
        cmSystemTools::Message(msg.str().c_str(), "Warning");
    }

    // Perform the main install script generation.
    this->cmInstallGenerator::GenerateScript(os);
}

void
cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream&      os,
                                                  const std::string& config,
                                                  Indent             indent)
{
    cmStateEnums::TargetType targetType = this->Target->GetType();
    cmInstallType            type       = cmInstallType();
    switch(targetType)
    {
        case cmStateEnums::EXECUTABLE:
            type = cmInstallType_EXECUTABLE;
            break;
        case cmStateEnums::STATIC_LIBRARY:
            type = cmInstallType_STATIC_LIBRARY;
            break;
        case cmStateEnums::SHARED_LIBRARY:
            type = cmInstallType_SHARED_LIBRARY;
            break;
        case cmStateEnums::MODULE_LIBRARY:
            type = cmInstallType_MODULE_LIBRARY;
            break;
        case cmStateEnums::INTERFACE_LIBRARY:
            // Not reachable. We never create a cmInstallTargetGenerator for
            // an INTERFACE_LIBRARY.
            assert(false &&
                   "INTERFACE_LIBRARY targets have no installable outputs.");
            break;

        case cmStateEnums::OBJECT_LIBRARY:
            this->GenerateScriptForConfigObjectLibrary(os, config, indent);
            return;

        case cmStateEnums::UTILITY:
        case cmStateEnums::GLOBAL_TARGET:
        case cmStateEnums::UNKNOWN_LIBRARY:
            this->Target->GetLocalGenerator()->IssueMessage(
                cmake::INTERNAL_ERROR, "cmInstallTargetGenerator created with "
                                       "non-installable target.");
            return;
    }

    // Compute the build tree directory from which to copy the target.
    std::string fromDirConfig;
    if(this->Target->NeedRelinkBeforeInstall(config))
    {
        fromDirConfig =
            this->Target->GetLocalGenerator()->GetCurrentBinaryDirectory();
        fromDirConfig += cmake::GetCMakeFilesDirectory();
        fromDirConfig += "/CMakeRelink.dir/";
    } else
    {
        cmStateEnums::ArtifactType artifact =
            this->ImportLibrary ? cmStateEnums::ImportLibraryArtifact
                                : cmStateEnums::RuntimeBinaryArtifact;
        fromDirConfig = this->Target->GetDirectory(config, artifact);
        fromDirConfig += "/";
    }

    std::string toDir =
        this->ConvertToAbsoluteDestination(this->GetDestination(config));
    toDir += "/";

    // Compute the list of files to install for this target.
    std::vector<std::string> filesFrom;
    std::vector<std::string> filesTo;
    std::string              literal_args;

    if(targetType == cmStateEnums::EXECUTABLE)
    {
        // There is a bug in cmInstallCommand if this fails.
        assert(this->NamelinkMode == NamelinkModeNone);

        std::string targetName;
        std::string targetNameReal;
        std::string targetNameImport;
        std::string targetNamePDB;
        this->Target->GetExecutableNames(targetName, targetNameReal,
                                         targetNameImport, targetNamePDB,
                                         config);
        if(this->ImportLibrary)
        {
            std::string from1 = fromDirConfig + targetNameImport;
            std::string to1   = toDir + targetNameImport;
            filesFrom.push_back(std::move(from1));
            filesTo.push_back(std::move(to1));
            std::string targetNameImportLib;
            if(this->Target->GetImplibGNUtoMS(config, targetNameImport,
                                              targetNameImportLib))
            {
                filesFrom.push_back(fromDirConfig + targetNameImportLib);
                filesTo.push_back(toDir + targetNameImportLib);
            }

            // An import library looks like a static library.
            type = cmInstallType_STATIC_LIBRARY;
        } else
        {
            std::string from1 = fromDirConfig + targetName;
            std::string to1   = toDir + targetName;

            // Handle OSX Bundles.
            if(this->Target->IsAppBundleOnApple())
            {
                cmMakefile const* mf = this->Target->Target->GetMakefile();

                // Get App Bundle Extension
                const char* ext = this->Target->GetProperty("BUNDLE_EXTENSION");
                if(!ext)
                {
                    ext = "app";
                }

                // Install the whole app bundle directory.
                type = cmInstallType_DIRECTORY;
                literal_args += " USE_SOURCE_PERMISSIONS";
                from1 += ".";
                from1 += ext;

                // Tweaks apply to the binary inside the bundle.
                to1 += ".";
                to1 += ext;
                to1 += "/";
                if(!mf->PlatformIsAppleEmbedded())
                {
                    to1 += "Contents/MacOS/";
                }
                to1 += targetName;
            } else
            {
                // Tweaks apply to the real file, so list it first.
                if(targetNameReal != targetName)
                {
                    std::string from2 = fromDirConfig + targetNameReal;
                    std::string to2   = toDir += targetNameReal;
                    filesFrom.push_back(std::move(from2));
                    filesTo.push_back(std::move(to2));
                }
            }

            filesFrom.push_back(std::move(from1));
            filesTo.push_back(std::move(to1));
        }
    } else
    {
        std::string targetName;
        std::string targetNameSO;
        std::string targetNameReal;
        std::string targetNameImport;
        std::string targetNamePDB;
        this->Target->GetLibraryNames(targetName, targetNameSO, targetNameReal,
                                      targetNameImport, targetNamePDB, config);
        if(this->ImportLibrary)
        {
            // There is a bug in cmInstallCommand if this fails.
            assert(this->NamelinkMode == NamelinkModeNone);

            std::string from1 = fromDirConfig + targetNameImport;
            std::string to1   = toDir + targetNameImport;
            filesFrom.push_back(std::move(from1));
            filesTo.push_back(std::move(to1));
            std::string targetNameImportLib;
            if(this->Target->GetImplibGNUtoMS(config, targetNameImport,
                                              targetNameImportLib))
            {
                filesFrom.push_back(fromDirConfig + targetNameImportLib);
                filesTo.push_back(toDir + targetNameImportLib);
            }

            // An import library looks like a static library.
            type = cmInstallType_STATIC_LIBRARY;
        } else if(this->Target->IsFrameworkOnApple())
        {
            // There is a bug in cmInstallCommand if this fails.
            assert(this->NamelinkMode == NamelinkModeNone);

            // Install the whole framework directory.
            type = cmInstallType_DIRECTORY;
            literal_args += " USE_SOURCE_PERMISSIONS";

            std::string from1 = fromDirConfig + targetName;
            from1             = cmSystemTools::GetFilenamePath(from1);

            // Tweaks apply to the binary inside the bundle.
            std::string to1 = toDir + targetNameReal;

            filesFrom.push_back(std::move(from1));
            filesTo.push_back(std::move(to1));
        } else if(this->Target->IsCFBundleOnApple())
        {
            // Install the whole app bundle directory.
            type = cmInstallType_DIRECTORY;
            literal_args += " USE_SOURCE_PERMISSIONS";

            std::string targetNameBase =
                targetName.substr(0, targetName.find('/'));

            std::string from1 = fromDirConfig + targetNameBase;
            std::string to1   = toDir + targetName;

            filesFrom.push_back(std::move(from1));
            filesTo.push_back(std::move(to1));
        } else
        {
            bool haveNamelink = false;

            // Library link name.
            std::string fromName = fromDirConfig + targetName;
            std::string toName   = toDir + targetName;

            // Library interface name.
            std::string fromSOName;
            std::string toSOName;
            if(targetNameSO != targetName)
            {
                haveNamelink = true;
                fromSOName   = fromDirConfig + targetNameSO;
                toSOName     = toDir + targetNameSO;
            }

            // Library implementation name.
            std::string fromRealName;
            std::string toRealName;
            if(targetNameReal != targetName && targetNameReal != targetNameSO)
            {
                haveNamelink = true;
                fromRealName = fromDirConfig + targetNameReal;
                toRealName   = toDir + targetNameReal;
            }

            // Add the names based on the current namelink mode.
            if(haveNamelink)
            {
                // With a namelink we need to check the mode.
                if(this->NamelinkMode == NamelinkModeOnly)
                {
                    // Install the namelink only.
                    filesFrom.push_back(fromName);
                    filesTo.push_back(toName);
                } else
                {
                    // Install the real file if it has its own name.
                    if(!fromRealName.empty())
                    {
                        filesFrom.push_back(fromRealName);
                        filesTo.push_back(toRealName);
                    }

                    // Install the soname link if it has its own name.
                    if(!fromSOName.empty())
                    {
                        filesFrom.push_back(fromSOName);
                        filesTo.push_back(toSOName);
                    }

                    // Install the namelink if it is not to be skipped.
                    if(this->NamelinkMode != NamelinkModeSkip)
                    {
                        filesFrom.push_back(fromName);
                        filesTo.push_back(toName);
                    }
                }
            } else
            {
                // Without a namelink there will be only one file.  Install it
                // if this is not a namelink-only rule.
                if(this->NamelinkMode != NamelinkModeOnly)
                {
                    filesFrom.push_back(fromName);
                    filesTo.push_back(toName);
                }
            }
        }
    }

    // If this fails the above code is buggy.
    assert(filesFrom.size() == filesTo.size());

    // Skip this rule if no files are to be installed for the target.
    if(filesFrom.empty())
    {
        return;
    }

    // Add pre-installation tweaks.
    this->AddTweak(os, indent, config, filesTo,
                   &cmInstallTargetGenerator::PreReplacementTweaks);

    // Write code to install the target file.
    const char* no_dir_permissions = nullptr;
    const char* no_rename          = nullptr;
    bool        optional           = this->Optional || this->ImportLibrary;
    this->AddInstallRule(os, this->GetDestination(config), type, filesFrom,
                         optional, this->FilePermissions.c_str(),
                         no_dir_permissions, no_rename, literal_args.c_str(),
                         indent);

    // Add post-installation tweaks.
    this->AddTweak(os, indent, config, filesTo,
                   &cmInstallTargetGenerator::PostReplacementTweaks);
}

static std::string
computeInstallObjectDir(cmGeneratorTarget* gt, std::string const& config)
{
    std::string objectDir = "objects";
    if(!config.empty())
    {
        objectDir += "-";
        objectDir += config;
    }
    objectDir += "/";
    objectDir += gt->GetName();
    return objectDir;
}

void
cmInstallTargetGenerator::GenerateScriptForConfigObjectLibrary(
    std::ostream& os, const std::string& config, Indent indent)
{
    // Compute all the object files inside this target
    std::vector<std::string> objects;
    this->Target->GetTargetObjectNames(config, objects);

    std::string const dest = this->GetDestination(config) + "/" +
                             computeInstallObjectDir(this->Target, config);

    std::string const obj_dir      = this->Target->GetObjectDirectory(config);
    std::string const literal_args = " FILES_FROM_DIR \"" + obj_dir + "\"";

    const char* no_dir_permissions = nullptr;
    const char* no_rename          = nullptr;
    this->AddInstallRule(os, dest, cmInstallType_FILES, objects, this->Optional,
                         this->FilePermissions.c_str(), no_dir_permissions,
                         no_rename, literal_args.c_str(), indent);
}

void
cmInstallTargetGenerator::GetInstallObjectNames(
    std::string const& config, std::vector<std::string>& objects) const
{
    this->Target->GetTargetObjectNames(config, objects);
    for(std::string& o : objects)
    {
        o = computeInstallObjectDir(this->Target, config) + "/" + o;
    }
}

std::string
cmInstallTargetGenerator::GetDestination(std::string const& config) const
{
    cmGeneratorExpression ge;
    return ge.Parse(this->Destination)
        ->Evaluate(this->Target->GetLocalGenerator(), config);
}

std::string
cmInstallTargetGenerator::GetInstallFilename(const std::string& config) const
{
    NameType nameType = this->ImportLibrary ? NameImplib : NameNormal;
    return cmInstallTargetGenerator::GetInstallFilename(this->Target, config,
                                                        nameType);
}

std::string
cmInstallTargetGenerator::GetInstallFilename(cmGeneratorTarget const* target,
                                             const std::string&       config,
                                             NameType                 nameType)
{
    std::string fname;
    // Compute the name of the library.
    if(target->GetType() == cmStateEnums::EXECUTABLE)
    {
        std::string targetName;
        std::string targetNameReal;
        std::string targetNameImport;
        std::string targetNamePDB;
        target->GetExecutableNames(targetName, targetNameReal, targetNameImport,
                                   targetNamePDB, config);
        if(nameType == NameImplib)
        {
            // Use the import library name.
            if(!target->GetImplibGNUtoMS(config, targetNameImport, fname,
                                         "${CMAKE_IMPORT_LIBRARY_SUFFIX}"))
            {
                fname = targetNameImport;
            }
        } else if(nameType == NameReal)
        {
            // Use the canonical name.
            fname = targetNameReal;
        } else
        {
            // Use the canonical name.
            fname = targetName;
        }
    } else
    {
        std::string targetName;
        std::string targetNameSO;
        std::string targetNameReal;
        std::string targetNameImport;
        std::string targetNamePDB;
        target->GetLibraryNames(targetName, targetNameSO, targetNameReal,
                                targetNameImport, targetNamePDB, config);
        if(nameType == NameImplib)
        {
            // Use the import library name.
            if(!target->GetImplibGNUtoMS(config, targetNameImport, fname,
                                         "${CMAKE_IMPORT_LIBRARY_SUFFIX}"))
            {
                fname = targetNameImport;
            }
        } else if(nameType == NameSO)
        {
            // Use the soname.
            fname = targetNameSO;
        } else if(nameType == NameReal)
        {
            // Use the real name.
            fname = targetNameReal;
        } else
        {
            // Use the canonical name.
            fname = targetName;
        }
    }

    return fname;
}

void
cmInstallTargetGenerator::Compute(cmLocalGenerator* lg)
{
    // Lookup this target in the current directory.
    this->Target = lg->FindLocalNonAliasGeneratorTarget(this->TargetName);
    if(!this->Target)
    {
        // If no local target has been found, find it in the global scope.
        this->Target =
            lg->GetGlobalGenerator()->FindGeneratorTarget(this->TargetName);
    }
}

void
cmInstallTargetGenerator::AddTweak(std::ostream& os, Indent indent,
                                   const std::string& config,
                                   std::string const& file, TweakMethod tweak)
{
    std::ostringstream tw;
    (this->*tweak)(tw, indent.Next(), config, file);
    std::string tws = tw.str();
    if(!tws.empty())
    {
        os << indent << "if(EXISTS \"" << file << "\" AND\n"
           << indent << "   NOT IS_SYMLINK \"" << file << "\")\n";
        os << tws;
        os << indent << "endif()\n";
    }
}

void
cmInstallTargetGenerator::AddTweak(std::ostream& os, Indent indent,
                                   const std::string&              config,
                                   std::vector<std::string> const& files,
                                   TweakMethod                     tweak)
{
    if(files.size() == 1)
    {
        // Tweak a single file.
        this->AddTweak(os, indent, config, this->GetDestDirPath(files[0]),
                       tweak);
    } else
    {
        // Generate a foreach loop to tweak multiple files.
        std::ostringstream tw;
        this->AddTweak(tw, indent.Next(), config, "${file}", tweak);
        std::string tws = tw.str();
        if(!tws.empty())
        {
            Indent indent2 = indent.Next().Next();
            os << indent << "foreach(file\n";
            for(std::string const& f : files)
            {
                os << indent2 << "\"" << this->GetDestDirPath(f) << "\"\n";
            }
            os << indent2 << ")\n";
            os << tws;
            os << indent << "endforeach()\n";
        }
    }
}

std::string
cmInstallTargetGenerator::GetDestDirPath(std::string const& file)
{
    // Construct the path of the file on disk after installation on
    // which tweaks may be performed.
    std::string toDestDirPath = "$ENV{DESTDIR}";
    if(file[0] != '/' && file[0] != '$')
    {
        toDestDirPath += "/";
    }
    toDestDirPath += file;
    return toDestDirPath;
}

void
cmInstallTargetGenerator::PreReplacementTweaks(std::ostream& os, Indent indent,
                                               const std::string& config,
                                               std::string const& file)
{
    this->AddRPathCheckRule(os, indent, config, file);
}

void
cmInstallTargetGenerator::PostReplacementTweaks(std::ostream& os, Indent indent,
                                                const std::string& config,
                                                std::string const& file)
{
    this->AddInstallNamePatchRule(os, indent, config, file);
    this->AddChrpathPatchRule(os, indent, config, file);
    this->AddUniversalInstallRule(os, indent, file);
    this->AddRanlibRule(os, indent, file);
    this->AddStripRule(os, indent, file);
}

void
cmInstallTargetGenerator::AddInstallNamePatchRule(
    std::ostream& os, Indent indent, const std::string& config,
    std::string const& toDestDirPath)
{
    if(this->ImportLibrary ||
       !(this->Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
         this->Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
         this->Target->GetType() == cmStateEnums::EXECUTABLE))
    {
        return;
    }

    // Fix the install_name settings in installed binaries.
    std::string installNameTool =
        this->Target->Target->GetMakefile()->GetSafeDefinition(
            "CMAKE_INSTALL_NAME_TOOL");

    if(installNameTool.empty())
    {
        return;
    }

    // Build a map of build-tree install_name to install-tree install_name for
    // shared libraries linked to this target.
    std::map<std::string, std::string> install_name_remap;
    if(cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config))
    {
        std::set<cmGeneratorTarget const*> const& sharedLibs =
            cli->GetSharedLibrariesLinked();
        for(cmGeneratorTarget const* tgt : sharedLibs)
        {
            // The install_name of an imported target does not change.
            if(tgt->IsImported())
            {
                continue;
            }

            // If the build tree and install tree use different path
            // components of the install_name field then we need to create a
            // mapping to be applied after installation.
            std::string for_build = tgt->GetInstallNameDirForBuildTree(config);
            std::string for_install = tgt->GetInstallNameDirForInstallTree();
            if(for_build != for_install)
            {
                // The directory portions differ.  Append the filename to
                // create the mapping.
                std::string fname =
                    this->GetInstallFilename(tgt, config, NameSO);

                // Map from the build-tree install_name.
                for_build += fname;

                // Map to the install-tree install_name.
                for_install += fname;

                // Store the mapping entry.
                install_name_remap[for_build] = for_install;
            }
        }
    }

    // Edit the install_name of the target itself if necessary.
    std::string new_id;
    if(this->Target->GetType() == cmStateEnums::SHARED_LIBRARY)
    {
        std::string for_build =
            this->Target->GetInstallNameDirForBuildTree(config);
        std::string for_install =
            this->Target->GetInstallNameDirForInstallTree();

        if(this->Target->IsFrameworkOnApple() && for_install.empty())
        {
            // Frameworks seem to have an id corresponding to their own full
            // path.
            // ...
            // for_install = fullDestPath_without_DESTDIR_or_name;
        }

        // If the install name will change on installation set the new id
        // on the installed file.
        if(for_build != for_install)
        {
            // Prepare to refer to the install-tree install_name.
            new_id = for_install;
            new_id += this->GetInstallFilename(this->Target, config, NameSO);
        }
    }

    // Write a rule to run install_name_tool to set the install-tree
    // install_name value and references.
    if(!new_id.empty() || !install_name_remap.empty())
    {
        os << indent << "execute_process(COMMAND \"" << installNameTool;
        os << "\"";
        if(!new_id.empty())
        {
            os << "\n" << indent << "  -id \"" << new_id << "\"";
        }
        for(auto const& i : install_name_remap)
        {
            os << "\n"
               << indent << "  -change \"" << i.first << "\" \"" << i.second
               << "\"";
        }
        os << "\n" << indent << "  \"" << toDestDirPath << "\")\n";
    }
}

void
cmInstallTargetGenerator::AddRPathCheckRule(std::ostream& os, Indent indent,
                                            const std::string& config,
                                            std::string const& toDestDirPath)
{
    // Skip the chrpath if the target does not need it.
    if(this->ImportLibrary || !this->Target->IsChrpathUsed(config))
    {
        return;
    }
    // Skip if on Apple
    if(this->Target->Target->GetMakefile()->IsOn(
           "CMAKE_PLATFORM_HAS_INSTALLNAME"))
    {
        return;
    }

    // Get the link information for this target.
    // It can provide the RPATH.
    cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config);
    if(!cli)
    {
        return;
    }

    // Get the install RPATH from the link information.
    std::string newRpath = cli->GetChrpathString();

    // Write a rule to remove the installed file if its rpath is not the
    // new rpath.  This is needed for existing build/install trees when
    // the installed rpath changes but the file is not rebuilt.
    /* clang-format off */
  os << indent << "file(RPATH_CHECK\n"
     << indent << "     FILE \"" << toDestDirPath << "\"\n"
     << indent << "     RPATH \"" << newRpath << "\")\n";
    /* clang-format on */
}

void
cmInstallTargetGenerator::AddChrpathPatchRule(std::ostream& os, Indent indent,
                                              const std::string& config,
                                              std::string const& toDestDirPath)
{
    // Skip the chrpath if the target does not need it.
    if(this->ImportLibrary || !this->Target->IsChrpathUsed(config))
    {
        return;
    }

    // Get the link information for this target.
    // It can provide the RPATH.
    cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config);
    if(!cli)
    {
        return;
    }

    cmMakefile* mf = this->Target->Target->GetMakefile();

    if(mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME"))
    {
        // If using install_name_tool, set up the rules to modify the rpaths.
        std::string installNameTool =
            mf->GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL");

        std::vector<std::string> oldRuntimeDirs, newRuntimeDirs;
        cli->GetRPath(oldRuntimeDirs, false);
        cli->GetRPath(newRuntimeDirs, true);

        std::string darwin_major_version_s =
            mf->GetSafeDefinition("DARWIN_MAJOR_VERSION");

        std::istringstream ss(darwin_major_version_s);
        int                darwin_major_version;
        ss >> darwin_major_version;
        if(!ss.fail() && darwin_major_version <= 9 &&
           (!oldRuntimeDirs.empty() || !newRuntimeDirs.empty()))
        {
            std::ostringstream msg;
            msg << "WARNING: Target \"" << this->Target->GetName()
                << "\" has runtime paths which cannot be changed during "
                   "install.  "
                << "To change runtime paths, OS X version 10.6 or newer is "
                   "required.  "
                << "Therefore, runtime paths will not be changed when "
                   "installing.  "
                << "CMAKE_BUILD_WITH_INSTALL_RPATH may be used to work around"
                   " this limitation.";
            mf->IssueMessage(cmake::WARNING, msg.str());
        } else
        {
            // Note: These paths are kept unique to avoid
            // install_name_tool corruption.
            std::set<std::string> runpaths;
            for(std::string const& i : oldRuntimeDirs)
            {
                std::string runpath =
                    mf->GetGlobalGenerator()->ExpandCFGIntDir(i, config);

                if(runpaths.find(runpath) == runpaths.end())
                {
                    runpaths.insert(runpath);
                    os << indent << "execute_process(COMMAND "
                       << installNameTool << "\n";
                    os << indent << "  -delete_rpath \"" << runpath << "\"\n";
                    os << indent << "  \"" << toDestDirPath << "\")\n";
                }
            }

            runpaths.clear();
            for(std::string const& i : newRuntimeDirs)
            {
                std::string runpath =
                    mf->GetGlobalGenerator()->ExpandCFGIntDir(i, config);

                if(runpaths.find(runpath) == runpaths.end())
                {
                    os << indent << "execute_process(COMMAND "
                       << installNameTool << "\n";
                    os << indent << "  -add_rpath \"" << runpath << "\"\n";
                    os << indent << "  \"" << toDestDirPath << "\")\n";
                }
            }
        }
    } else
    {
        // Construct the original rpath string to be replaced.
        std::string oldRpath = cli->GetRPathString(false);

        // Get the install RPATH from the link information.
        std::string newRpath = cli->GetChrpathString();

        // Skip the rule if the paths are identical
        if(oldRpath == newRpath)
        {
            return;
        }

        // Write a rule to run chrpath to set the install-tree RPATH
        os << indent << "file(RPATH_CHANGE\n"
           << indent << "     FILE \"" << toDestDirPath << "\"\n"
           << indent << "     OLD_RPATH \"" << oldRpath << "\"\n"
           << indent << "     NEW_RPATH \"" << newRpath << "\")\n";
    }
}

void
cmInstallTargetGenerator::AddStripRule(std::ostream& os, Indent indent,
                                       const std::string& toDestDirPath)
{
    // don't strip static and import libraries, because it removes the only
    // symbol table they have so you can't link to them anymore
    if(this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
       this->ImportLibrary)
    {
        return;
    }

    // Don't handle OSX Bundles.
    if(this->Target->Target->GetMakefile()->IsOn("APPLE") &&
       this->Target->GetPropertyAsBool("MACOSX_BUNDLE"))
    {
        return;
    }

    if(!this->Target->Target->GetMakefile()->IsSet("CMAKE_STRIP"))
    {
        return;
    }

    os << indent << "if(CMAKE_INSTALL_DO_STRIP)\n";
    os << indent << "  execute_process(COMMAND \""
       << this->Target->Target->GetMakefile()->GetDefinition("CMAKE_STRIP")
       << "\" \"" << toDestDirPath << "\")\n";
    os << indent << "endif()\n";
}

void
cmInstallTargetGenerator::AddRanlibRule(std::ostream& os, Indent indent,
                                        const std::string& toDestDirPath)
{
    // Static libraries need ranlib on this platform.
    if(this->Target->GetType() != cmStateEnums::STATIC_LIBRARY)
    {
        return;
    }

    // Perform post-installation processing on the file depending
    // on its type.
    if(!this->Target->Target->GetMakefile()->IsOn("APPLE"))
    {
        return;
    }

    std::string ranlib =
        this->Target->Target->GetMakefile()->GetRequiredDefinition(
            "CMAKE_RANLIB");
    if(ranlib.empty())
    {
        return;
    }

    os << indent << "execute_process(COMMAND \"" << ranlib << "\" \""
       << toDestDirPath << "\")\n";
}

void
cmInstallTargetGenerator::AddUniversalInstallRule(
    std::ostream& os, Indent indent, const std::string& toDestDirPath)
{
    cmMakefile const* mf = this->Target->Target->GetMakefile();

    if(!mf->PlatformIsAppleEmbedded() || !mf->IsOn("XCODE"))
    {
        return;
    }

    const char* xcodeVersion = mf->GetDefinition("XCODE_VERSION");
    if(!xcodeVersion || cmSystemTools::VersionCompareGreater("6", xcodeVersion))
    {
        return;
    }

    switch(this->Target->GetType())
    {
        case cmStateEnums::EXECUTABLE:
        case cmStateEnums::STATIC_LIBRARY:
        case cmStateEnums::SHARED_LIBRARY:
        case cmStateEnums::MODULE_LIBRARY:
            break;

        default:
            return;
    }

    if(!this->Target->Target->GetPropertyAsBool("IOS_INSTALL_COMBINED"))
    {
        return;
    }

    os << indent << "include(CMakeIOSInstallCombined)\n";
    os << indent << "ios_install_combined("
       << "\"" << this->Target->Target->GetName() << "\" "
       << "\"" << toDestDirPath << "\")\n";
}
