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

#include <algorithm>
#include <memory>  // IWYU pragma: keep
#include <sstream>
#include <stddef.h>
#include <vector>

#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalUnixMakefileGenerator3.h"
#include "cmLinkLineComputer.h"
#include "cmLinkLineDeviceComputer.h"
#include "cmLocalGenerator.h"
#include "cmLocalUnixMakefileGenerator3.h"
#include "cmMakefile.h"
#include "cmOSXBundleGenerator.h"
#include "cmOutputConverter.h"
#include "cmRulePlaceholderExpander.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStateSnapshot.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmake.h"

cmMakefileLibraryTargetGenerator::cmMakefileLibraryTargetGenerator(
    cmGeneratorTarget* target)
: cmMakefileTargetGenerator(target)
{
    this->CustomCommandDriver = OnDepends;
    if(this->GeneratorTarget->GetType() != cmStateEnums::INTERFACE_LIBRARY)
    {
        this->GeneratorTarget->GetLibraryNames(
            this->TargetNameOut, this->TargetNameSO, this->TargetNameReal,
            this->TargetNameImport, this->TargetNamePDB, this->ConfigName);
    }

    this->OSXBundleGenerator =
        new cmOSXBundleGenerator(target, this->ConfigName);
    this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
}

cmMakefileLibraryTargetGenerator::~cmMakefileLibraryTargetGenerator()
{
    delete this->OSXBundleGenerator;
}

void
cmMakefileLibraryTargetGenerator::WriteRuleFiles()
{
    // create the build.make file and directory, put in the common blocks
    this->CreateRuleFile();

    // write rules used to help build object files
    this->WriteCommonCodeRules();

    // write the per-target per-language flags
    this->WriteTargetLanguageFlags();

    // write in rules for object files and custom commands
    this->WriteTargetBuildRules();

    // write the link rules
    // Write the rule for this target type.
    switch(this->GeneratorTarget->GetType())
    {
        case cmStateEnums::STATIC_LIBRARY:
            this->WriteStaticLibraryRules();
            break;
        case cmStateEnums::SHARED_LIBRARY:
            this->WriteSharedLibraryRules(false);
            if(this->GeneratorTarget->NeedRelinkBeforeInstall(this->ConfigName))
            {
                // Write rules to link an installable version of the target.
                this->WriteSharedLibraryRules(true);
            }
            break;
        case cmStateEnums::MODULE_LIBRARY:
            this->WriteModuleLibraryRules(false);
            if(this->GeneratorTarget->NeedRelinkBeforeInstall(this->ConfigName))
            {
                // Write rules to link an installable version of the target.
                this->WriteModuleLibraryRules(true);
            }
            break;
        case cmStateEnums::OBJECT_LIBRARY:
            this->WriteObjectLibraryRules();
            break;
        default:
            // If language is not known, this is an error.
            cmSystemTools::Error("Unknown Library Type");
            break;
    }

    // Write clean target
    this->WriteTargetCleanRules();

    // Write the dependency generation rule.  This must be done last so
    // that multiple output pair information is available.
    this->WriteTargetDependRules();

    // close the streams
    this->CloseFileStreams();
}

void
cmMakefileLibraryTargetGenerator::WriteObjectLibraryRules()
{
    std::vector<std::string> commands;
    std::vector<std::string> depends;

    // Add post-build rules.
    this->LocalGenerator->AppendCustomCommands(
        commands, this->GeneratorTarget->GetPostBuildCommands(),
        this->GeneratorTarget, this->LocalGenerator->GetBinaryDirectory());

    // Depend on the object files.
    this->AppendObjectDepends(depends);

    // Write the rule.
    this->LocalGenerator->WriteMakeRule(*this->BuildFileStream, nullptr,
                                        this->GeneratorTarget->GetName(),
                                        depends, commands, true);

    // Write the main driver rule to build everything in this target.
    this->WriteTargetDriverRule(this->GeneratorTarget->GetName(), false);
}

void
cmMakefileLibraryTargetGenerator::WriteStaticLibraryRules()
{
    const std::string                     cuda_lang("CUDA");
    cmGeneratorTarget::LinkClosure const* closure =
        this->GeneratorTarget->GetLinkClosure(this->ConfigName);

    const bool hasCUDA =
        (std::find(closure->Languages.begin(), closure->Languages.end(),
                   cuda_lang) != closure->Languages.end());

    const bool resolveDeviceSymbols =
        this->GeneratorTarget->GetPropertyAsBool("CUDA_RESOLVE_DEVICE_SYMBOLS");
    if(hasCUDA && resolveDeviceSymbols)
    {
        std::string linkRuleVar = "CMAKE_CUDA_DEVICE_LINK_LIBRARY";
        this->WriteDeviceLibraryRules(linkRuleVar, false);
    }

    std::string linkLanguage =
        this->GeneratorTarget->GetLinkerLanguage(this->ConfigName);

    std::string linkRuleVar = this->GeneratorTarget->GetCreateRuleVariable(
        linkLanguage, this->ConfigName);

    std::string extraFlags;
    this->LocalGenerator->GetStaticLibraryFlags(
        extraFlags, cmSystemTools::UpperCase(this->ConfigName), linkLanguage,
        this->GeneratorTarget);
    this->WriteLibraryRules(linkRuleVar, extraFlags, false);
}

