/*============================================================================ 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 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 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; } 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].c_str())) { 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].c_str() << "\" which is not already a custom command output."; this->SetError(e.str().c_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 no_depends; this->Makefile->AddCustomCommandToTarget(target.c_str(), 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.c_str(), 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].c_str())) { 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().c_str()); return false; } } } else { // Use the old-style mode for backward compatibility. this->Makefile->AddCustomCommandOldStyle(target.c_str(), outputs, depends, source.c_str(), commandLines, comment); } return true; } //---------------------------------------------------------------------------- bool cmAddCustomCommandCommand ::CheckOutputs(const std::vector& outputs) { for(std::vector::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.c_str()); 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().c_str()); return false; } } return true; }