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

#include <algorithm>
#include <functional>
#include <map>
#include <sstream>
#include <utility>

#include <cm/memory>
#include <cm/string>
#include <cmext/algorithm>
#include <cmext/memory>

#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmGhsMultiGpj.h"
#include "cmList.h"
#include "cmLocalGenerator.h"
#include "cmLocalGhsMultiGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmSourceFile.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
#include "cmVersion.h"
#include "cmake.h"

const char* cmGlobalGhsMultiGenerator::FILE_EXTENSION = ".gpj";
#ifdef __linux__
const char* cmGlobalGhsMultiGenerator::DEFAULT_BUILD_PROGRAM = "gbuild";
#elif defined(_WIN32)
const char* cmGlobalGhsMultiGenerator::DEFAULT_BUILD_PROGRAM = "gbuild.exe";
#endif
const char* cmGlobalGhsMultiGenerator::CHECK_BUILD_SYSTEM_TARGET =
  "RERUN_CMAKE";

cmGlobalGhsMultiGenerator::cmGlobalGhsMultiGenerator(cmake* cm)
  : cmGlobalGenerator(cm)
{
  cm->GetState()->SetGhsMultiIDE(true);
}

cmGlobalGhsMultiGenerator::~cmGlobalGhsMultiGenerator() = default;

std::unique_ptr<cmLocalGenerator>
cmGlobalGhsMultiGenerator::CreateLocalGenerator(cmMakefile* mf)
{
  return std::unique_ptr<cmLocalGenerator>(
    cm::make_unique<cmLocalGhsMultiGenerator>(this, mf));
}

cmDocumentationEntry cmGlobalGhsMultiGenerator::GetDocumentation()
{
  return {
    GetActualName(),
    "Generates Green Hills MULTI files (experimental, work-in-progress)."
  };
}

void cmGlobalGhsMultiGenerator::ComputeTargetObjectDirectory(
  cmGeneratorTarget* gt) const
{
  // Compute full path to object file directory for this target.
  std::string dir =
    cmStrCat(gt->LocalGenerator->GetCurrentBinaryDirectory(), '/',
             gt->LocalGenerator->GetTargetDirectory(gt), '/');
  gt->ObjectDirectory = dir;
}

bool cmGlobalGhsMultiGenerator::SetGeneratorToolset(std::string const& ts,
                                                    bool build, cmMakefile* mf)
{
  /* In build mode nothing to be done.
   * Toolset already determined and build tool absolute path is cached.
   */
  if (build) {
    return true;
  }

  /* Determine the absolute directory for the toolset */
  std::string tsp;
  this->GetToolset(mf, tsp, ts);

  /* no toolset was found */
  if (tsp.empty()) {
    return false;
  }

  /* set the build tool to use */
  std::string gbuild(tsp + ((tsp.back() == '/') ? "" : "/") +
                     DEFAULT_BUILD_PROGRAM);
  cmValue prevTool = mf->GetDefinition("CMAKE_MAKE_PROGRAM");

  /* check if the toolset changed from last generate */
  if (cmNonempty(prevTool) && !cmSystemTools::ComparePath(gbuild, *prevTool)) {
    std::string const& e = cmStrCat(
      "toolset build tool: ", gbuild, '\n',
      "Does not match the previously used build tool: ", *prevTool, '\n',
      "Either remove the CMakeCache.txt file and CMakeFiles "
      "directory or choose a different binary directory.");
    mf->IssueMessage(MessageType::FATAL_ERROR, e);
    return false;
  }

  /* store the toolset that is being used for this build */
  mf->AddCacheDefinition("CMAKE_MAKE_PROGRAM", gbuild, "build program to use",
                         cmStateEnums::INTERNAL, true);

  mf->AddDefinition("CMAKE_SYSTEM_VERSION", tsp);

  return true;
}

