/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
/* clang-format off */
#include "cmGeneratorTarget.h"
/* clang-format on */

#include "cmConfigure.h"

#include <algorithm>
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include <cm/string_view>
#include <cmext/algorithm>

#include "cmsys/RegularExpression.hxx"

#include "cmEvaluatedTargetProperty.h"
#include "cmFileSetMetadata.h"
#include "cmGenExContext.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionDAGChecker.h"
#include "cmGeneratorFileSet.h"
#include "cmGeneratorFileSets.h"
#include "cmGlobalGenerator.h"
#include "cmLinkItem.h"
#include "cmList.h"
#include "cmListFileCache.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmSourceFile.h"
#include "cmSourceFileLocation.h"
#include "cmSourceGroup.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
#include "cmake.h"

namespace {
using UseTo = cmGeneratorTarget::UseTo;

void AddObjectEntries(cmGeneratorTarget const* headTarget,
                      cm::GenEx::Context const& context,
                      cmGeneratorExpressionDAGChecker* dagChecker,
                      cm::EvaluatedTargetPropertyEntries& entries)
{
  if (cmLinkImplementationLibraries const* impl =
        headTarget->GetLinkImplementationLibraries(context.Config,
                                                   UseTo::Link)) {
    entries.HadContextSensitiveCondition = impl->HadContextSensitiveCondition;
    for (cmLinkItem const& lib : impl->Libraries) {
      if (lib.Target &&
          lib.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
        std::string uniqueName =
          headTarget->GetGlobalGenerator()->IndexGeneratorTargetUniquely(
            lib.Target);
        std::string genex =
          cmStrCat("$<TARGET_OBJECTS:", std::move(uniqueName), '>');
        cmGeneratorExpression ge(*headTarget->Makefile->GetCMakeInstance(),
                                 lib.Backtrace);
        std::unique_ptr<cmCompiledGeneratorExpression> cge =
          ge.Parse(std::move(genex));
        cge->SetEvaluateForBuildsystem(true);

        cm::EvaluatedTargetPropertyEntry ee(lib, lib.Backtrace);
        cmExpandList(cge->Evaluate(context, dagChecker, headTarget),
                     ee.Values);
        if (cge->GetHadContextSensitiveCondition()) {
          ee.ContextDependent = true;
        }
        entries.Entries.emplace_back(std::move(ee));
      }
    }
  }
}

void AddFileSetEntries(cmGeneratorTarget const* headTarget,
                       cmGeneratorFileSets const* fileSets,
                       cm::GenEx::Context const& context,
                       cmGeneratorExpressionDAGChecker* dagChecker,
                       cm::EvaluatedTargetPropertyEntries& entries)
{
  auto sources = fileSets->GetSources(context, headTarget, dagChecker);
  entries =
    EvaluateTargetPropertyEntries(headTarget, context, dagChecker, sources);
}

bool processSources(cmGeneratorTarget const* tgt, std::string const& config,
                    cm::EvaluatedTargetPropertyEntries& entries,
                    std::vector<BT<std::string>>& srcs,
                    std::unordered_set<std::string>& uniqueSrcs,
                    bool debugSources,
                    std::function<void(cmSourceFile*)> postProcess = {})
{
  cmMakefile* mf = tgt->Target->GetMakefile();

  bool contextDependent = entries.HadContextSensitiveCondition;

  for (cm::EvaluatedTargetPropertyEntry& entry : entries.Entries) {
    if (entry.ContextDependent) {
      contextDependent = true;
    }

    cmLinkItem const& item = entry.LinkItem;
    std::string const& targetName = item.AsStr();

    for (std::string& src : entry.Values) {
      cmSourceFile* sf = mf->GetOrCreateSource(src);
      std::string e;
      std::string w;
      std::string fullPath = sf->ResolveFullPath(&e, &w);
      cmLocalGenerator const* const lg = tgt->GetLocalGenerator();
      if (!w.empty()) {
        lg->IssuePolicyWarning(cmPolicies::CMP0115, {}, w, entry.Backtrace);
      }
      if (fullPath.empty()) {
        if (!e.empty()) {
          lg->IssueMessage(MessageType::FATAL_ERROR, e, entry.Backtrace);
        }
        return contextDependent;
      }

      if (!targetName.empty() && !cmSystemTools::FileIsFullPath(src)) {
        std::ostringstream err;
        if (!targetName.empty()) {
          err << "Target \"" << targetName
              << "\" contains relative path in its INTERFACE_SOURCES:\n  \""
              << src << "\"";
        } else {
          err << "Found relative path while evaluating sources of \""
              << tgt->GetName() << "\":\n  \"" << src << "\"\n";
        }
        tgt->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR,
                                               err.str());
        return contextDependent;
      }
      src = fullPath;

      if (postProcess) {
        postProcess(sf);
      }
    }
    std::string usedSources;
    for (std::string const& src : entry.Values) {
      if (uniqueSrcs.insert(src).second) {
        srcs.emplace_back(src, entry.Backtrace);
        if (debugSources) {
          usedSources += cmStrCat(" * ", src, '\n');
        }
      } else {
        auto const& fileSets =
          tgt->GetGeneratorFileSets()->GetAllFileSetsForSource(config, src);
        if (fileSets.empty()) {
          continue;
        }
        auto fileMustBeUnique = [&fileSets]() -> bool {
          return std::none_of(
            fileSets.begin(), fileSets.end(),
            [](cmGeneratorFileSet const* fileSet) {
              return cm::FileSetMetadata::GetAttributes(fileSet->GetType())
                .contains(cm::FileSetMetadata::FileSetAttributes::
                            FilesInMultipleFileSets);
            });
        };
        if (fileMustBeUnique()) {
          auto const* fileSet = *fileSets.begin();
          switch (tgt->GetPolicyStatusCMP0211()) {
            case cmPolicies::WARN:
              tgt->GetLocalGenerator()->IssuePolicyWarning(
                cmPolicies::CMP0211, {},
                cmStrCat("In target \"", tgt->GetName(), "\" the file\n  ",
                         src, "\nalready belongs to file set \"",
                         fileSet->GetName(), "\"."));
              CM_FALLTHROUGH;
            case cmPolicies::OLD:
              break;
            default:
              tgt->GetLocalGenerator()->IssueMessage(
                MessageType::FATAL_ERROR,
                cmStrCat("In target \"", tgt->GetName(), "\" the file\n  ",
                         src, "\nalready belongs to file set \"",
                         fileSet->GetName(), "\"."));
          }
        }
      }
    }
    if (!usedSources.empty()) {
      tgt->GetLocalGenerator()->GetCMakeInstance()->IssueMessage(
        MessageType::LOG,
        cmStrCat("Used sources for target ", tgt->GetName(), ":\n",
                 usedSources),
        entry.Backtrace);
    }
  }
  return contextDependent;
}
}

