| /*============================================================================ |
| CMake - Cross Platform Makefile Generator |
| Copyright 2000-2009 Kitware, Inc., Insight Software Consortium |
| |
| Distributed under the OSI-approved BSD License (the "License"); |
| see accompanying file Copyright.txt for details. |
| |
| This software is distributed WITHOUT ANY WARRANTY; without even the |
| implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the License for more information. |
| ============================================================================*/ |
| #include "cmAddCustomCommandCommand.h" |
| |
| #include "cmTarget.h" |
| |
| #include "cmSourceFile.h" |
| |
| // cmAddCustomCommandCommand |
| bool 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; |
| std::string comment_buffer; |
| const char* comment = 0; |
| std::vector<std::string> depends, outputs, output, byproducts; |
| bool verbatim = false; |
| bool append = false; |
| bool uses_terminal = 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_nothing |
| }; |
| |
| tdoing doing = doing_nothing; |
| |
| for (unsigned int j = 0; j < args.size(); ++j) { |
| std::string const& copy = args[j]; |
| |
| if (copy == "SOURCE") { |
| doing = doing_source; |
| } else if (copy == "COMMAND") { |
| doing = doing_command; |
| |
| // Save the current command before starting the next command. |
| if (!currentLine.empty()) { |
| commandLines.push_back(currentLine); |
| currentLine.clear(); |
| } |
| } else if (copy == "PRE_BUILD") { |
| cctype = cmTarget::PRE_BUILD; |
| } else if (copy == "PRE_LINK") { |
| cctype = cmTarget::PRE_LINK; |
| } else if (copy == "POST_BUILD") { |
| cctype = cmTarget::POST_BUILD; |
| } else if (copy == "VERBATIM") { |
| verbatim = true; |
| } else if (copy == "APPEND") { |
| append = true; |
| } else if (copy == "USES_TERMINAL") { |
| uses_terminal = true; |
| } else if (copy == "TARGET") { |
| doing = doing_target; |
| } else if (copy == "ARGS") { |
| // Ignore this old keyword. |
| } else if (copy == "DEPENDS") { |
| doing = doing_depends; |
| } else if (copy == "OUTPUTS") { |
| doing = doing_outputs; |
| } else if (copy == "OUTPUT") { |
| doing = doing_output; |
| } else if (copy == "BYPRODUCTS") { |
| doing = doing_byproducts; |
| } else if (copy == "WORKING_DIRECTORY") { |
| doing = doing_working_directory; |
| } else if (copy == "MAIN_DEPENDENCY") { |
| doing = doing_main_dependency; |
| } else if (copy == "IMPLICIT_DEPENDS") { |
| doing = doing_implicit_depends_lang; |
| } else if (copy == "COMMENT") { |
| doing = doing_comment; |
| } else { |
| std::string filename; |
| switch (doing) { |
| case doing_output: |
| case doing_outputs: |
| case doing_byproducts: |
| if (!cmSystemTools::FileIsFullPath(copy.c_str())) { |
| // 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.c_str())) { |
| filename = cmSystemTools::CollapseFullPath(filename); |
| } |
| switch (doing) { |
| 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. |
| cmCustomCommand::ImplicitDependsPair entry(implicit_depends_lang, |
| dep); |
| implicit_depends.push_back(entry); |
| |
| // 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(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 \"" << output[0] |
| << "\" which is not already a custom command output."; |
| this->SetError(e.str()); |
| return false; |
| } |
| |
| // Convert working directory to a full path. |
| if (!working.empty()) { |
| const char* build_dir = this->Makefile->GetCurrentBinaryDirectory(); |
| working = cmSystemTools::CollapseFullPath(working, build_dir); |
| } |
| |
| // 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); |
| } 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); |
| |
| // 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; |
| cmake::MessageType messageType = cmake::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 = cmake::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 == cmake::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::vector<std::string>::const_iterator o = outputs.begin(); |
| o != outputs.end(); ++o) { |
| // Make sure the file will not be generated into the source |
| // directory during an out of source build. |
| if (!this->Makefile->CanIWriteThisFile(o->c_str())) { |
| 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 != o->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; |
| } |