bool cmGlobalGhsMultiGenerator::SetGeneratorPlatform(std::string const& p,
                                                     cmMakefile* mf)
{
  /* set primary target */
  cmValue t = mf->GetDefinition("GHS_PRIMARY_TARGET");
  if (cmIsOff(t)) {
    /* Use the value from `-A` or use `arm` */
    std::string arch = "arm";
    if (!cmIsOff(p)) {
      arch = p;
    }
    cmValue platform = mf->GetDefinition("GHS_TARGET_PLATFORM");
    std::string tgt = cmStrCat(arch, '_', platform, ".tgt");

    /* update the primary target name*/
    mf->AddDefinition("GHS_PRIMARY_TARGET", tgt);
  }
  return true;
}

void cmGlobalGhsMultiGenerator::EnableLanguage(
  std::vector<std::string> const& l, cmMakefile* mf, bool optional)
{
  mf->AddDefinition("CMAKE_SYSTEM_NAME", "GHS-MULTI");

  mf->AddDefinition("GHSMULTI", "1"); // identifier for user CMake files

  this->cmGlobalGenerator::EnableLanguage(l, mf, optional);
}

bool cmGlobalGhsMultiGenerator::FindMakeProgram(cmMakefile* /*mf*/)
{
  // The GHS generator only knows how to lookup its build tool
  // during generation of the project files, but this
  // can only be done after the toolset is specified.

  return true;
}

void cmGlobalGhsMultiGenerator::GetToolset(cmMakefile* mf, std::string& tsp,
                                           const std::string& ts)
{
  /* Determine tsp - full path of the toolset from ts (toolset hint via -T) */

  std::string root = mf->GetSafeDefinition("GHS_TOOLSET_ROOT");

  // Check if `-T` was set by user
  if (ts.empty()) {
    // Enter toolset search mode
    std::vector<std::string> output;

    // Make sure root exists...
    if (!cmSystemTools::PathExists(root)) {
      std::string msg =
        "GHS_TOOLSET_ROOT directory \"" + root + "\" does not exist.";
      mf->IssueMessage(MessageType::FATAL_ERROR, msg);
      tsp = "";
      return;
    }

    // Add a directory separator
    if (root.back() != '/') {
      root += "/";
    }

    // Get all compiler directories in toolset root
    cmSystemTools::Glob(root, "comp_[^;]+", output);

    if (output.empty()) {
      // No compiler directories found
      std::string msg =
        "No GHS toolsets found in GHS_TOOLSET_ROOT \"" + root + "\".";
      mf->IssueMessage(MessageType::FATAL_ERROR, msg);
      tsp = "";
    } else {
      // Use latest? version
      tsp = root + output.back();
    }

  } else {
    // Toolset was provided by user
    std::string tryPath;

    // NOTE: CollapseFullPath() will determine if user toolset was full path or
    //       or relative path.
    tryPath = cmSystemTools::CollapseFullPath(ts, root);
    if (!cmSystemTools::FileExists(tryPath)) {
      std::string msg = "GHS toolset \"" + tryPath + "\" does not exist.";
      mf->IssueMessage(MessageType::FATAL_ERROR, msg);
      tsp = "";
    } else {
      tsp = tryPath;
    }
  }
}

void cmGlobalGhsMultiGenerator::WriteFileHeader(std::ostream& fout)
{
  /* clang-format off */
  fout << "#!gbuild\n"
          "#\n"
          "# CMAKE generated file: DO NOT EDIT!\n"
          "# Generated by \"" << GetActualName() << "\""
          " Generator, CMake Version " << cmVersion::GetMajorVersion() << '.'
       << cmVersion::GetMinorVersion() << "\n"
          "#\n\n";
  /* clang-format on */
}