std::vector<BT<std::string>> cmGeneratorTarget::GetSourceFilePaths(
  std::string const& config) const
{
  std::vector<BT<std::string>> files;

  cmList debugProperties{ this->Makefile->GetDefinition(
    "CMAKE_DEBUG_TARGET_PROPERTIES") };
  bool debugSources =
    !this->DebugSourcesDone && cm::contains(debugProperties, "SOURCES");

  this->DebugSourcesDone = true;

  cm::GenEx::Context context(this->LocalGenerator, config,
                             /*language=*/std::string());

  cmGeneratorExpressionDAGChecker dagChecker{
    this, "SOURCES", nullptr, nullptr, context,
  };

  cm::EvaluatedTargetPropertyEntries entries =
    cm::EvaluateTargetPropertyEntries(this, context, &dagChecker,
                                      this->SourceEntries);

  std::unordered_set<std::string> uniqueSrcs;
  bool contextDependentDirectSources =
    processSources(this, config, entries, files, uniqueSrcs, debugSources);

  // Collect INTERFACE_SOURCES of all direct link-dependencies.
  cm::EvaluatedTargetPropertyEntries linkInterfaceSourcesEntries;
  cm::AddInterfaceEntries(this, "INTERFACE_SOURCES", context, &dagChecker,
                          linkInterfaceSourcesEntries,
                          cm::IncludeRuntimeInterface::No, UseTo::Compile);
  bool contextDependentInterfaceSources =
    processSources(this, config, linkInterfaceSourcesEntries, files,
                   uniqueSrcs, debugSources);

  // Collect TARGET_OBJECTS of direct object link-dependencies.
  bool contextDependentObjects = false;
  if (this->GetType() != cmStateEnums::OBJECT_LIBRARY) {
    cm::EvaluatedTargetPropertyEntries linkObjectsEntries;
    AddObjectEntries(this, context, &dagChecker, linkObjectsEntries);
    contextDependentObjects = processSources(this, config, linkObjectsEntries,
                                             files, uniqueSrcs, debugSources);
    // Note that for imported targets or multi-config generators supporting
    // cross-config builds the paths to the object files must be per-config,
    // so contextDependentObjects will be true here even if object libraries
    // are specified without per-config generator expressions.
  }

  // Collect this target's file sets.
  cmGeneratorExpressionDAGChecker fsDagChecker{
    this, "SOURCES", nullptr, nullptr, context,
  };

  cm::EvaluatedTargetPropertyEntries fileSetEntries;
  AddFileSetEntries(this, this->FileSets.get(), context, &fsDagChecker,
                    fileSetEntries);
  auto processFileSetEntry = [this, &config](cmSourceFile* sf) {
    cmGeneratorFileSet const* fileSet = this->GetFileSetForSource(config, sf);
    if (fileSet && fileSet->GetType() == cm::FileSetMetadata::HEADERS) {
      sf->SetProperty("HEADER_FILE_ONLY", "TRUE");
#if !defined(CMAKE_BOOTSTRAP)
      cmMakefile* mf = this->Target->GetMakefile();
      std::string const& path = sf->GetFullPath();
      bool found = false;
      for (auto const& sg : mf->GetSourceGroups()) {
        if (sg->MatchChildrenFiles(path)) {
          found = true;
          break;
        }
      }
      if (!found) {
        mf->GetOrCreateSourceGroup("Header Files")->AddGroupFile(path);
      }
#endif
    }
  };
  bool contextDependentFileSets =
    processSources(this, config, fileSetEntries, files, uniqueSrcs,
                   debugSources, processFileSetEntry);

  // Collect file sets INTERFACE_SOURCES of all direct link-dependencies.
  cm::EvaluatedTargetPropertyEntries linkInterfaceFileSetsEntries;
  cm::AddInterfaceFileSetsEntries(this, cm::FileSetMetadata::SOURCES,
                                  "INTERFACE_SOURCES", context, &fsDagChecker,
                                  linkInterfaceFileSetsEntries);
  bool contextDependentInterfaceFileSets =
    processSources(this, config, linkInterfaceFileSetsEntries, files,
                   uniqueSrcs, debugSources);

  // Determine if sources are context-dependent or not.
  if (!contextDependentDirectSources && !contextDependentInterfaceSources &&
      !contextDependentObjects && !contextDependentFileSets &&
      !contextDependentInterfaceFileSets) {
    this->SourcesAreContextDependent = Tribool::False;
  } else {
    this->SourcesAreContextDependent = Tribool::True;
  }

  return files;
}

