| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 | /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying   file Copyright.txt or https://cmake.org/licensing for details.  */#include "cmProjectCommand.h"#include "cmsys/RegularExpression.hxx"#include <functional>#include <sstream>#include <stdio.h>#include "cmAlgorithms.h"#include "cmMakefile.h"#include "cmMessageType.h"#include "cmPolicies.h"#include "cmStateTypes.h"#include "cmSystemTools.h"class cmExecutionStatus;// cmProjectCommandbool cmProjectCommand::InitialPass(std::vector<std::string> const& args,                                   cmExecutionStatus&){  if (args.empty()) {    this->SetError("PROJECT called with incorrect number of arguments");    return false;  }  if (!this->IncludeByVariable("CMAKE_PROJECT_INCLUDE_BEFORE")) {    return false;  }  std::string const& projectName = args[0];  this->Makefile->SetProjectName(projectName);  this->Makefile->AddCacheDefinition(    projectName + "_BINARY_DIR",    this->Makefile->GetCurrentBinaryDirectory().c_str(),    "Value Computed by CMake", cmStateEnums::STATIC);  this->Makefile->AddCacheDefinition(    projectName + "_SOURCE_DIR",    this->Makefile->GetCurrentSourceDirectory().c_str(),    "Value Computed by CMake", cmStateEnums::STATIC);  this->Makefile->AddDefinition(    "PROJECT_BINARY_DIR", this->Makefile->GetCurrentBinaryDirectory().c_str());  this->Makefile->AddDefinition(    "PROJECT_SOURCE_DIR", this->Makefile->GetCurrentSourceDirectory().c_str());  this->Makefile->AddDefinition("PROJECT_NAME", projectName.c_str());  // Set the CMAKE_PROJECT_NAME variable to be the highest-level  // project name in the tree. If there are two project commands  // in the same CMakeLists.txt file, and it is the top level  // CMakeLists.txt file, then go with the last one, so that  // CMAKE_PROJECT_NAME will match PROJECT_NAME, and cmake --build  // will work.  if (!this->Makefile->GetDefinition("CMAKE_PROJECT_NAME") ||      (this->Makefile->IsRootMakefile())) {    this->Makefile->AddDefinition("CMAKE_PROJECT_NAME", projectName.c_str());    this->Makefile->AddCacheDefinition(      "CMAKE_PROJECT_NAME", projectName.c_str(), "Value Computed by CMake",      cmStateEnums::STATIC);  }  bool haveVersion = false;  bool haveLanguages = false;  bool haveDescription = false;  bool haveHomepage = false;  bool injectedProjectCommand = false;  std::string version;  std::string description;  std::string homepage;  std::vector<std::string> languages;  std::function<void()> missedValueReporter;  auto resetReporter = [&missedValueReporter]() {    missedValueReporter = std::function<void()>();  };  enum Doing  {    DoingDescription,    DoingHomepage,    DoingLanguages,    DoingVersion  };  Doing doing = DoingLanguages;  for (size_t i = 1; i < args.size(); ++i) {    if (args[i] == "LANGUAGES") {      if (haveLanguages) {        this->Makefile->IssueMessage(          MessageType::FATAL_ERROR,          "LANGUAGES may be specified at most once.");        cmSystemTools::SetFatalErrorOccured();        return true;      }      haveLanguages = true;      if (missedValueReporter) {        missedValueReporter();      }      doing = DoingLanguages;      if (!languages.empty()) {        std::string msg =          "the following parameters must be specified after LANGUAGES "          "keyword: ";        msg += cmJoin(languages, ", ");        msg += '.';        this->Makefile->IssueMessage(MessageType::WARNING, msg);      }    } else if (args[i] == "VERSION") {      if (haveVersion) {        this->Makefile->IssueMessage(MessageType::FATAL_ERROR,                                     "VERSION may be specified at most once.");        cmSystemTools::SetFatalErrorOccured();        return true;      }      haveVersion = true;      if (missedValueReporter) {        missedValueReporter();      }      doing = DoingVersion;      missedValueReporter = [this, &resetReporter]() {        this->Makefile->IssueMessage(          MessageType::WARNING,          "VERSION keyword not followed by a value or was followed by a "          "value that expanded to nothing.");        resetReporter();      };    } else if (args[i] == "DESCRIPTION") {      if (haveDescription) {        this->Makefile->IssueMessage(          MessageType::FATAL_ERROR,          "DESCRIPTION may be specified at most once.");        cmSystemTools::SetFatalErrorOccured();        return true;      }      haveDescription = true;      if (missedValueReporter) {        missedValueReporter();      }      doing = DoingDescription;      missedValueReporter = [this, &resetReporter]() {        this->Makefile->IssueMessage(          MessageType::WARNING,          "DESCRIPTION keyword not followed by a value or was followed "          "by a value that expanded to nothing.");        resetReporter();      };    } else if (args[i] == "HOMEPAGE_URL") {      if (haveHomepage) {        this->Makefile->IssueMessage(          MessageType::FATAL_ERROR,          "HOMEPAGE_URL may be specified at most once.");        cmSystemTools::SetFatalErrorOccured();        return true;      }      haveHomepage = true;      doing = DoingHomepage;      missedValueReporter = [this, &resetReporter]() {        this->Makefile->IssueMessage(          MessageType::WARNING,          "HOMEPAGE_URL keyword not followed by a value or was followed "          "by a value that expanded to nothing.");        resetReporter();      };    } else if (i == 1 && args[i] == "__CMAKE_INJECTED_PROJECT_COMMAND__") {      injectedProjectCommand = true;    } else if (doing == DoingVersion) {      doing = DoingLanguages;      version = args[i];      resetReporter();    } else if (doing == DoingDescription) {      doing = DoingLanguages;      description = args[i];      resetReporter();    } else if (doing == DoingHomepage) {      doing = DoingLanguages;      homepage = args[i];      resetReporter();    } else // doing == DoingLanguages    {      languages.push_back(args[i]);    }  }  if (missedValueReporter) {    missedValueReporter();  }  if ((haveVersion || haveDescription || haveHomepage) && !haveLanguages &&      !languages.empty()) {    this->Makefile->IssueMessage(      MessageType::FATAL_ERROR,      "project with VERSION, DESCRIPTION or HOMEPAGE_URL must "      "use LANGUAGES before language names.");    cmSystemTools::SetFatalErrorOccured();    return true;  }  if (haveLanguages && languages.empty()) {    languages.emplace_back("NONE");  }  cmPolicies::PolicyStatus const cmp0048 =    this->Makefile->GetPolicyStatus(cmPolicies::CMP0048);  if (haveVersion) {    // Set project VERSION variables to given values    if (cmp0048 == cmPolicies::OLD || cmp0048 == cmPolicies::WARN) {      this->Makefile->IssueMessage(        MessageType::FATAL_ERROR,        "VERSION not allowed unless CMP0048 is set to NEW");      cmSystemTools::SetFatalErrorOccured();      return true;    }    cmsys::RegularExpression vx(      R"(^([0-9]+(\.[0-9]+(\.[0-9]+(\.[0-9]+)?)?)?)?$)");    if (!vx.find(version)) {      std::string e = "VERSION \"" + version + "\" format invalid.";      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);      cmSystemTools::SetFatalErrorOccured();      return true;    }    constexpr std::size_t MAX_VERSION_COMPONENTS = 4u;    std::string vs;    char vb[MAX_VERSION_COMPONENTS][64];    unsigned int v[MAX_VERSION_COMPONENTS] = { 0, 0, 0, 0 };    int vc =      sscanf(version.c_str(), "%u.%u.%u.%u", &v[0], &v[1], &v[2], &v[3]);    for (auto i = 0u; i < MAX_VERSION_COMPONENTS; ++i) {      if (int(i) < vc) {        sprintf(vb[i], "%u", v[i]);        vs += &"."[size_t(i == 0)];        vs += vb[i];      } else {        vb[i][0] = 0;      }    }    std::string vv;    vv = projectName + "_VERSION";    this->Makefile->AddDefinition("PROJECT_VERSION", vs.c_str());    this->Makefile->AddDefinition(vv, vs.c_str());    vv = projectName + "_VERSION_MAJOR";    this->Makefile->AddDefinition("PROJECT_VERSION_MAJOR", vb[0]);    this->Makefile->AddDefinition(vv, vb[0]);    vv = projectName + "_VERSION_MINOR";    this->Makefile->AddDefinition("PROJECT_VERSION_MINOR", vb[1]);    this->Makefile->AddDefinition(vv, vb[1]);    vv = projectName + "_VERSION_PATCH";    this->Makefile->AddDefinition("PROJECT_VERSION_PATCH", vb[2]);    this->Makefile->AddDefinition(vv, vb[2]);    vv = projectName + "_VERSION_TWEAK";    this->Makefile->AddDefinition("PROJECT_VERSION_TWEAK", vb[3]);    this->Makefile->AddDefinition(vv, vb[3]);    // Also, try set top level variables    TopLevelCMakeVarCondSet("CMAKE_PROJECT_VERSION", vs.c_str());    TopLevelCMakeVarCondSet("CMAKE_PROJECT_VERSION_MAJOR", vb[0]);    TopLevelCMakeVarCondSet("CMAKE_PROJECT_VERSION_MINOR", vb[1]);    TopLevelCMakeVarCondSet("CMAKE_PROJECT_VERSION_PATCH", vb[2]);    TopLevelCMakeVarCondSet("CMAKE_PROJECT_VERSION_TWEAK", vb[3]);  } else if (cmp0048 != cmPolicies::OLD) {    // Set project VERSION variables to empty    std::vector<std::string> vv = { "PROJECT_VERSION",                                    "PROJECT_VERSION_MAJOR",                                    "PROJECT_VERSION_MINOR",                                    "PROJECT_VERSION_PATCH",                                    "PROJECT_VERSION_TWEAK",                                    projectName + "_VERSION",                                    projectName + "_VERSION_MAJOR",                                    projectName + "_VERSION_MINOR",                                    projectName + "_VERSION_PATCH",                                    projectName + "_VERSION_TWEAK" };    if (this->Makefile->IsRootMakefile()) {      vv.emplace_back("CMAKE_PROJECT_VERSION");      vv.emplace_back("CMAKE_PROJECT_VERSION_MAJOR");      vv.emplace_back("CMAKE_PROJECT_VERSION_MINOR");      vv.emplace_back("CMAKE_PROJECT_VERSION_PATCH");      vv.emplace_back("CMAKE_PROJECT_VERSION_TWEAK");    }    std::string vw;    for (std::string const& i : vv) {      const char* const v = this->Makefile->GetDefinition(i);      if (v && *v) {        if (cmp0048 == cmPolicies::WARN) {          if (!injectedProjectCommand) {            vw += "\n  ";            vw += i;          }        } else {          this->Makefile->AddDefinition(i, "");        }      }    }    if (!vw.empty()) {      std::ostringstream w;      w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0048)        << "\nThe following variable(s) would be set to empty:" << vw;      this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str());    }  }  this->Makefile->AddDefinition("PROJECT_DESCRIPTION", description.c_str());  this->Makefile->AddDefinition(projectName + "_DESCRIPTION",                                description.c_str());  TopLevelCMakeVarCondSet("CMAKE_PROJECT_DESCRIPTION", description.c_str());  this->Makefile->AddDefinition("PROJECT_HOMEPAGE_URL", homepage.c_str());  this->Makefile->AddDefinition(projectName + "_HOMEPAGE_URL",                                homepage.c_str());  TopLevelCMakeVarCondSet("CMAKE_PROJECT_HOMEPAGE_URL", homepage.c_str());  if (languages.empty()) {    // if no language is specified do c and c++    languages = { "C", "CXX" };  }  this->Makefile->EnableLanguage(languages, false);  if (!this->IncludeByVariable("CMAKE_PROJECT_INCLUDE")) {    return false;  }  if (!this->IncludeByVariable("CMAKE_PROJECT_" + projectName + "_INCLUDE")) {    return false;  }  return true;}bool cmProjectCommand::IncludeByVariable(const std::string& variable){  const char* const include = this->Makefile->GetDefinition(variable);  if (!include) {    return true;  }  const bool readit = this->Makefile->ReadDependentFile(include);  if (readit) {    return true;  }  if (cmSystemTools::GetFatalErrorOccured()) {    return true;  }  std::string m = "could not find file:\n"                  "  ";  m += include;  this->SetError(m);  return false;}void cmProjectCommand::TopLevelCMakeVarCondSet(const char* const name,                                               const char* const value){  // Set the CMAKE_PROJECT_XXX variable to be the highest-level  // project name in the tree. If there are two project commands  // in the same CMakeLists.txt file, and it is the top level  // CMakeLists.txt file, then go with the last one.  if (!this->Makefile->GetDefinition(name) ||      (this->Makefile->IsRootMakefile())) {    this->Makefile->AddDefinition(name, value);    this->Makefile->AddCacheDefinition(name, value, "Value Computed by CMake",                                       cmStateEnums::STATIC);  }}
 |