void cmGlobalGhsMultiGenerator::WriteCustomRuleBOD(std::ostream& fout)
{
  fout << "Commands {\n"
          "  Custom_Rule_Command {\n"
          "    name = \"Custom Rule Command\"\n"
          "    exec = \""
#ifdef _WIN32
          "cmd.exe"
#else
          "/bin/sh"
#endif
          "\"\n"
          "    options = {\"SpecialOptions\"}\n"
          "  }\n"
          "}\n"

          "\n\n"
          "FileTypes {\n"
          "  CmakeRule {\n"
          "    name = \"Custom Rule\"\n"
          "    action = \"&Run\"\n"
          "    extensions = {\""
#ifdef _WIN32
          "bat"
#else
          "sh"
#endif
          "\"}\n"
          "    grepable = false\n"
          "    command = \"Custom Rule Command\"\n"
          "    commandLine = \"$COMMAND "
#ifdef _WIN32
          "/c"
#endif
          " $INPUTFILE\"\n"
          "    progress = \"Processing Custom Rule\"\n"
          "    promoteToFirstPass = true\n"
          "    outputType = \"None\"\n"
          "    color = \"#800080\"\n"
          "  }\n"
          "}\n";
}

void cmGlobalGhsMultiGenerator::WriteCustomTargetBOD(std::ostream& fout)
{
  fout << "FileTypes {\n"
          "  CmakeTarget {\n"
          "    name = \"Custom Target\"\n"
          "    action = \"&Execute\"\n"
          "    grepable = false\n"
          "    outputType = \"None\"\n"
          "    color = \"#800080\"\n"
          "  }\n"
          "}\n";
}

void cmGlobalGhsMultiGenerator::WriteTopLevelProject(std::ostream& fout,
                                                     cmLocalGenerator* root)
{
  this->WriteFileHeader(fout);
  this->WriteMacros(fout, root);
  this->WriteHighLevelDirectives(fout, root);
  GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fout);

  fout << "# Top Level Project File\n";

  // Specify BSP option if supplied by user
  // -- not all platforms require this entry in the project file
  cmValue bspName = root->GetMakefile()->GetDefinition("GHS_BSP_NAME");
  if (!cmIsOff(bspName)) {
    fout << "    -bsp " << *bspName << '\n';
  }

  // Specify OS DIR if supplied by user
  // -- not all platforms require this entry in the project file
  cmValue osDir = root->GetMakefile()->GetDefinition("GHS_OS_DIR");
  if (!cmIsOff(osDir)) {
    cmValue osDirOption =
      root->GetMakefile()->GetDefinition("GHS_OS_DIR_OPTION");
    fout << "    ";
    if (cmIsOff(osDirOption)) {
      fout << "";
    } else {
      fout << *osDirOption;
    }
    fout << "\"" << osDir << "\"\n";
  }
}

void cmGlobalGhsMultiGenerator::WriteSubProjects(std::ostream& fout,
                                                 bool filterPredefined)
{
  std::set<std::string> predefinedTargets;
  predefinedTargets.insert(this->GetInstallTargetName());
  predefinedTargets.insert(this->GetAllTargetName());
  predefinedTargets.insert(std::string(CHECK_BUILD_SYSTEM_TARGET));

  // All known targets
  for (cmGeneratorTarget const* target : this->ProjectTargets) {
    if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
        target->GetType() == cmStateEnums::MODULE_LIBRARY ||
        target->GetType() == cmStateEnums::SHARED_LIBRARY ||
        (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
         target->GetName() != this->GetInstallTargetName())) {
      continue;
    }
    /* Check if the current target is a predefined CMake target */
    bool predefinedTarget =
      predefinedTargets.find(target->GetName()) != predefinedTargets.end();
    if ((filterPredefined && predefinedTarget) ||
        (!filterPredefined && !predefinedTarget)) {
      fout << target->GetName() + ".tgt" + FILE_EXTENSION << " [Project]\n";
    }
  }
}