void
cmMakefileLibraryTargetGenerator::WriteSharedLibraryRules(bool relink)
{
    if(this->GeneratorTarget->IsFrameworkOnApple())
    {
        this->WriteFrameworkRules(relink);
        return;
    }

    if(!relink)
    {
        const std::string                     cuda_lang("CUDA");
        cmGeneratorTarget::LinkClosure const* closure =
            this->GeneratorTarget->GetLinkClosure(this->ConfigName);

        const bool hasCUDA =
            (std::find(closure->Languages.begin(), closure->Languages.end(),
                       cuda_lang) != closure->Languages.end());
        if(hasCUDA)
        {
            std::string linkRuleVar = "CMAKE_CUDA_DEVICE_LINK_LIBRARY";
            this->WriteDeviceLibraryRules(linkRuleVar, relink);
        }
    }

    std::string linkLanguage =
        this->GeneratorTarget->GetLinkerLanguage(this->ConfigName);
    std::string linkRuleVar = "CMAKE_";
    linkRuleVar += linkLanguage;
    linkRuleVar += "_CREATE_SHARED_LIBRARY";

    std::string extraFlags;
    this->GetTargetLinkFlags(extraFlags, linkLanguage);
    this->LocalGenerator->AddConfigVariableFlags(
        extraFlags, "CMAKE_SHARED_LINKER_FLAGS", this->ConfigName);

    std::unique_ptr<cmLinkLineComputer> linkLineComputer(
        this->CreateLinkLineComputer(
            this->LocalGenerator,
            this->LocalGenerator->GetStateSnapshot().GetDirectory()));

    this->AddModuleDefinitionFlag(linkLineComputer.get(), extraFlags);

    if(this->GeneratorTarget->GetPropertyAsBool("LINK_WHAT_YOU_USE"))
    {
        this->LocalGenerator->AppendFlags(extraFlags, " -Wl,--no-as-needed");
    }
    this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
}

void
cmMakefileLibraryTargetGenerator::WriteModuleLibraryRules(bool relink)
{
    if(!relink)
    {
        const std::string                     cuda_lang("CUDA");
        cmGeneratorTarget::LinkClosure const* closure =
            this->GeneratorTarget->GetLinkClosure(this->ConfigName);

        const bool hasCUDA =
            (std::find(closure->Languages.begin(), closure->Languages.end(),
                       cuda_lang) != closure->Languages.end());
        if(hasCUDA)
        {
            std::string linkRuleVar = "CMAKE_CUDA_DEVICE_LINK_LIBRARY";
            this->WriteDeviceLibraryRules(linkRuleVar, relink);
        }
    }

    std::string linkLanguage =
        this->GeneratorTarget->GetLinkerLanguage(this->ConfigName);
    std::string linkRuleVar = "CMAKE_";
    linkRuleVar += linkLanguage;
    linkRuleVar += "_CREATE_SHARED_MODULE";

    std::string extraFlags;
    this->GetTargetLinkFlags(extraFlags, linkLanguage);
    this->LocalGenerator->AddConfigVariableFlags(
        extraFlags, "CMAKE_MODULE_LINKER_FLAGS", this->ConfigName);

    std::unique_ptr<cmLinkLineComputer> linkLineComputer(
        this->CreateLinkLineComputer(
            this->LocalGenerator,
            this->LocalGenerator->GetStateSnapshot().GetDirectory()));

    this->AddModuleDefinitionFlag(linkLineComputer.get(), extraFlags);

    this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
}

void
cmMakefileLibraryTargetGenerator::WriteFrameworkRules(bool relink)
{
    std::string linkLanguage =
        this->GeneratorTarget->GetLinkerLanguage(this->ConfigName);
    std::string linkRuleVar = "CMAKE_";
    linkRuleVar += linkLanguage;
    linkRuleVar += "_CREATE_MACOSX_FRAMEWORK";

    std::string extraFlags;
    this->GetTargetLinkFlags(extraFlags, linkLanguage);
    this->LocalGenerator->AddConfigVariableFlags(
        extraFlags, "CMAKE_MACOSX_FRAMEWORK_LINKER_FLAGS", this->ConfigName);

    this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
}

