1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmAddCustomTargetCommand.h"
9 #include "cmCustomCommand.h"
10 #include "cmCustomCommandLines.h"
11 #include "cmExecutionStatus.h"
12 #include "cmGeneratorExpression.h"
13 #include "cmGlobalGenerator.h"
14 #include "cmMakefile.h"
15 #include "cmMessageType.h"
16 #include "cmStateTypes.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
21 bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
22 cmExecutionStatus& status)
25 status.SetError("called with incorrect number of arguments");
29 cmMakefile& mf = status.GetMakefile();
30 std::string const& targetName = args[0];
32 // Check the target name.
33 if (targetName.find_first_of("/\\") != std::string::npos) {
34 status.SetError(cmStrCat("called with invalid target name \"", targetName,
35 "\". Target names may not contain a slash. "
36 "Use ADD_CUSTOM_COMMAND to generate files."));
40 // Accumulate one command line at a time.
41 cmCustomCommandLine currentLine;
43 // Save all command lines.
44 cmCustomCommandLines commandLines;
46 // Accumulate dependencies.
47 std::vector<std::string> depends;
48 std::vector<std::string> byproducts;
49 std::string working_directory;
50 bool verbatim = false;
51 bool uses_terminal = false;
52 bool command_expand_lists = false;
53 std::string comment_buffer;
54 const char* comment = nullptr;
55 std::vector<std::string> sources;
58 // Keep track of parser state.
64 doing_working_directory,
70 tdoing doing = doing_command;
72 // Look for the ALL option.
73 bool excludeFromAll = true;
74 unsigned int start = 1;
75 if (args.size() > 1) {
76 if (args[1] == "ALL") {
77 excludeFromAll = false;
82 // Parse the rest of the arguments.
83 for (unsigned int j = start; j < args.size(); ++j) {
84 std::string const& copy = args[j];
86 if (copy == "DEPENDS") {
87 doing = doing_depends;
88 } else if (copy == "BYPRODUCTS") {
89 doing = doing_byproducts;
90 } else if (copy == "WORKING_DIRECTORY") {
91 doing = doing_working_directory;
92 } else if (copy == "VERBATIM") {
93 doing = doing_nothing;
95 } else if (copy == "USES_TERMINAL") {
96 doing = doing_nothing;
98 } else if (copy == "COMMAND_EXPAND_LISTS") {
99 doing = doing_nothing;
100 command_expand_lists = true;
101 } else if (copy == "COMMENT") {
102 doing = doing_comment;
103 } else if (copy == "JOB_POOL") {
104 doing = doing_job_pool;
105 } else if (copy == "COMMAND") {
106 doing = doing_command;
108 // Save the current command before starting the next command.
109 if (!currentLine.empty()) {
110 commandLines.push_back(currentLine);
113 } else if (copy == "SOURCES") {
114 doing = doing_source;
117 case doing_working_directory:
118 working_directory = copy;
121 currentLine.push_back(copy);
123 case doing_byproducts: {
124 std::string filename;
125 if (!cmSystemTools::FileIsFullPath(copy) &&
126 cmGeneratorExpression::Find(copy) != 0) {
127 filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/');
130 cmSystemTools::ConvertToUnixSlashes(filename);
131 if (cmSystemTools::FileIsFullPath(filename)) {
132 filename = cmSystemTools::CollapseFullPath(filename);
134 byproducts.push_back(filename);
136 case doing_depends: {
137 std::string dep = copy;
138 cmSystemTools::ConvertToUnixSlashes(dep);
139 depends.push_back(std::move(dep));
142 comment_buffer = copy;
143 comment = comment_buffer.c_str();
146 sources.push_back(copy);
152 status.SetError("Wrong syntax. Unknown type of argument.");
158 std::string::size_type pos = targetName.find_first_of("#<>");
159 if (pos != std::string::npos) {
160 status.SetError(cmStrCat("called with target name containing a \"",
162 "\". This character is not allowed."));
166 // Some requirements on custom target names already exist
167 // and have been checked at this point.
168 // The following restrictions overlap but depend on policy CMP0037.
169 bool nameOk = cmGeneratorExpression::IsValidTargetName(targetName) &&
170 !cmGlobalGenerator::IsReservedTarget(targetName);
172 nameOk = targetName.find(':') == std::string::npos;
174 if (!nameOk && !mf.CheckCMP0037(targetName, cmStateEnums::UTILITY)) {
178 // Store the last command line finished.
179 if (!currentLine.empty()) {
180 commandLines.push_back(currentLine);
184 // Enforce name uniqueness.
187 if (!mf.EnforceUniqueName(targetName, msg, true)) {
188 status.SetError(msg);
193 if (commandLines.empty() && !byproducts.empty()) {
194 mf.IssueMessage(MessageType::FATAL_ERROR,
195 "BYPRODUCTS may not be specified without any COMMAND");
198 if (commandLines.empty() && uses_terminal) {
199 mf.IssueMessage(MessageType::FATAL_ERROR,
200 "USES_TERMINAL may not be specified without any COMMAND");
203 if (commandLines.empty() && command_expand_lists) {
205 MessageType::FATAL_ERROR,
206 "COMMAND_EXPAND_LISTS may not be specified without any COMMAND");
210 if (uses_terminal && !job_pool.empty()) {
211 status.SetError("JOB_POOL is shadowed by USES_TERMINAL.");
215 // Add the utility target to the makefile.
216 auto cc = cm::make_unique<cmCustomCommand>();
217 cc->SetWorkingDirectory(working_directory.c_str());
218 cc->SetByproducts(byproducts);
219 cc->SetDepends(depends);
220 cc->SetCommandLines(commandLines);
221 cc->SetEscapeOldStyle(!verbatim);
222 cc->SetComment(comment);
223 cc->SetUsesTerminal(uses_terminal);
224 cc->SetCommandExpandLists(command_expand_lists);
225 cc->SetJobPool(job_pool);
227 mf.AddUtilityCommand(targetName, excludeFromAll, std::move(cc));
229 // Add additional user-specified source files to the target.
230 target->AddSources(sources);