void cmGlobalGhsMultiGenerator::WriteProjectLine(
  std::ostream& fout, cmGeneratorTarget const* target,
  std::string& rootBinaryDir)
{
  cmValue projFile = target->GetProperty("GENERATOR_FILE_NAME");
  cmValue projType = target->GetProperty("GENERATOR_FILE_NAME_EXT");
  /* If either value is not valid then this particular target is an
   * unsupported target type and should be skipped.
   */
  if (projFile && projType) {
    std::string path = cmSystemTools::RelativePath(rootBinaryDir, *projFile);

    fout << path;
    fout << ' ' << *projType << '\n';
  }
}

void cmGlobalGhsMultiGenerator::WriteTargets(cmLocalGenerator* root)
{
  std::string rootBinaryDir = root->GetCurrentBinaryDirectory();

  // All known targets
  for (cmGeneratorTarget const* target : this->ProjectTargets) {
    if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY ||
        target->GetType() == cmStateEnums::MODULE_LIBRARY ||
        target->GetType() == cmStateEnums::SHARED_LIBRARY ||
        (target->GetType() == cmStateEnums::GLOBAL_TARGET &&
         target->GetName() != this->GetInstallTargetName())) {
      continue;
    }

    // create target build file
    std::string name = cmStrCat(target->GetName(), ".tgt", FILE_EXTENSION);
    std::string fname = cmStrCat(rootBinaryDir, "/", name);
    cmGeneratedFileStream fbld(fname);
    fbld.SetCopyIfDifferent(true);
    this->WriteFileHeader(fbld);
    GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fbld);
    std::vector<cmGeneratorTarget const*> build;
    if (this->ComputeTargetBuildOrder(target, build)) {
      cmSystemTools::Error(
        cmStrCat("The inter-target dependency graph for target [",
                 target->GetName(), "] had a cycle.\n"));
    } else {
      for (auto& tgt : build) {
        this->WriteProjectLine(fbld, tgt, rootBinaryDir);
      }
    }
    fbld.Close();
  }
}

void cmGlobalGhsMultiGenerator::Generate()
{
  std::string fname;

  // first do the superclass method
  this->cmGlobalGenerator::Generate();

  // output top-level projects
  for (auto& it : this->ProjectMap) {
    this->OutputTopLevelProject(it.second[0], it.second);
  }

  // create custom rule BOD file
  fname = this->GetCMakeInstance()->GetHomeOutputDirectory() +
    "/CMakeFiles/custom_rule.bod";
  cmGeneratedFileStream frule(fname);
  frule.SetCopyIfDifferent(true);
  this->WriteFileHeader(frule);
  this->WriteCustomRuleBOD(frule);
  frule.Close();

  // create custom target BOD file
  fname = this->GetCMakeInstance()->GetHomeOutputDirectory() +
    "/CMakeFiles/custom_target.bod";
  cmGeneratedFileStream ftarget(fname);
  ftarget.SetCopyIfDifferent(true);
  this->WriteFileHeader(ftarget);
  this->WriteCustomTargetBOD(ftarget);
  ftarget.Close();
}

void cmGlobalGhsMultiGenerator::OutputTopLevelProject(
  cmLocalGenerator* root, std::vector<cmLocalGenerator*>& generators)
{
  std::string fname;

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

  // Collect all targets under this root generator and the transitive
  // closure of their dependencies.
  TargetDependSet projectTargets;
  TargetDependSet originalTargets;
  this->GetTargetSets(projectTargets, originalTargets, root, generators);
  OrderedTargetDependSet sortedProjectTargets(projectTargets, "");
  this->ProjectTargets.clear();
  for (cmGeneratorTarget const* t : sortedProjectTargets) {
    /* save list of all targets in sorted order */
    this->ProjectTargets.push_back(t);
  }

  /* Name top-level projects as filename.top.gpj to avoid name clashes
   * with target projects.  This avoid the issue where the project has
   * the same name as the executable target.
   */
  fname = cmStrCat(root->GetCurrentBinaryDirectory(), '/',
                   root->GetProjectName(), ".top", FILE_EXTENSION);

  cmGeneratedFileStream top(fname);
  top.SetCopyIfDifferent(true);
  this->WriteTopLevelProject(top, root);
  this->WriteTargets(root);
  this->WriteSubProjects(top, true);
  this->WriteSubProjects(top, false);
  top.Close();
}