void
cmMakefileLibraryTargetGenerator::WriteDeviceLibraryRules(
    const std::string& linkRuleVar, bool relink)
{
#ifdef CMAKE_BUILD_WITH_CMAKE
    // TODO: Merge the methods that call this method to avoid
    // code duplication.
    std::vector<std::string> commands;

    // Get the language to use for linking this library.
    std::string       linkLanguage = "CUDA";
    std::string const objExt =
        this->Makefile->GetSafeDefinition("CMAKE_CUDA_OUTPUT_EXTENSION");

    // Build list of dependencies.
    std::vector<std::string> depends;
    this->AppendLinkDepends(depends, linkLanguage);

    // Create set of linking flags.
    std::string linkFlags;
    this->GetTargetLinkFlags(linkFlags, linkLanguage);

    // Get the name of the device object to generate.
    std::string const targetOutputReal =
        this->GeneratorTarget->ObjectDirectory + "cmake_device_link" + objExt;
    this->DeviceLinkObject = targetOutputReal;

    this->NumberOfProgressActions++;
    if(!this->NoRuleMessages)
    {
        cmLocalUnixMakefileGenerator3::EchoProgress progress;
        this->MakeEchoProgress(progress);
        // Add the link message.
        std::string buildEcho = "Linking " + linkLanguage + " device code ";
        buildEcho += this->LocalGenerator->ConvertToOutputFormat(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                this->DeviceLinkObject),
            cmOutputConverter::SHELL);
        this->LocalGenerator->AppendEcho(
            commands, buildEcho, cmLocalUnixMakefileGenerator3::EchoLink,
            &progress);
    }
    // Clean files associated with this library.
    std::vector<std::string> libCleanFiles;
    libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
        this->LocalGenerator->GetCurrentBinaryDirectory(), targetOutputReal));

    // Determine whether a link script will be used.
    bool useLinkScript = this->GlobalGenerator->GetUseLinkScript();

    bool useResponseFileForObjects =
        this->CheckUseResponseFileForObjects(linkLanguage);
    bool const useResponseFileForLibs =
        this->CheckUseResponseFileForLibraries(linkLanguage);

    cmRulePlaceholderExpander::RuleVariables vars;
    vars.Language = linkLanguage.c_str();

    // Expand the rule variables.
    std::vector<std::string> real_link_commands;
    {
        bool useWatcomQuote =
            this->Makefile->IsOn(linkRuleVar + "_USE_WATCOM_QUOTE");

        // Set path conversion for link script shells.
        this->LocalGenerator->SetLinkScriptShell(useLinkScript);

        // Collect up flags to link in needed libraries.
        std::string linkLibs;
        if(this->GeneratorTarget->GetType() != cmStateEnums::STATIC_LIBRARY)
        {
            std::unique_ptr<cmLinkLineComputer> linkLineComputer(
                new cmLinkLineDeviceComputer(
                    this->LocalGenerator,
                    this->LocalGenerator->GetStateSnapshot().GetDirectory()));
            linkLineComputer->SetForResponse(useResponseFileForLibs);
            linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
            linkLineComputer->SetRelink(relink);

            this->CreateLinkLibs(linkLineComputer.get(), linkLibs,
                                 useResponseFileForLibs, depends);
        }

        // Construct object file lists that may be needed to expand the
        // rule.
        std::string buildObjs;
        this->CreateObjectLists(useLinkScript, false,  // useArchiveRules
                                useResponseFileForObjects, buildObjs, depends,
                                useWatcomQuote);

        cmOutputConverter::OutputFormat output =
            (useWatcomQuote) ? cmOutputConverter::WATCOMQUOTE
                             : cmOutputConverter::SHELL;

        std::string objectDir = this->GeneratorTarget->GetSupportDirectory();
        objectDir             = this->LocalGenerator->ConvertToOutputFormat(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(), objectDir),
            cmOutputConverter::SHELL);

        std::string target = this->LocalGenerator->ConvertToOutputFormat(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetOutputReal),
            output);

        std::string targetFullPathCompilePDB = this->ComputeTargetCompilePDB();
        std::string targetOutPathCompilePDB =
            this->LocalGenerator->ConvertToOutputFormat(
                targetFullPathCompilePDB, cmOutputConverter::SHELL);

        vars.Objects          = buildObjs.c_str();
        vars.ObjectDir        = objectDir.c_str();
        vars.Target           = target.c_str();
        vars.LinkLibraries    = linkLibs.c_str();
        vars.ObjectsQuoted    = buildObjs.c_str();
        vars.LinkFlags        = linkFlags.c_str();
        vars.TargetCompilePDB = targetOutPathCompilePDB.c_str();

        // Add language-specific flags.
        std::string langFlags;
        this->LocalGenerator->AddLanguageFlagsForLinking(
            langFlags, this->GeneratorTarget, linkLanguage, this->ConfigName);

        vars.LanguageCompileFlags = langFlags.c_str();

        std::string launcher;
        const char* val = this->LocalGenerator->GetRuleLauncher(
            this->GeneratorTarget, "RULE_LAUNCH_LINK");
        if(val && *val)
        {
            launcher = val;
            launcher += " ";
        }

        std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
            this->LocalGenerator->CreateRulePlaceholderExpander());

        // Construct the main link rule and expand placeholders.
        rulePlaceholderExpander->SetTargetImpLib(targetOutputReal);
        std::string linkRule = this->GetLinkRule(linkRuleVar);
        cmSystemTools::ExpandListArgument(linkRule, real_link_commands);

        // Expand placeholders.
        for(std::string& real_link_command : real_link_commands)
        {
            real_link_command = launcher + real_link_command;
            rulePlaceholderExpander->ExpandRuleVariables(
                this->LocalGenerator, real_link_command, vars);
        }
        // Restore path conversion to normal shells.
        this->LocalGenerator->SetLinkScriptShell(false);

        // Clean all the possible library names and symlinks.
        this->CleanFiles.insert(this->CleanFiles.end(), libCleanFiles.begin(),
                                libCleanFiles.end());
    }

    std::vector<std::string> commands1;
    // Optionally convert the build rule to use a script to avoid long
    // command lines in the make shell.
    if(useLinkScript)
    {
        // Use a link script.
        const char* name = (relink ? "drelink.txt" : "dlink.txt");
        this->CreateLinkScript(name, real_link_commands, commands1, depends);
    } else
    {
        // No link script.  Just use the link rule directly.
        commands1 = real_link_commands;
    }
    this->LocalGenerator->CreateCDCommand(
        commands1, this->Makefile->GetCurrentBinaryDirectory(),
        this->LocalGenerator->GetBinaryDirectory());
    commands.insert(commands.end(), commands1.begin(), commands1.end());
    commands1.clear();

    // Compute the list of outputs.
    std::vector<std::string> outputs(1, targetOutputReal);

    // Write the build rule.
    this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends,
                        commands, false);

    // Write the main driver rule to build everything in this target.
    this->WriteTargetDriverRule(targetOutputReal, relink);
