| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 | /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying   file Copyright.txt or https://cmake.org/licensing for details.  */#include "cmAddCustomCommandCommand.h"#include <sstream>#include <unordered_set>#include <utility>#include "cmCustomCommand.h"#include "cmCustomCommandLines.h"#include "cmGlobalGenerator.h"#include "cmMakefile.h"#include "cmMessageType.h"#include "cmPolicies.h"#include "cmSourceFile.h"#include "cmSystemTools.h"#include "cmTarget.h"class cmExecutionStatus;// cmAddCustomCommandCommandbool cmAddCustomCommandCommand::InitialPass(  std::vector<std::string> const& args, cmExecutionStatus&){  /* Let's complain at the end of this function about the lack of a particular     arg. For the moment, let's say that COMMAND, and either TARGET or SOURCE     are required.  */  if (args.size() < 4) {    this->SetError("called with wrong number of arguments.");    return false;  }  std::string source, target, main_dependency, working, depfile, job_pool;  std::string comment_buffer;  const char* comment = nullptr;  std::vector<std::string> depends, outputs, output, byproducts;  bool verbatim = false;  bool append = false;  bool uses_terminal = false;  bool command_expand_lists = false;  std::string implicit_depends_lang;  cmCustomCommand::ImplicitDependsList implicit_depends;  // Accumulate one command line at a time.  cmCustomCommandLine currentLine;  // Save all command lines.  cmCustomCommandLines commandLines;  cmTarget::CustomCommandType cctype = cmTarget::POST_BUILD;  enum tdoing  {    doing_source,    doing_command,    doing_target,    doing_depends,    doing_implicit_depends_lang,    doing_implicit_depends_file,    doing_main_dependency,    doing_output,    doing_outputs,    doing_byproducts,    doing_comment,    doing_working_directory,    doing_depfile,    doing_job_pool,    doing_nothing  };  tdoing doing = doing_nothing;#define MAKE_STATIC_KEYWORD(KEYWORD)                                          \  static const std::string key##KEYWORD = #KEYWORD  MAKE_STATIC_KEYWORD(APPEND);  MAKE_STATIC_KEYWORD(ARGS);  MAKE_STATIC_KEYWORD(BYPRODUCTS);  MAKE_STATIC_KEYWORD(COMMAND);  MAKE_STATIC_KEYWORD(COMMAND_EXPAND_LISTS);  MAKE_STATIC_KEYWORD(COMMENT);  MAKE_STATIC_KEYWORD(DEPENDS);  MAKE_STATIC_KEYWORD(DEPFILE);  MAKE_STATIC_KEYWORD(IMPLICIT_DEPENDS);  MAKE_STATIC_KEYWORD(JOB_POOL);  MAKE_STATIC_KEYWORD(MAIN_DEPENDENCY);  MAKE_STATIC_KEYWORD(OUTPUT);  MAKE_STATIC_KEYWORD(OUTPUTS);  MAKE_STATIC_KEYWORD(POST_BUILD);  MAKE_STATIC_KEYWORD(PRE_BUILD);  MAKE_STATIC_KEYWORD(PRE_LINK);  MAKE_STATIC_KEYWORD(SOURCE);  MAKE_STATIC_KEYWORD(TARGET);  MAKE_STATIC_KEYWORD(USES_TERMINAL);  MAKE_STATIC_KEYWORD(VERBATIM);  MAKE_STATIC_KEYWORD(WORKING_DIRECTORY);#undef MAKE_STATIC_KEYWORD  static std::unordered_set<std::string> const keywords{    keyAPPEND,    keyARGS,    keyBYPRODUCTS,    keyCOMMAND,    keyCOMMAND_EXPAND_LISTS,    keyCOMMENT,    keyDEPENDS,    keyDEPFILE,    keyIMPLICIT_DEPENDS,    keyJOB_POOL,    keyMAIN_DEPENDENCY,    keyOUTPUT,    keyOUTPUTS,    keyPOST_BUILD,    keyPRE_BUILD,    keyPRE_LINK,    keySOURCE,    keyTARGET,    keyUSES_TERMINAL,    keyVERBATIM,    keyWORKING_DIRECTORY  };  for (std::string const& copy : args) {    if (keywords.count(copy)) {      if (copy == keySOURCE) {        doing = doing_source;      } else if (copy == keyCOMMAND) {        doing = doing_command;        // Save the current command before starting the next command.        if (!currentLine.empty()) {          commandLines.push_back(currentLine);          currentLine.clear();        }      } else if (copy == keyPRE_BUILD) {        cctype = cmTarget::PRE_BUILD;      } else if (copy == keyPRE_LINK) {        cctype = cmTarget::PRE_LINK;      } else if (copy == keyPOST_BUILD) {        cctype = cmTarget::POST_BUILD;      } else if (copy == keyVERBATIM) {        verbatim = true;      } else if (copy == keyAPPEND) {        append = true;      } else if (copy == keyUSES_TERMINAL) {        uses_terminal = true;      } else if (copy == keyCOMMAND_EXPAND_LISTS) {        command_expand_lists = true;      } else if (copy == keyTARGET) {        doing = doing_target;      } else if (copy == keyARGS) {        // Ignore this old keyword.      } else if (copy == keyDEPENDS) {        doing = doing_depends;      } else if (copy == keyOUTPUTS) {        doing = doing_outputs;      } else if (copy == keyOUTPUT) {        doing = doing_output;      } else if (copy == keyBYPRODUCTS) {        doing = doing_byproducts;      } else if (copy == keyWORKING_DIRECTORY) {        doing = doing_working_directory;      } else if (copy == keyMAIN_DEPENDENCY) {        doing = doing_main_dependency;      } else if (copy == keyIMPLICIT_DEPENDS) {        doing = doing_implicit_depends_lang;      } else if (copy == keyCOMMENT) {        doing = doing_comment;      } else if (copy == keyDEPFILE) {        doing = doing_depfile;        if (this->Makefile->GetGlobalGenerator()->GetName() != "Ninja") {          this->SetError("Option DEPFILE not supported by " +                         this->Makefile->GetGlobalGenerator()->GetName());          return false;        }      } else if (copy == keyJOB_POOL) {        doing = doing_job_pool;      }    } else {      std::string filename;      switch (doing) {        case doing_output:        case doing_outputs:        case doing_byproducts:          if (!cmSystemTools::FileIsFullPath(copy)) {            // This is an output to be generated, so it should be            // under the build tree.  CMake 2.4 placed this under the            // source tree.  However the only case that this change            // will break is when someone writes            //            //   add_custom_command(OUTPUT out.txt ...)            //            // and later references "${CMAKE_CURRENT_SOURCE_DIR}/out.txt".            // This is fairly obscure so we can wait for someone to            // complain.            filename = this->Makefile->GetCurrentBinaryDirectory();            filename += "/";          }          filename += copy;          cmSystemTools::ConvertToUnixSlashes(filename);          break;        case doing_source:        // We do not want to convert the argument to SOURCE because        // that option is only available for backward compatibility.        // Old-style use of this command may use the SOURCE==TARGET        // trick which we must preserve.  If we convert the source        // to a full path then it will no longer equal the target.        default:          break;      }      if (cmSystemTools::FileIsFullPath(filename)) {        filename = cmSystemTools::CollapseFullPath(filename);      }      switch (doing) {        case doing_depfile:          depfile = copy;          break;        case doing_job_pool:          job_pool = copy;          break;        case doing_working_directory:          working = copy;          break;        case doing_source:          source = copy;          break;        case doing_output:          output.push_back(filename);          break;        case doing_main_dependency:          main_dependency = copy;          break;        case doing_implicit_depends_lang:          implicit_depends_lang = copy;          doing = doing_implicit_depends_file;          break;        case doing_implicit_depends_file: {          // An implicit dependency starting point is also an          // explicit dependency.          std::string dep = copy;          cmSystemTools::ConvertToUnixSlashes(dep);          depends.push_back(dep);          // Add the implicit dependency language and file.          implicit_depends.emplace_back(implicit_depends_lang, dep);          // Switch back to looking for a language.          doing = doing_implicit_depends_lang;        } break;        case doing_command:          currentLine.push_back(copy);          break;        case doing_target:          target = copy;          break;        case doing_depends: {          std::string dep = copy;          cmSystemTools::ConvertToUnixSlashes(dep);          depends.push_back(std::move(dep));        } break;        case doing_outputs:          outputs.push_back(filename);          break;        case doing_byproducts:          byproducts.push_back(filename);          break;        case doing_comment:          comment_buffer = copy;          comment = comment_buffer.c_str();          break;        default:          this->SetError("Wrong syntax. Unknown type of argument.");          return false;      }    }  }  // Store the last command line finished.  if (!currentLine.empty()) {    commandLines.push_back(currentLine);    currentLine.clear();  }  // At this point we could complain about the lack of arguments.  For  // the moment, let's say that COMMAND, TARGET are always required.  if (output.empty() && target.empty()) {    this->SetError("Wrong syntax. A TARGET or OUTPUT must be specified.");    return false;  }  if (source.empty() && !target.empty() && !output.empty()) {    this->SetError(      "Wrong syntax. A TARGET and OUTPUT can not both be specified.");    return false;  }  if (append && output.empty()) {    this->SetError("given APPEND option with no OUTPUT.");    return false;  }  // Make sure the output names and locations are safe.  if (!this->CheckOutputs(output) || !this->CheckOutputs(outputs) ||      !this->CheckOutputs(byproducts)) {    return false;  }  // Check for an append request.  if (append) {    // Lookup an existing command.    if (cmSourceFile* sf =          this->Makefile->GetSourceFileWithOutput(output[0])) {      if (cmCustomCommand* cc = sf->GetCustomCommand()) {        cc->AppendCommands(commandLines);        cc->AppendDepends(depends);        cc->AppendImplicitDepends(implicit_depends);        return true;      }    }    // No command for this output exists.    std::ostringstream e;    e << "given APPEND option with output\n\"" << output[0]      << "\"\nwhich is not already a custom command output.";    this->SetError(e.str());    return false;  }  if (uses_terminal && !job_pool.empty()) {    this->SetError("JOB_POOL is shadowed by USES_TERMINAL.");    return false;  }  // Choose which mode of the command to use.  bool escapeOldStyle = !verbatim;  if (source.empty() && output.empty()) {    // Source is empty, use the target.    std::vector<std::string> no_depends;    this->Makefile->AddCustomCommandToTarget(      target, byproducts, no_depends, commandLines, cctype, comment,      working.c_str(), escapeOldStyle, uses_terminal, depfile, job_pool,      command_expand_lists);  } else if (target.empty()) {    // Target is empty, use the output.    this->Makefile->AddCustomCommandToOutput(      output, byproducts, depends, main_dependency, commandLines, comment,      working.c_str(), false, escapeOldStyle, uses_terminal,      command_expand_lists, depfile, job_pool);    // Add implicit dependency scanning requests if any were given.    if (!implicit_depends.empty()) {      bool okay = false;      if (cmSourceFile* sf =            this->Makefile->GetSourceFileWithOutput(output[0])) {        if (cmCustomCommand* cc = sf->GetCustomCommand()) {          okay = true;          cc->SetImplicitDepends(implicit_depends);        }      }      if (!okay) {        std::ostringstream e;        e << "could not locate source file with a custom command producing \""          << output[0] << "\" even though this command tried to create it!";        this->SetError(e.str());        return false;      }    }  } else if (!byproducts.empty()) {    this->SetError("BYPRODUCTS may not be specified with SOURCE signatures");    return false;  } else if (uses_terminal) {    this->SetError("USES_TERMINAL may not be used with SOURCE signatures");    return false;  } else {    bool issueMessage = true;    std::ostringstream e;    MessageType messageType = MessageType::AUTHOR_WARNING;    switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0050)) {      case cmPolicies::WARN:        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0050) << "\n";        break;      case cmPolicies::OLD:        issueMessage = false;        break;      case cmPolicies::REQUIRED_ALWAYS:      case cmPolicies::REQUIRED_IF_USED:      case cmPolicies::NEW:        messageType = MessageType::FATAL_ERROR;        break;    }    if (issueMessage) {      e << "The SOURCE signatures of add_custom_command are no longer "           "supported.";      this->Makefile->IssueMessage(messageType, e.str());      if (messageType == MessageType::FATAL_ERROR) {        return false;      }    }    // Use the old-style mode for backward compatibility.    this->Makefile->AddCustomCommandOldStyle(target, outputs, depends, source,                                             commandLines, comment);  }  return true;}bool cmAddCustomCommandCommand::CheckOutputs(  const std::vector<std::string>& outputs){  for (std::string const& o : outputs) {    // Make sure the file will not be generated into the source    // directory during an out of source build.    if (!this->Makefile->CanIWriteThisFile(o)) {      std::string e = "attempted to have a file \"" + o +        "\" in a source directory as an output of custom command.";      this->SetError(e);      cmSystemTools::SetFatalErrorOccured();      return false;    }    // Make sure the output file name has no invalid characters.    std::string::size_type pos = o.find_first_of("#<>");    if (pos != std::string::npos) {      std::ostringstream msg;      msg << "called with OUTPUT containing a \"" << o[pos]          << "\".  This character is not allowed.";      this->SetError(msg.str());      return false;    }  }  return true;}
 |