std::vector<cmGlobalGenerator::GeneratedMakeCommand>
cmGlobalGhsMultiGenerator::GenerateBuildCommand(
  const std::string& makeProgram, const std::string& projectName,
  const std::string& projectDir, std::vector<std::string> const& targetNames,
  const std::string& /*config*/, int jobs, bool verbose,
  const cmBuildOptions& /*buildOptions*/,
  std::vector<std::string> const& makeOptions)
{
  GeneratedMakeCommand makeCommand;

  makeCommand.Add(this->SelectMakeProgram(makeProgram));

  if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) {
    if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL) {
      makeCommand.Add("-parallel");
    } else {
      makeCommand.Add(std::string("-parallel=") + std::to_string(jobs));
    }
  }

  /* determine the top-project file in the project directory */
  std::string proj = projectName + ".top" + FILE_EXTENSION;
  std::vector<std::string> files;
  cmSystemTools::Glob(projectDir, ".*\\.top\\.gpj", files);
  if (!files.empty()) {
    /* use the real top-level project in the directory */
    proj = files.at(0);
  }
  makeCommand.Add("-top", proj);

  /* determine targets to build */
  bool build_all = false;
  if (!targetNames.empty()) {
    for (const auto& tname : targetNames) {
      if (!tname.empty()) {
        if (tname == "clean") {
          makeCommand.Add("-clean");
        } else {
          makeCommand.Add(tname + ".tgt.gpj");
        }
      } else {
        build_all = true;
      }
    }
  } else {
    build_all = true;
  }

  if (build_all) {
    /* transform name to default build */;
    std::string all = std::string(this->GetAllTargetName()) + ".tgt.gpj";
    makeCommand.Add(all);
  }

  if (verbose) {
    makeCommand.Add("-commands");
  }
  makeCommand.Add(makeOptions.begin(), makeOptions.end());

  return { std::move(makeCommand) };
}

void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout,
                                            cmLocalGenerator* root)
{
  fout << "macro PROJ_NAME=" << root->GetProjectName() << '\n';
  cmValue ghsGpjMacros = root->GetMakefile()->GetDefinition("GHS_GPJ_MACROS");
  if (ghsGpjMacros) {
    cmList expandedList{ *ghsGpjMacros };
    for (std::string const& arg : expandedList) {
      fout << "macro " << arg << '\n';
    }
  }
}

void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives(
  std::ostream& fout, cmLocalGenerator* root)
{
  /* put primary target and customization files into project file */
  cmValue const tgt = root->GetMakefile()->GetDefinition("GHS_PRIMARY_TARGET");

  /* clang-format off */
  fout << "primaryTarget=" << tgt << "\n"
          "customization=" << root->GetBinaryDirectory()
       << "/CMakeFiles/custom_rule.bod\n"
          "customization=" << root->GetBinaryDirectory()
       << "/CMakeFiles/custom_target.bod" << '\n';
  /* clang-format on */

  cmValue const customization =
    root->GetMakefile()->GetDefinition("GHS_CUSTOMIZATION");
  if (cmNonempty(customization)) {
    fout << "customization="
         << cmGlobalGhsMultiGenerator::TrimQuotes(*customization) << '\n';
    this->GetCMakeInstance()->MarkCliAsUsed("GHS_CUSTOMIZATION");
  }
}

std::string cmGlobalGhsMultiGenerator::TrimQuotes(std::string str)
{
  cm::erase(str, '"');
  return str;
}

bool cmGlobalGhsMultiGenerator::TargetCompare::operator()(
  cmGeneratorTarget const* l, cmGeneratorTarget const* r) const
{
  // Make sure a given named target is ordered first,
  // e.g. to set ALL_BUILD as the default active project.
  // When the empty string is named this is a no-op.
  if (r->GetName() == this->First) {
    return false;
  }
  if (l->GetName() == this->First) {
    return true;
  }
  return l->GetName() < r->GetName();
}