#else
    static_cast<void>(linkRuleVar);
    static_cast<void>(relink);
#endif
}

void
cmMakefileLibraryTargetGenerator::WriteLibraryRules(
    const std::string& linkRuleVar, const std::string& extraFlags, bool relink)
{
    // TODO: Merge the methods that call this method to avoid
    // code duplication.
    std::vector<std::string> commands;

    // Get the language to use for linking this library.
    std::string linkLanguage =
        this->GeneratorTarget->GetLinkerLanguage(this->ConfigName);

    // Make sure we have a link language.
    if(linkLanguage.empty())
    {
        cmSystemTools::Error("Cannot determine link language for target \"",
                             this->GeneratorTarget->GetName().c_str(), "\".");
        return;
    }

    // Build list of dependencies.
    std::vector<std::string> depends;
    this->AppendLinkDepends(depends, linkLanguage);
    if(!this->DeviceLinkObject.empty())
    {
        depends.push_back(this->DeviceLinkObject);
    }

    // Create set of linking flags.
    std::string linkFlags;
    this->LocalGenerator->AppendFlags(linkFlags, extraFlags);
    this->LocalGenerator->AppendIPOLinkerFlags(linkFlags, this->GeneratorTarget,
                                               this->ConfigName, linkLanguage);

    // Add OSX version flags, if any.
    if(this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY ||
       this->GeneratorTarget->GetType() == cmStateEnums::MODULE_LIBRARY)
    {
        this->AppendOSXVerFlag(linkFlags, linkLanguage, "COMPATIBILITY", true);
        this->AppendOSXVerFlag(linkFlags, linkLanguage, "CURRENT", false);
    }

    // Construct the name of the library.
    std::string targetName;
    std::string targetNameSO;
    std::string targetNameReal;
    std::string targetNameImport;
    std::string targetNamePDB;
    this->GeneratorTarget->GetLibraryNames(targetName, targetNameSO,
                                           targetNameReal, targetNameImport,
                                           targetNamePDB, this->ConfigName);

    // Construct the full path version of the names.
    std::string outpath;
    std::string outpathImp;
    if(this->GeneratorTarget->IsFrameworkOnApple())
    {
        outpath = this->GeneratorTarget->GetDirectory(this->ConfigName);
        this->OSXBundleGenerator->CreateFramework(targetName, outpath);
        outpath += "/";
    } else if(this->GeneratorTarget->IsCFBundleOnApple())
    {
        outpath = this->GeneratorTarget->GetDirectory(this->ConfigName);
        this->OSXBundleGenerator->CreateCFBundle(targetName, outpath);
        outpath += "/";
    } else if(relink)
    {
        outpath = this->Makefile->GetCurrentBinaryDirectory();
        outpath += cmake::GetCMakeFilesDirectory();
        outpath += "/CMakeRelink.dir";
        cmSystemTools::MakeDirectory(outpath);
        outpath += "/";
        if(!targetNameImport.empty())
        {
            outpathImp = outpath;
        }
    } else
    {
        outpath = this->GeneratorTarget->GetDirectory(this->ConfigName);
        cmSystemTools::MakeDirectory(outpath);
        outpath += "/";
        if(!targetNameImport.empty())
        {
            outpathImp = this->GeneratorTarget->GetDirectory(
                this->ConfigName, cmStateEnums::ImportLibraryArtifact);
            cmSystemTools::MakeDirectory(outpathImp);
            outpathImp += "/";
        }
    }

    std::string compilePdbOutputPath =
        this->GeneratorTarget->GetCompilePDBDirectory(this->ConfigName);
    cmSystemTools::MakeDirectory(compilePdbOutputPath);

    std::string pdbOutputPath =
        this->GeneratorTarget->GetPDBDirectory(this->ConfigName);
    cmSystemTools::MakeDirectory(pdbOutputPath);
    pdbOutputPath += "/";

    std::string targetFullPath       = outpath + targetName;
    std::string targetFullPathPDB    = pdbOutputPath + targetNamePDB;
    std::string targetFullPathSO     = outpath + targetNameSO;
    std::string targetFullPathReal   = outpath + targetNameReal;
    std::string targetFullPathImport = outpathImp + targetNameImport;

    // Construct the output path version of the names for use in command
    // arguments.
    std::string targetOutPathPDB = this->LocalGenerator->ConvertToOutputFormat(
        targetFullPathPDB, cmOutputConverter::SHELL);

    std::string targetOutPath = this->LocalGenerator->ConvertToOutputFormat(
        this->LocalGenerator->MaybeConvertToRelativePath(
            this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPath),
        cmOutputConverter::SHELL);
    std::string targetOutPathSO = this->LocalGenerator->ConvertToOutputFormat(
        this->LocalGenerator->MaybeConvertToRelativePath(
            this->LocalGenerator->GetCurrentBinaryDirectory(),
            targetFullPathSO),
        cmOutputConverter::SHELL);
    std::string targetOutPathReal = this->LocalGenerator->ConvertToOutputFormat(
        this->LocalGenerator->MaybeConvertToRelativePath(
            this->LocalGenerator->GetCurrentBinaryDirectory(),
            targetFullPathReal),
        cmOutputConverter::SHELL);
    std::string targetOutPathImport =
        this->LocalGenerator->ConvertToOutputFormat(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetFullPathImport),
            cmOutputConverter::SHELL);

    this->NumberOfProgressActions++;
    if(!this->NoRuleMessages)
    {
        cmLocalUnixMakefileGenerator3::EchoProgress progress;
        this->MakeEchoProgress(progress);
        // Add the link message.
        std::string buildEcho = "Linking ";
        buildEcho += linkLanguage;
        switch(this->GeneratorTarget->GetType())
        {
            case cmStateEnums::STATIC_LIBRARY:
                buildEcho += " static library ";
                break;
            case cmStateEnums::SHARED_LIBRARY:
                buildEcho += " shared library ";
                break;
            case cmStateEnums::MODULE_LIBRARY:
                if(this->GeneratorTarget->IsCFBundleOnApple())
                {
                    buildEcho += " CFBundle";
                }
                buildEcho += " shared module ";
                break;
            default:
                buildEcho += " library ";
                break;
        }
        buildEcho += targetOutPath;
        this->LocalGenerator->AppendEcho(
            commands, buildEcho, cmLocalUnixMakefileGenerator3::EchoLink,
            &progress);
    }

    // Clean files associated with this library.
    std::vector<std::string> libCleanFiles;
    libCleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
        this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPathReal));

    std::vector<std::string> commands1;
    // Add a command to remove any existing files for this library.
    // for static libs only
    if(this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY)
    {
        this->LocalGenerator->AppendCleanCommand(
            commands1, libCleanFiles, this->GeneratorTarget, "target");
        this->LocalGenerator->CreateCDCommand(
            commands1, this->Makefile->GetCurrentBinaryDirectory(),
            this->LocalGenerator->GetBinaryDirectory());
        commands.insert(commands.end(), commands1.begin(), commands1.end());
        commands1.clear();
    }

    if(targetName != targetNameReal)
    {
        libCleanFiles.push_back(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetFullPath));
    }
    if(targetNameSO != targetNameReal && targetNameSO != targetName)
    {
        libCleanFiles.push_back(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetFullPathSO));
    }
    if(!targetNameImport.empty())
    {
        libCleanFiles.push_back(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetFullPathImport));
        std::string implib;
        if(this->GeneratorTarget->GetImplibGNUtoMS(
               this->ConfigName, targetFullPathImport, implib))
        {
            libCleanFiles.push_back(
                this->LocalGenerator->MaybeConvertToRelativePath(
                    this->LocalGenerator->GetCurrentBinaryDirectory(), implib));
        }
    }

    // List the PDB for cleaning only when the whole target is
    // cleaned.  We do not want to delete the .pdb file just before
    // linking the target.
    this->CleanFiles.push_back(this->LocalGenerator->MaybeConvertToRelativePath(
        this->LocalGenerator->GetCurrentBinaryDirectory(), targetFullPathPDB));