void cmGeneratorTarget::GetSourceFiles(std::vector<cmSourceFile*>& files,
                                       std::string const& config) const
{
  std::vector<BT<cmSourceFile*>> tmp = this->GetSourceFiles(config);
  files.reserve(tmp.size());
  for (BT<cmSourceFile*>& v : tmp) {
    files.push_back(v.Value);
  }
}

std::vector<BT<cmSourceFile*>> cmGeneratorTarget::GetSourceFiles(
  std::string const& config) const
{
  std::vector<BT<cmSourceFile*>> files;
  KindedSources const& kinded = this->GetKindedSources(config);
  files.reserve(kinded.Sources.size());
  for (SourceAndKind const& si : kinded.Sources) {
    files.push_back(si.Source);
  }
  return files;
}

void cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries(
  std::vector<cmSourceFile*>& files, std::string const& config) const
{
  std::vector<BT<cmSourceFile*>> tmp =
    this->GetSourceFilesWithoutObjectLibraries(config);
  files.reserve(tmp.size());
  for (BT<cmSourceFile*>& v : tmp) {
    files.push_back(v.Value);
  }
}

std::vector<BT<cmSourceFile*>>
cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries(
  std::string const& config) const
{
  std::vector<BT<cmSourceFile*>> files;
  KindedSources const& kinded = this->GetKindedSources(config);
  files.reserve(kinded.Sources.size());
  for (SourceAndKind const& si : kinded.Sources) {
    if (si.Source.Value->GetObjectLibrary().empty()) {
      files.push_back(si.Source);
    }
  }
  return files;
}