cmGlobalGhsMultiGenerator::OrderedTargetDependSet::OrderedTargetDependSet(
  TargetDependSet const& targets, std::string const& first)
  : derived(TargetCompare(first))
{
  this->insert(targets.begin(), targets.end());
}

bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder(
  cmGeneratorTarget const* tgt, std::vector<cmGeneratorTarget const*>& build)
{
  std::vector<cmGeneratorTarget const*> t{ tgt };
  return this->ComputeTargetBuildOrder(t, build);
}

bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder(
  std::vector<cmGeneratorTarget const*>& tgt,
  std::vector<cmGeneratorTarget const*>& build)
{
  std::set<cmGeneratorTarget const*> temp;
  std::set<cmGeneratorTarget const*> perm;

  for (const auto* const ti : tgt) {
    bool r = this->VisitTarget(temp, perm, build, ti);
    if (r) {
      return r;
    }
  }
  return false;
}

bool cmGlobalGhsMultiGenerator::VisitTarget(
  std::set<cmGeneratorTarget const*>& temp,
  std::set<cmGeneratorTarget const*>& perm,
  std::vector<cmGeneratorTarget const*>& order, cmGeneratorTarget const* ti)
{
  /* check if permanent mark is set*/
  if (perm.find(ti) == perm.end()) {
    /* set temporary mark; check if revisit*/
    if (temp.insert(ti).second) {
      /* sort targets lexicographically to ensure that nodes are always visited
       * in the same order */
      OrderedTargetDependSet sortedTargets(this->GetTargetDirectDepends(ti),
                                           "");
      for (auto const& di : sortedTargets) {
        if (this->VisitTarget(temp, perm, order, di)) {
          return true;
        }
      }
      /* mark as complete; insert into beginning of list*/
      perm.insert(ti);
      order.push_back(ti);
      return false;
    }
    /* revisiting item - not a DAG */
    return true;
  }
  /* already complete */
  return false;
}