#ifdef _WIN32
    // There may be a manifest file for this target.  Add it to the
    // clean set just in case.
    if(this->GeneratorTarget->GetType() != cmStateEnums::STATIC_LIBRARY)
    {
        libCleanFiles.push_back(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetFullPath + ".manifest"));
    }
#endif

    // Add the pre-build and pre-link rules building but not when relinking.
    if(!relink)
    {
        this->LocalGenerator->AppendCustomCommands(
            commands, this->GeneratorTarget->GetPreBuildCommands(),
            this->GeneratorTarget, this->LocalGenerator->GetBinaryDirectory());
        this->LocalGenerator->AppendCustomCommands(
            commands, this->GeneratorTarget->GetPreLinkCommands(),
            this->GeneratorTarget, this->LocalGenerator->GetBinaryDirectory());
    }

    // Determine whether a link script will be used.
    bool useLinkScript = this->GlobalGenerator->GetUseLinkScript();

    bool useResponseFileForObjects =
        this->CheckUseResponseFileForObjects(linkLanguage);
    bool const useResponseFileForLibs =
        this->CheckUseResponseFileForLibraries(linkLanguage);

    // For static libraries there might be archiving rules.
    bool                     haveStaticLibraryRule = false;
    std::vector<std::string> archiveCreateCommands;
    std::vector<std::string> archiveAppendCommands;
    std::vector<std::string> archiveFinishCommands;
    std::string::size_type   archiveCommandLimit = std::string::npos;
    if(this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY)
    {
        haveStaticLibraryRule   = this->Makefile->IsDefinitionSet(linkRuleVar);
        std::string arCreateVar = "CMAKE_";
        arCreateVar += linkLanguage;
        arCreateVar += "_ARCHIVE_CREATE";

        arCreateVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
            arCreateVar, linkLanguage, this->ConfigName);

        if(const char* rule = this->Makefile->GetDefinition(arCreateVar))
        {
            cmSystemTools::ExpandListArgument(rule, archiveCreateCommands);
        }
        std::string arAppendVar = "CMAKE_";
        arAppendVar += linkLanguage;
        arAppendVar += "_ARCHIVE_APPEND";

        arAppendVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
            arAppendVar, linkLanguage, this->ConfigName);

        if(const char* rule = this->Makefile->GetDefinition(arAppendVar))
        {
            cmSystemTools::ExpandListArgument(rule, archiveAppendCommands);
        }
        std::string arFinishVar = "CMAKE_";
        arFinishVar += linkLanguage;
        arFinishVar += "_ARCHIVE_FINISH";

        arFinishVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
            arFinishVar, linkLanguage, this->ConfigName);

        if(const char* rule = this->Makefile->GetDefinition(arFinishVar))
        {
            cmSystemTools::ExpandListArgument(rule, archiveFinishCommands);
        }
    }

    // Decide whether to use archiving rules.
    bool useArchiveRules = !haveStaticLibraryRule &&
                           !archiveCreateCommands.empty() &&
                           !archiveAppendCommands.empty();
    if(useArchiveRules)
    {
        // Archiving rules are always run with a link script.
        useLinkScript = true;

        // Archiving rules never use a response file.
        useResponseFileForObjects = false;

        // Limit the length of individual object lists to less than half of
        // the command line length limit (leaving half for other flags).
        // This may result in several calls to the archiver.
        if(size_t limit = cmSystemTools::CalculateCommandLineLengthLimit())
        {
            archiveCommandLimit = limit / 2;
        } else
        {
            archiveCommandLimit = 8000;
        }
    }

    // Expand the rule variables.
    std::vector<std::string> real_link_commands;
    {
        bool useWatcomQuote =
            this->Makefile->IsOn(linkRuleVar + "_USE_WATCOM_QUOTE");

        // Set path conversion for link script shells.
        this->LocalGenerator->SetLinkScriptShell(useLinkScript);

        // Collect up flags to link in needed libraries.
        std::string linkLibs;
        if(this->GeneratorTarget->GetType() != cmStateEnums::STATIC_LIBRARY)
        {
            std::unique_ptr<cmLinkLineComputer> linkLineComputer(
                this->CreateLinkLineComputer(
                    this->LocalGenerator,
                    this->LocalGenerator->GetStateSnapshot().GetDirectory()));
            linkLineComputer->SetForResponse(useResponseFileForLibs);
            linkLineComputer->SetUseWatcomQuote(useWatcomQuote);
            linkLineComputer->SetRelink(relink);

            this->CreateLinkLibs(linkLineComputer.get(), linkLibs,
                                 useResponseFileForLibs, depends);
        }

        // Construct object file lists that may be needed to expand the
        // rule.
        std::string buildObjs;
        this->CreateObjectLists(useLinkScript, useArchiveRules,
                                useResponseFileForObjects, buildObjs, depends,
                                useWatcomQuote);
        if(!this->DeviceLinkObject.empty())
        {
            buildObjs +=
                " " + this->LocalGenerator->ConvertToOutputFormat(
                          this->LocalGenerator->MaybeConvertToRelativePath(
                              this->LocalGenerator->GetCurrentBinaryDirectory(),
                              this->DeviceLinkObject),
                          cmOutputConverter::SHELL);
        }

        // maybe create .def file from list of objects
        this->GenDefFile(real_link_commands);

        std::string manifests = this->GetManifests();

        cmRulePlaceholderExpander::RuleVariables vars;
        vars.TargetPDB = targetOutPathPDB.c_str();

        // Setup the target version.
        std::string targetVersionMajor;
        std::string targetVersionMinor;
        {
            std::ostringstream majorStream;
            std::ostringstream minorStream;
            int                major;
            int                minor;
            this->GeneratorTarget->GetTargetVersion(major, minor);
            majorStream << major;
            minorStream << minor;
            targetVersionMajor = majorStream.str();
            targetVersionMinor = minorStream.str();
        }
        vars.TargetVersionMajor = targetVersionMajor.c_str();
        vars.TargetVersionMinor = targetVersionMinor.c_str();

        vars.CMTargetName = this->GeneratorTarget->GetName().c_str();
        vars.CMTargetType =
            cmState::GetTargetTypeName(this->GeneratorTarget->GetType());
        vars.Language         = linkLanguage.c_str();
        vars.Objects          = buildObjs.c_str();
        std::string objectDir = this->GeneratorTarget->GetSupportDirectory();

        objectDir = this->LocalGenerator->ConvertToOutputFormat(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(), objectDir),
            cmOutputConverter::SHELL);

        vars.ObjectDir = objectDir.c_str();
        cmOutputConverter::OutputFormat output =
            (useWatcomQuote) ? cmOutputConverter::WATCOMQUOTE
                             : cmOutputConverter::SHELL;
        std::string target = this->LocalGenerator->ConvertToOutputFormat(
            this->LocalGenerator->MaybeConvertToRelativePath(
                this->LocalGenerator->GetCurrentBinaryDirectory(),
                targetFullPathReal),
            output);
        vars.Target        = target.c_str();
        vars.LinkLibraries = linkLibs.c_str();
        vars.ObjectsQuoted = buildObjs.c_str();
        if(this->GeneratorTarget->HasSOName(this->ConfigName))
        {
            vars.SONameFlag   = this->Makefile->GetSONameFlag(linkLanguage);
            vars.TargetSOName = targetNameSO.c_str();
        }
        vars.LinkFlags = linkFlags.c_str();

        vars.Manifests = manifests.c_str();

        // Compute the directory portion of the install_name setting.
        std::string install_name_dir;
        if(this->GeneratorTarget->GetType() == cmStateEnums::SHARED_LIBRARY)
        {
            // Get the install_name directory for the build tree.
            install_name_dir =
                this->GeneratorTarget->GetInstallNameDirForBuildTree(
                    this->ConfigName);

            // Set the rule variable replacement value.
            if(install_name_dir.empty())
            {
                vars.TargetInstallNameDir = "";
            } else
            {
                // Convert to a path for the native build tool.
                install_name_dir = this->LocalGenerator->ConvertToOutputFormat(
                    install_name_dir, cmOutputConverter::SHELL);
                vars.TargetInstallNameDir = install_name_dir.c_str();
            }
        }

        // Add language-specific flags.
        std::string langFlags;
        this->LocalGenerator->AddLanguageFlagsForLinking(
            langFlags, this->GeneratorTarget, linkLanguage, this->ConfigName);

        this->LocalGenerator->AddArchitectureFlags(
            langFlags, this->GeneratorTarget, linkLanguage, this->ConfigName);

        vars.LanguageCompileFlags = langFlags.c_str();

        std::string launcher;
        const char* val = this->LocalGenerator->GetRuleLauncher(
            this->GeneratorTarget, "RULE_LAUNCH_LINK");
        if(val && *val)
        {
            launcher = val;
            launcher += " ";
        }

        std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
            this->LocalGenerator->CreateRulePlaceholderExpander());
        // Construct the main link rule and expand placeholders.
        rulePlaceholderExpander->SetTargetImpLib(targetOutPathImport);
        if(useArchiveRules)
        {
            // Construct the individual object list strings.
            std::vector<std::string> object_strings;
            this->WriteObjectsStrings(object_strings, archiveCommandLimit);

            // Add the cuda device object to the list of archive files. This
            // will only occur on archives which have
            // CUDA_RESOLVE_DEVICE_SYMBOLS enabled
            if(!this->DeviceLinkObject.empty())
            {
                object_strings.push_back(
                    this->LocalGenerator->ConvertToOutputFormat(
                        this->LocalGenerator->MaybeConvertToRelativePath(
                            this->LocalGenerator->GetCurrentBinaryDirectory(),
                            this->DeviceLinkObject),
                        cmOutputConverter::SHELL));
            }

            // Create the archive with the first set of objects.
            std::vector<std::string>::iterator osi = object_strings.begin();
            {
                vars.Objects = osi->c_str();
                for(std::string const& acc : archiveCreateCommands)
                {
                    std::string cmd = launcher + acc;
                    rulePlaceholderExpander->ExpandRuleVariables(
                        this->LocalGenerator, cmd, vars);
                    real_link_commands.push_back(std::move(cmd));
                }
            }
            // Append to the archive with the other object sets.
            for(++osi; osi != object_strings.end(); ++osi)
            {
                vars.Objects = osi->c_str();
                for(std::string const& aac : archiveAppendCommands)
                {
                    std::string cmd = launcher + aac;
                    rulePlaceholderExpander->ExpandRuleVariables(
                        this->LocalGenerator, cmd, vars);
                    real_link_commands.push_back(std::move(cmd));
                }
            }
            // Finish the archive.
            vars.Objects = "";
            for(std::string const& afc : archiveFinishCommands)
            {
                std::string cmd = launcher + afc;
                rulePlaceholderExpander->ExpandRuleVariables(
                    this->LocalGenerator, cmd, vars);
                // If there is no ranlib the command will be ":".  Skip it.
                if(!cmd.empty() && cmd[0] != ':')
                {
                    real_link_commands.push_back(std::move(cmd));
                }
            }
        } else
        {
            // Get the set of commands.
            std::string linkRule = this->GetLinkRule(linkRuleVar);
            cmSystemTools::ExpandListArgument(linkRule, real_link_commands);
            if(this->GeneratorTarget->GetPropertyAsBool("LINK_WHAT_YOU_USE") &&
               (this->GeneratorTarget->GetType() ==
                cmStateEnums::SHARED_LIBRARY))
            {
                std::string cmakeCommand =
                    this->LocalGenerator->ConvertToOutputFormat(
                        cmSystemTools::GetCMakeCommand(),
                        cmLocalGenerator::SHELL);
                cmakeCommand += " -E __run_co_compile --lwyu=";
                cmakeCommand += targetOutPathReal;
                real_link_commands.push_back(std::move(cmakeCommand));
            }

            // Expand placeholders.
            for(std::string& real_link_command : real_link_commands)
            {
                real_link_command = launcher + real_link_command;
                rulePlaceholderExpander->ExpandRuleVariables(
                    this->LocalGenerator, real_link_command, vars);
            }
        }

        // Restore path conversion to normal shells.
        this->LocalGenerator->SetLinkScriptShell(false);
    }

    // Optionally convert the build rule to use a script to avoid long
    // command lines in the make shell.
    if(useLinkScript)
    {
        // Use a link script.
        const char* name = (relink ? "relink.txt" : "link.txt");
        this->CreateLinkScript(name, real_link_commands, commands1, depends);
    } else
    {
        // No link script.  Just use the link rule directly.
        commands1 = real_link_commands;
    }
    this->LocalGenerator->CreateCDCommand(
        commands1, this->Makefile->GetCurrentBinaryDirectory(),
        this->LocalGenerator->GetBinaryDirectory());
    commands.insert(commands.end(), commands1.begin(), commands1.end());
    commands1.clear();

    // Add a rule to create necessary symlinks for the library.
    // Frameworks are handled by cmOSXBundleGenerator.
    if(targetOutPath != targetOutPathReal &&
       !this->GeneratorTarget->IsFrameworkOnApple())
    {
        std::string symlink = "$(CMAKE_COMMAND) -E cmake_symlink_library ";
        symlink += targetOutPathReal;
        symlink += " ";
        symlink += targetOutPathSO;
        symlink += " ";
        symlink += targetOutPath;
        commands1.push_back(std::move(symlink));
        this->LocalGenerator->CreateCDCommand(
            commands1, this->Makefile->GetCurrentBinaryDirectory(),
            this->LocalGenerator->GetBinaryDirectory());
        commands.insert(commands.end(), commands1.begin(), commands1.end());
        commands1.clear();
    }

    // Add the post-build rules when building but not when relinking.
    if(!relink)
    {
        this->LocalGenerator->AppendCustomCommands(
            commands, this->GeneratorTarget->GetPostBuildCommands(),
            this->GeneratorTarget, this->LocalGenerator->GetBinaryDirectory());
    }

    // Compute the list of outputs.
    std::vector<std::string> outputs(1, targetFullPathReal);
    if(targetNameSO != targetNameReal)
    {
        outputs.push_back(targetFullPathSO);
    }
    if(targetName != targetNameSO && targetName != targetNameReal)
    {
        outputs.push_back(targetFullPath);
    }

    // Write the build rule.
    this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends,
                        commands, false);

    // Write the main driver rule to build everything in this target.
    this->WriteTargetDriverRule(targetFullPath, relink);

    // Clean all the possible library names and symlinks.
    this->CleanFiles.insert(this->CleanFiles.end(), libCleanFiles.begin(),
                            libCleanFiles.end());
}
