/* 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 #include #include #include #include #include #include #include #include "cmCustomCommand.h" #include "cmCustomCommandLines.h" #include "cmCustomCommandTypes.h" #include "cmExecutionStatus.h" #include "cmGeneratorExpression.h" #include "cmGlobalGenerator.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" bool cmAddCustomCommandCommand(std::vector const& args, cmExecutionStatus& status) { /* 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) { status.SetError("called with wrong number of arguments."); return false; } cmMakefile& mf = status.GetMakefile(); std::string source; std::string target; std::string main_dependency; std::string working; std::string depfile; std::string job_pool; std::string job_server_aware; std::string comment_buffer; const char* comment = nullptr; std::vector depends; std::vector outputs; std::vector output; std::vector byproducts; bool verbatim = false; bool append = false; bool uses_terminal = false; bool command_expand_lists = false; bool depends_explicit_only = mf.IsOn("CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY"); bool codegen = false; std::string implicit_depends_lang; cmImplicitDependsList implicit_depends; // Accumulate one command line at a time. cmCustomCommandLine currentLine; // Save all command lines. cmCustomCommandLines commandLines; cmCustomCommandType cctype = cmCustomCommandType::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_job_server_aware, 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(JOB_SERVER_AWARE); 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); MAKE_STATIC_KEYWORD(DEPENDS_EXPLICIT_ONLY); MAKE_STATIC_KEYWORD(CODEGEN); #undef MAKE_STATIC_KEYWORD static std::unordered_set 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, keyJOB_SERVER_AWARE, keyTARGET, keyUSES_TERMINAL, keyVERBATIM, keyWORKING_DIRECTORY, keyDEPENDS_EXPLICIT_ONLY, keyCODEGEN }; /* clang-format off */ static std::set const supportedTargetKeywords{ keyARGS, keyBYPRODUCTS, keyCOMMAND, keyCOMMAND_EXPAND_LISTS, keyCOMMENT, keyPOST_BUILD, keyPRE_BUILD, keyPRE_LINK, keyTARGET, keyUSES_TERMINAL, keyVERBATIM, keyWORKING_DIRECTORY }; /* clang-format on */ static std::set const supportedOutputKeywords{ keyAPPEND, keyARGS, keyBYPRODUCTS, keyCODEGEN, keyCOMMAND, keyCOMMAND_EXPAND_LISTS, keyCOMMENT, keyDEPENDS, keyDEPENDS_EXPLICIT_ONLY, keyDEPFILE, keyIMPLICIT_DEPENDS, keyJOB_POOL, keyJOB_SERVER_AWARE, keyMAIN_DEPENDENCY, keyOUTPUT, keyUSES_TERMINAL, keyVERBATIM, keyWORKING_DIRECTORY }; /* clang-format off */ static std::set const supportedAppendKeywords{ keyAPPEND, keyARGS, keyCOMMAND, keyCOMMENT, // Allowed but ignored keyDEPENDS, keyIMPLICIT_DEPENDS, keyMAIN_DEPENDENCY, // Allowed but ignored keyOUTPUT, keyWORKING_DIRECTORY // Allowed but ignored }; /* clang-format on */ std::set keywordsSeen; std::string const* keywordExpectingValue = nullptr; auto const cmp0175 = mf.GetPolicyStatus(cmPolicies::CMP0175); for (std::string const& copy : args) { if (keywords.count(copy)) { // Check if a preceding keyword expected a value but there wasn't one if (keywordExpectingValue) { std::string const msg = cmStrCat("Keyword ", *keywordExpectingValue, " requires a value, but none was given."); if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, '\n', cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } keywordExpectingValue = nullptr; keywordsSeen.insert(copy); if (copy == keySOURCE) { doing = doing_source; keywordExpectingValue = &keySOURCE; } 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 = cmCustomCommandType::PRE_BUILD; } else if (copy == keyPRE_LINK) { cctype = cmCustomCommandType::PRE_LINK; } else if (copy == keyPOST_BUILD) { cctype = cmCustomCommandType::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 == keyDEPENDS_EXPLICIT_ONLY) { depends_explicit_only = true; } else if (copy == keyCODEGEN) { codegen = true; } else if (copy == keyTARGET) { doing = doing_target; keywordExpectingValue = &keyTARGET; } 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; keywordExpectingValue = &keyOUTPUT; } else if (copy == keyBYPRODUCTS) { doing = doing_byproducts; } else if (copy == keyWORKING_DIRECTORY) { doing = doing_working_directory; keywordExpectingValue = &keyWORKING_DIRECTORY; } else if (copy == keyMAIN_DEPENDENCY) { doing = doing_main_dependency; keywordExpectingValue = &keyMAIN_DEPENDENCY; } else if (copy == keyIMPLICIT_DEPENDS) { doing = doing_implicit_depends_lang; } else if (copy == keyCOMMENT) { doing = doing_comment; keywordExpectingValue = &keyCOMMENT; } else if (copy == keyDEPFILE) { doing = doing_depfile; if (!mf.GetGlobalGenerator()->SupportsCustomCommandDepfile()) { status.SetError(cmStrCat("Option DEPFILE not supported by ", mf.GetGlobalGenerator()->GetName())); return false; } keywordExpectingValue = &keyDEPFILE; } else if (copy == keyJOB_POOL) { doing = doing_job_pool; keywordExpectingValue = &keyJOB_POOL; } else if (copy == keyJOB_SERVER_AWARE) { doing = doing_job_server_aware; keywordExpectingValue = &keyJOB_SERVER_AWARE; } } else { keywordExpectingValue = nullptr; // Value is being processed now std::string filename; switch (doing) { case doing_output: case doing_outputs: case doing_byproducts: if (!cmSystemTools::FileIsFullPath(copy) && cmGeneratorExpression::Find(copy) != 0) { // This is an output to be generated, so it should be // under the build tree. filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/'); } 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_job_server_aware: job_server_aware = 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; // Upfront path conversion is correct because Genex // are not supported. 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: depends.push_back(copy); break; case doing_outputs: outputs.push_back(filename); break; case doing_byproducts: byproducts.push_back(filename); break; case doing_comment: if (!comment_buffer.empty()) { std::string const msg = "COMMENT requires exactly one argument, but multiple values " "or COMMENT keywords have been given."; if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, '\n', cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } comment_buffer = copy; comment = comment_buffer.c_str(); break; default: status.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()) { status.SetError("Wrong syntax. A TARGET or OUTPUT must be specified."); return false; } if (source.empty() && !target.empty() && !output.empty()) { status.SetError( "Wrong syntax. A TARGET and OUTPUT can not both be specified."); return false; } if (append && output.empty()) { status.SetError("given APPEND option with no OUTPUT."); return false; } if (!implicit_depends.empty() && !depfile.empty() && mf.GetGlobalGenerator()->GetName() != "Ninja") { // Makefiles generators does not support both at the same time status.SetError("IMPLICIT_DEPENDS and DEPFILE can not both be specified."); return false; } if (codegen) { if (output.empty()) { status.SetError("CODEGEN requires at least 1 OUTPUT."); return false; } if (append) { status.SetError("CODEGEN may not be used with APPEND."); return false; } if (!implicit_depends.empty()) { status.SetError("CODEGEN is not compatible with IMPLICIT_DEPENDS."); return false; } if (mf.GetPolicyStatus(cmPolicies::CMP0171) != cmPolicies::NEW) { status.SetError("CODEGEN option requires policy CMP0171 be set to NEW!"); return false; } } // Check for an append request. if (append) { std::vector unsupportedKeywordsUsed; std::set_difference(keywordsSeen.begin(), keywordsSeen.end(), supportedAppendKeywords.begin(), supportedAppendKeywords.end(), std::back_inserter(unsupportedKeywordsUsed)); if (!unsupportedKeywordsUsed.empty()) { std::string const msg = cmJoin(unsupportedKeywordsUsed, ", "_s, "The following keywords are not supported when using " "APPEND with add_custom_command(OUTPUT): "_s); if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, ".\n", cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends, commandLines); return true; } if (uses_terminal && !job_pool.empty()) { status.SetError("JOB_POOL is shadowed by USES_TERMINAL."); return false; } // Choose which mode of the command to use. auto cc = cm::make_unique(); cc->SetByproducts(byproducts); cc->SetCommandLines(commandLines); cc->SetComment(comment); cc->SetWorkingDirectory(working.c_str()); cc->SetEscapeOldStyle(!verbatim); cc->SetUsesTerminal(uses_terminal); cc->SetDepfile(depfile); cc->SetJobPool(job_pool); cc->SetJobserverAware(cmIsOn(job_server_aware)); cc->SetCommandExpandLists(command_expand_lists); cc->SetDependsExplicitOnly(depends_explicit_only); if (source.empty() && output.empty()) { // Source is empty, use the target. if (commandLines.empty()) { std::string const msg = "At least one COMMAND must be given."; if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, '\n', cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } std::vector unsupportedKeywordsUsed; std::set_difference(keywordsSeen.begin(), keywordsSeen.end(), supportedTargetKeywords.begin(), supportedTargetKeywords.end(), std::back_inserter(unsupportedKeywordsUsed)); if (!unsupportedKeywordsUsed.empty()) { std::string const msg = cmJoin(unsupportedKeywordsUsed, ", "_s, "The following keywords are not supported when using " "add_custom_command(TARGET): "_s); if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, ".\n", cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } auto const prePostCount = keywordsSeen.count(keyPRE_BUILD) + keywordsSeen.count(keyPRE_LINK) + keywordsSeen.count(keyPOST_BUILD); if (prePostCount != 1) { std::string msg = "Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given."; if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { msg += " Assuming "; switch (cctype) { case cmCustomCommandType::PRE_BUILD: msg += "PRE_BUILD"; break; case cmCustomCommandType::PRE_LINK: msg += "PRE_LINK"; break; case cmCustomCommandType::POST_BUILD: msg += "POST_BUILD"; } mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, " to preserve backward compatibility.\n", cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } mf.AddCustomCommandToTarget(target, cctype, std::move(cc)); } else if (target.empty()) { // Target is empty, use the output. std::vector unsupportedKeywordsUsed; std::set_difference(keywordsSeen.begin(), keywordsSeen.end(), supportedOutputKeywords.begin(), supportedOutputKeywords.end(), std::back_inserter(unsupportedKeywordsUsed)); if (!unsupportedKeywordsUsed.empty()) { std::string const msg = cmJoin(unsupportedKeywordsUsed, ", "_s, "The following keywords are not supported when using " "add_custom_command(OUTPUT): "_s); if (cmp0175 == cmPolicies::NEW) { mf.IssueMessage(MessageType::FATAL_ERROR, msg); return false; } if (cmp0175 == cmPolicies::WARN) { mf.IssueMessage( MessageType::AUTHOR_WARNING, cmStrCat(msg, ".\n", cmPolicies::GetPolicyWarning(cmPolicies::CMP0175))); } } cc->SetOutputs(output); cc->SetMainDependency(main_dependency); cc->SetDepends(depends); cc->SetCodegen(codegen); cc->SetImplicitDepends(implicit_depends); mf.AddCustomCommandToOutput(std::move(cc)); } else { if (!byproducts.empty()) { status.SetError( "BYPRODUCTS may not be specified with SOURCE signatures"); return false; } if (uses_terminal) { status.SetError("USES_TERMINAL may not be used with SOURCE signatures"); return false; } bool issueMessage = true; std::ostringstream e; MessageType messageType = MessageType::AUTHOR_WARNING; switch (mf.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."; mf.IssueMessage(messageType, e.str()); if (messageType == MessageType::FATAL_ERROR) { return false; } } // Use the old-style mode for backward compatibility. mf.AddCustomCommandOldStyle(target, outputs, depends, source, commandLines, comment); } return true; }