bool cmGlobalGhsMultiGenerator::AddCheckTarget()
{
  // Skip the target if no regeneration is to be done.
  if (this->GlobalSettingIsOn("CMAKE_SUPPRESS_REGENERATION")) {
    return false;
  }

  // Get the generators.
  std::vector<std::unique_ptr<cmLocalGenerator>> const& generators =
    this->LocalGenerators;
  auto& lg =
    cm::static_reference_cast<cmLocalGhsMultiGenerator>(generators[0]);

  // The name of the output file for the custom command.
  this->StampFile = lg.GetBinaryDirectory() + std::string("/CMakeFiles/") +
    CHECK_BUILD_SYSTEM_TARGET;

  // Add a custom rule to re-run CMake if any input files changed.
  {
    // Collect the input files used to generate all targets in this
    // project.
    std::vector<std::string> listFiles;
    for (const auto& gen : generators) {
      cm::append(listFiles, gen->GetMakefile()->GetListFiles());
    }

    // Add the cache file.
    listFiles.emplace_back(cmStrCat(
      this->GetCMakeInstance()->GetHomeOutputDirectory(), "/CMakeCache.txt"));

    // Print not implemented warning.
    if (this->GetCMakeInstance()->DoWriteGlobVerifyTarget()) {
      std::ostringstream msg;
      msg << "Any pre-check scripts, such as those generated for file(GLOB "
             "CONFIGURE_DEPENDS), will not be run by gbuild.";
      this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
                                             msg.str());
    }

    // Sort the list of input files and remove duplicates.
    std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>());
    auto newEnd = std::unique(listFiles.begin(), listFiles.end());
    listFiles.erase(newEnd, listFiles.end());

    // Create a rule to re-run CMake and create output file.
    cmCustomCommandLines commandLines;
    commandLines.emplace_back(
      cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), "-E", "rm", "-f",
                          this->StampFile }));
    std::string argS = cmStrCat("-S", lg.GetSourceDirectory());
    std::string argB = cmStrCat("-B", lg.GetBinaryDirectory());
    commandLines.emplace_back(
      cmMakeCommandLine({ cmSystemTools::GetCMakeCommand(), argS, argB }));
    commandLines.emplace_back(cmMakeCommandLine(
      { cmSystemTools::GetCMakeCommand(), "-E", "touch", this->StampFile }));

    /* Create the target(Exclude from ALL_BUILD).
     *
     * The build tool, currently, does not support rereading the project files
     * if they get updated. So do not run this target as part of ALL_BUILD.
     */
    auto cc = cm::make_unique<cmCustomCommand>();
    cmTarget* tgt =
      lg.AddUtilityCommand(CHECK_BUILD_SYSTEM_TARGET, true, std::move(cc));
    auto ptr = cm::make_unique<cmGeneratorTarget>(tgt, &lg);
    auto* gt = ptr.get();
    lg.AddGeneratorTarget(std::move(ptr));

    // Add the rule.
    cc = cm::make_unique<cmCustomCommand>();
    cc->SetOutputs(this->StampFile);
    cc->SetDepends(listFiles);
    cc->SetCommandLines(commandLines);
    cc->SetComment("Checking Build System");
    cc->SetEscapeOldStyle(false);
    cc->SetStdPipesUTF8(true);

    if (cmSourceFile* file =
          lg.AddCustomCommandToOutput(std::move(cc), true)) {
      gt->AddSource(file->ResolveFullPath());
    } else {
      cmSystemTools::Error("Error adding rule for " + this->StampFile);
    }
    // Organize in the "predefined targets" folder:
    if (this->UseFolderProperty()) {
      tgt->SetProperty("FOLDER", this->GetPredefinedTargetsFolder());
    }
  }

  return true;
}

void cmGlobalGhsMultiGenerator::AddAllTarget()
{
  // Add a special target that depends on ALL projects for easy build
  // of one configuration only.
  for (auto const& it : this->ProjectMap) {
    std::vector<cmLocalGenerator*> const& gen = it.second;
    // add the ALL_BUILD to the first local generator of each project
    if (!gen.empty()) {
      // Use no actual command lines so that the target itself is not
      // considered always out of date.
      auto cc = cm::make_unique<cmCustomCommand>();
      cc->SetEscapeOldStyle(false);
      cc->SetComment("Build all projects");
      cmTarget* allBuild = gen[0]->AddUtilityCommand(this->GetAllTargetName(),
                                                     true, std::move(cc));

      gen[0]->AddGeneratorTarget(
        cm::make_unique<cmGeneratorTarget>(allBuild, gen[0]));

      // Organize in the "predefined targets" folder:
      if (this->UseFolderProperty()) {
        allBuild->SetProperty("FOLDER", this->GetPredefinedTargetsFolder());
      }

      // Now make all targets depend on the ALL_BUILD target
      for (cmLocalGenerator const* i : gen) {
        for (const auto& tgt : i->GetGeneratorTargets()) {
          // Skip global or imported targets
          if (tgt->GetType() == cmStateEnums::GLOBAL_TARGET ||
              tgt->IsImported()) {
            continue;
          }
          // Skip Exclude From All Targets
          if (!this->IsExcluded(gen[0], tgt.get())) {
            allBuild->AddUtility(tgt->GetName(), false);
          }
        }
      }
    }
  }
}

void cmGlobalGhsMultiGenerator::AddExtraIDETargets()
{
  // Add a special target that depends on ALL projects.
  this->AddAllTarget();

  /* Add Custom Target to check if CMake needs to be rerun.
   *
   * The build tool, currently, does not support rereading the project files
   * if they get updated.  So do not make the other targets dependent on this
   * check.
   */
  this->AddCheckTarget();
}