cmGeneratorTarget::KindedSources const& cmGeneratorTarget::GetKindedSources(
  std::string const& config) const
{
  // If we already processed one configuration and found no dependency
  // on configuration then always use the one result.
  if (this->SourcesAreContextDependent == Tribool::False) {
    return this->KindedSourcesMap.begin()->second;
  }

  // Lookup any existing link implementation for this configuration.
  std::string const key = cmSystemTools::UpperCase(config);
  auto it = this->KindedSourcesMap.find(key);
  if (it != this->KindedSourcesMap.end()) {
    if (!it->second.Initialized) {
      std::ostringstream e;
      e << "The SOURCES of \"" << this->GetName()
        << "\" use a generator expression that depends on the "
           "SOURCES themselves.";
      this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
        MessageType::FATAL_ERROR, e.str(), this->GetBacktrace());
      static KindedSources empty;
      return empty;
    }
    return it->second;
  }

  // Add an entry to the map for this configuration.
  KindedSources& files = this->KindedSourcesMap[key];
  this->ComputeKindedSources(files, config);
  files.Initialized = true;
  return files;
}

void cmGeneratorTarget::ComputeKindedSources(KindedSources& files,
                                             std::string const& config) const
{
  // Get the source file paths by string.
  std::vector<BT<std::string>> srcs = this->GetSourceFilePaths(config);

  cmsys::RegularExpression header_regex(CM_HEADER_REGEX);
  std::vector<cmSourceFile*> badObjLib;

  cmValue const rustMainCrateRootProp =
    this->GetProperty("Rust_MAIN_CRATE_ROOT");
  cmSourceFile const* rustMainCrateRootSf = rustMainCrateRootProp
    ? this->Makefile->GetOrCreateSource(rustMainCrateRootProp)
    : nullptr;

  std::set<cmSourceFile*> emitted;
  for (BT<std::string> const& s : srcs) {
    // Create each source at most once.
    cmSourceFile* sf = this->Makefile->GetOrCreateSource(s.Value);
    if (!emitted.insert(sf).second) {
      continue;
    }

    // Compute the kind (classification) of this source file.
    SourceKind kind;
    std::string ext = cmSystemTools::LowerCase(sf->GetExtension());
    cmGeneratorFileSet const* fs = this->GetFileSetForSource(config, sf);
    if (sf->GetCustomCommand()) {
      kind = SourceKindCustomCommand;
    } else if (!this->Target->IsNormal() && !this->Target->IsImported() &&
               fs && (fs->GetType() == cm::FileSetMetadata::CXX_MODULES)) {
      kind = SourceKindCxxModuleSource;
    } else if (this->Target->GetType() == cmStateEnums::UTILITY ||
               this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY
               // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
               // NOLINTNEXTLINE(bugprone-branch-clone)
    ) {
      kind = SourceKindExtra;
    } else if (this->IsSourceFilePartOfUnityBatch(sf->ResolveFullPath())) {
      kind = SourceKindUnityBatched;
      // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
      // NOLINTNEXTLINE(bugprone-branch-clone)
    } else if (sf->GetPropertyAsBool("HEADER_FILE_ONLY")) {
      kind = SourceKindHeader;
    } else if (sf->GetPropertyAsBool("EXTERNAL_OBJECT")) {
      kind = SourceKindExternalObject;
    } else if (!sf->GetOrDetermineLanguage().empty()) {
      if (sf->GetOrDetermineLanguage() == "Rust") {
        // NOLINTNEXTLINE(bugprone-branch-clone)
        if (this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
          // There is no main crate root for object libraries.
          kind = SourceKindObjectSource;
        } else if (!rustMainCrateRootSf) {
          // We do not have a main crate root source file, we use the first
          // Rust source file for it.
          rustMainCrateRootSf = sf;
          kind = SourceKindRustMainCrateRoot;
        } else if (rustMainCrateRootSf == sf) {
          // Current source file is the main crate root defined in the target
          // Rust_MAIN_CRATE_ROOT property.
          kind = SourceKindRustMainCrateRoot;
        } else {
          // Any other Rust source file is treated as a normal object, but will
          // be built into a .rlib. Maybe in the future this could be changed?
          kind = SourceKindObjectSource;
        }
      } else {
        kind = SourceKindObjectSource;
      }
    } else if (ext == "def") {
      kind = SourceKindModuleDefinition;
      if (this->GetType() == cmStateEnums::OBJECT_LIBRARY) {
        badObjLib.push_back(sf);
      }
    } else if (ext == "idl") {
      kind = SourceKindIDL;
      if (this->GetType() == cmStateEnums::OBJECT_LIBRARY) {
        badObjLib.push_back(sf);
      }
    } else if (ext == "resx") {
      kind = SourceKindResx;
    } else if (ext == "appxmanifest") {
      kind = SourceKindAppManifest;
    } else if (ext == "manifest") {
      if (sf->GetPropertyAsBool("VS_DEPLOYMENT_CONTENT")) {
        kind = SourceKindExtra;
      } else {
        kind = SourceKindManifest;
      }
    } else if (ext == "pfx") {
      kind = SourceKindCertificate;
    } else if (ext == "xaml") {
      kind = SourceKindXaml;
    } else if (header_regex.find(sf->ResolveFullPath())) {
      kind = SourceKindHeader;
    } else {
      kind = SourceKindExtra;
    }

    // Save this classified source file in the result vector.
    files.Sources.push_back({ BT<cmSourceFile*>(sf, s.Backtrace), kind });
  }

  if (!badObjLib.empty()) {
    std::ostringstream e;
    e << "OBJECT library \"" << this->GetName() << "\" contains:\n";
    for (cmSourceFile* i : badObjLib) {
      e << "  " << i->GetLocation().GetName() << "\n";
    }
    e << "but may contain only sources that compile, header files, and "
         "other files that would not affect linking of a normal library.";
    this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
      MessageType::FATAL_ERROR, e.str(), this->GetBacktrace());
  }
}

