| /*============================================================================ |
| 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; |
| bool verbatim = false; |
| bool append = 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_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 == "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 == "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: |
| 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->GetCurrentOutputDirectory(); |
| 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_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)) |
| { |
| 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. |
| cmOStringStream 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->GetCurrentOutputDirectory(); |
| working = cmSystemTools::CollapseFullPath(working.c_str(), 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, no_depends, |
| commandLines, cctype, |
| comment, working.c_str(), |
| escapeOldStyle); |
| } |
| else if(target.empty()) |
| { |
| // Target is empty, use the output. |
| this->Makefile->AddCustomCommandToOutput(output, depends, |
| main_dependency, |
| commandLines, comment, |
| working.c_str(), false, |
| escapeOldStyle); |
| |
| // 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) |
| { |
| cmOStringStream 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 |
| { |
| bool issueMessage = true; |
| cmOStringStream e; |
| cmake::MessageType messageType = cmake::AUTHOR_WARNING; |
| switch(this->Makefile->GetPolicyStatus(cmPolicies::CMP0050)) |
| { |
| case cmPolicies::WARN: |
| e << (this->Makefile->GetPolicies() |
| ->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) |
| { |
| cmOStringStream msg; |
| msg << "called with OUTPUT containing a \"" << (*o)[pos] |
| << "\". This character is not allowed."; |
| this->SetError(msg.str()); |
| return false; |
| } |
| } |
| return true; |
| } |