std::vector<cmGeneratorTarget::AllConfigSource> const&
cmGeneratorTarget::GetAllConfigSources() const
{
  if (this->AllConfigSources.empty()) {
    this->ComputeAllConfigSources();
  }
  return this->AllConfigSources;
}

void cmGeneratorTarget::ComputeAllConfigSources() const
{
  std::vector<std::string> configs =
    this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);

  std::map<cmSourceFile const*, size_t> index;

  for (size_t ci = 0; ci < configs.size(); ++ci) {
    KindedSources const& sources = this->GetKindedSources(configs[ci]);
    for (SourceAndKind const& src : sources.Sources) {
      auto mi = index.find(src.Source.Value);
      if (mi == index.end()) {
        AllConfigSource acs;
        acs.Source = src.Source.Value;
        acs.Kind = src.Kind;
        this->AllConfigSources.push_back(std::move(acs));
        std::map<cmSourceFile const*, size_t>::value_type entry(
          src.Source.Value, this->AllConfigSources.size() - 1);
        mi = index.insert(entry).first;
      }
      this->AllConfigSources[mi->second].Configs.push_back(ci);
    }
  }
}

std::vector<cmGeneratorTarget::AllConfigSource>
cmGeneratorTarget::GetAllConfigSources(SourceKind kind) const
{
  std::vector<AllConfigSource> result;
  for (AllConfigSource const& source : this->GetAllConfigSources()) {
    if (source.Kind == kind) {
      result.push_back(source);
    }
  }
  return result;
}

void cmGeneratorTarget::ComputeAllConfigCompileLanguages() const
{
  std::set<std::string> languages;
  std::vector<AllConfigSource> const& sources = this->GetAllConfigSources();
  for (AllConfigSource const& si : sources) {
    std::string const& lang = si.Source->GetOrDetermineLanguage();
    if (!lang.empty()) {
      languages.emplace(lang);
    }
  }
  this->AllConfigCompileLanguages = languages;
}

std::set<std::string> cmGeneratorTarget::GetAllConfigCompileLanguages() const
{
  if (this->AllConfigCompileLanguages.empty()) {
    this->ComputeAllConfigCompileLanguages();
  }
  return this->AllConfigCompileLanguages;
}
