cmAddCustomCommandCommand.cxx 14 KB
Newer Older
1 2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
#include "cmAddCustomCommandCommand.h"
4

5
#include <sstream>
6
#include <unordered_set>
7
#include <utility>
8

9 10
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
11
#include "cmExecutionStatus.h"
12 13
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
14
#include "cmMessageType.h"
15
#include "cmPolicies.h"
16
#include "cmSourceFile.h"
17 18
#include "cmSystemTools.h"
#include "cmTarget.h"
19

20 21
static bool cmAddCustomCommandCommandCheckOutputs(
  const std::vector<std::string>& outputs, cmExecutionStatus& status);
22

23 24
bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
                               cmExecutionStatus& status)
25
{
26
  /* Let's complain at the end of this function about the lack of a particular
27 28
     arg. For the moment, let's say that COMMAND, and either TARGET or SOURCE
     are required.
29
  */
30
  if (args.size() < 4) {
31
    status.SetError("called with wrong number of arguments.");
32 33
    return false;
  }
34

35
  cmMakefile& mf = status.GetMakefile();
36
  std::string source, target, main_dependency, working, depfile, job_pool;
37
  std::string comment_buffer;
Daniel Pfeifer's avatar
Daniel Pfeifer committed
38
  const char* comment = nullptr;
39
  std::vector<std::string> depends, outputs, output, byproducts;
40
  bool verbatim = false;
41
  bool append = false;
42
  bool uses_terminal = false;
43
  bool command_expand_lists = false;
44 45
  std::string implicit_depends_lang;
  cmCustomCommand::ImplicitDependsList implicit_depends;
46 47 48 49 50 51

  // Accumulate one command line at a time.
  cmCustomCommandLine currentLine;

  // Save all command lines.
  cmCustomCommandLines commandLines;
52

53
  cmTarget::CustomCommandType cctype = cmTarget::POST_BUILD;
Andy Cedilnik's avatar
Andy Cedilnik committed
54

55 56
  enum tdoing
  {
57 58 59 60
    doing_source,
    doing_command,
    doing_target,
    doing_depends,
61 62
    doing_implicit_depends_lang,
    doing_implicit_depends_file,
63 64
    doing_main_dependency,
    doing_output,
65
    doing_outputs,
66
    doing_byproducts,
67
    doing_comment,
68
    doing_working_directory,
69
    doing_depfile,
70
    doing_job_pool,
71 72 73 74
    doing_nothing
  };

  tdoing doing = doing_nothing;
75

76 77 78 79 80 81 82 83 84 85 86
#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);
87
  MAKE_STATIC_KEYWORD(JOB_POOL);
88 89 90 91 92 93 94 95 96 97 98 99
  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);
#undef MAKE_STATIC_KEYWORD
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
  static std::unordered_set<std::string> 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,
    keyTARGET,
    keyUSES_TERMINAL,
    keyVERBATIM,
    keyWORKING_DIRECTORY
  };
123

124
  for (std::string const& copy : args) {
125 126 127 128 129
    if (keywords.count(copy)) {
      if (copy == keySOURCE) {
        doing = doing_source;
      } else if (copy == keyCOMMAND) {
        doing = doing_command;
130

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
        // Save the current command before starting the next command.
        if (!currentLine.empty()) {
          commandLines.push_back(currentLine);
          currentLine.clear();
        }
      } else if (copy == keyPRE_BUILD) {
        cctype = cmTarget::PRE_BUILD;
      } else if (copy == keyPRE_LINK) {
        cctype = cmTarget::PRE_LINK;
      } else if (copy == keyPOST_BUILD) {
        cctype = cmTarget::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 == keyTARGET) {
        doing = doing_target;
      } 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;
      } else if (copy == keyBYPRODUCTS) {
        doing = doing_byproducts;
      } else if (copy == keyWORKING_DIRECTORY) {
        doing = doing_working_directory;
      } else if (copy == keyMAIN_DEPENDENCY) {
        doing = doing_main_dependency;
      } else if (copy == keyIMPLICIT_DEPENDS) {
        doing = doing_implicit_depends_lang;
      } else if (copy == keyCOMMENT) {
        doing = doing_comment;
      } else if (copy == keyDEPFILE) {
        doing = doing_depfile;
172 173 174
        if (mf.GetGlobalGenerator()->GetName() != "Ninja") {
          status.SetError("Option DEPFILE not supported by " +
                          mf.GetGlobalGenerator()->GetName());
175 176
          return false;
        }
177 178
      } else if (copy == keyJOB_POOL) {
        doing = doing_job_pool;
179
      }
180
    } else {
181
      std::string filename;
182
      switch (doing) {
183
        case doing_output:
184
        case doing_outputs:
185
        case doing_byproducts:
186
          if (!cmSystemTools::FileIsFullPath(copy)) {
187 188 189 190 191 192 193 194 195 196
            // 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.
197
            filename = mf.GetCurrentBinaryDirectory();
198
            filename += "/";
199
          }
200
          filename += copy;
201
          cmSystemTools::ConvertToUnixSlashes(filename);
202
          break;
203
        case doing_source:
204 205 206 207 208
        // 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.
209
        default:
210
          break;
211
      }
212

213
      if (cmSystemTools::FileIsFullPath(filename)) {
214
        filename = cmSystemTools::CollapseFullPath(filename);
215 216
      }
      switch (doing) {
217 218 219
        case doing_depfile:
          depfile = copy;
          break;
220 221 222
        case doing_job_pool:
          job_pool = copy;
          break;
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
        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);
245

246
          // Add the implicit dependency language and file.
247
          implicit_depends.emplace_back(implicit_depends_lang, dep);
248

249 250 251 252 253 254 255 256 257 258 259 260
          // 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);
261
          depends.push_back(std::move(dep));
262 263 264 265 266 267 268 269 270 271 272 273
        } 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:
274
          status.SetError("Wrong syntax. Unknown type of argument.");
275
          return false;
276
      }
277
    }
278
  }
279

280
  // Store the last command line finished.
281
  if (!currentLine.empty()) {
282 283
    commandLines.push_back(currentLine);
    currentLine.clear();
284
  }
285 286 287

  // At this point we could complain about the lack of arguments.  For
  // the moment, let's say that COMMAND, TARGET are always required.
288
  if (output.empty() && target.empty()) {
289
    status.SetError("Wrong syntax. A TARGET or OUTPUT must be specified.");
290
    return false;
291
  }
Bill Hoffman's avatar
Bill Hoffman committed
292

293
  if (source.empty() && !target.empty() && !output.empty()) {
294
    status.SetError(
Andy Cedilnik's avatar
Andy Cedilnik committed
295
      "Wrong syntax. A TARGET and OUTPUT can not both be specified.");
Bill Hoffman's avatar
Bill Hoffman committed
296
    return false;
297 298
  }
  if (append && output.empty()) {
299
    status.SetError("given APPEND option with no OUTPUT.");
300
    return false;
301
  }
302

303
  // Make sure the output names and locations are safe.
304 305 306
  if (!cmAddCustomCommandCommandCheckOutputs(output, status) ||
      !cmAddCustomCommandCommandCheckOutputs(outputs, status) ||
      !cmAddCustomCommandCommandCheckOutputs(byproducts, status)) {
307
    return false;
308
  }
309

310
  // Check for an append request.
311
  if (append) {
312
    // Lookup an existing command.
313
    if (cmSourceFile* sf = mf.GetSourceFileWithOutput(output[0])) {
314
      if (cmCustomCommand* cc = sf->GetCustomCommand()) {
315 316
        cc->AppendCommands(commandLines);
        cc->AppendDepends(depends);
317
        cc->AppendImplicitDepends(implicit_depends);
318 319
        return true;
      }
320
    }
321 322

    // No command for this output exists.
323
    std::ostringstream e;
324 325
    e << "given APPEND option with output\n\"" << output[0]
      << "\"\nwhich is not already a custom command output.";
326
    status.SetError(e.str());
327
    return false;
328
  }
329

330
  if (uses_terminal && !job_pool.empty()) {
331
    status.SetError("JOB_POOL is shadowed by USES_TERMINAL.");
332 333 334
    return false;
  }

335
  // Choose which mode of the command to use.
336
  bool escapeOldStyle = !verbatim;
337
  if (source.empty() && output.empty()) {
338 339
    // Source is empty, use the target.
    std::vector<std::string> no_depends;
340 341 342 343
    mf.AddCustomCommandToTarget(target, byproducts, no_depends, commandLines,
                                cctype, comment, working.c_str(),
                                escapeOldStyle, uses_terminal, depfile,
                                job_pool, command_expand_lists);
344
  } else if (target.empty()) {
345
    // Target is empty, use the output.
346 347 348 349
    mf.AddCustomCommandToOutput(output, byproducts, depends, main_dependency,
                                commandLines, comment, working.c_str(), false,
                                escapeOldStyle, uses_terminal,
                                command_expand_lists, depfile, job_pool);
350

351
    // Add implicit dependency scanning requests if any were given.
352
    if (!implicit_depends.empty()) {
353
      bool okay = false;
354
      if (cmSourceFile* sf = mf.GetSourceFileWithOutput(output[0])) {
355
        if (cmCustomCommand* cc = sf->GetCustomCommand()) {
356
          okay = true;
357
          cc->SetImplicitDepends(implicit_depends);
358
        }
359 360
      }
      if (!okay) {
361
        std::ostringstream e;
362 363
        e << "could not locate source file with a custom command producing \""
          << output[0] << "\" even though this command tried to create it!";
364
        status.SetError(e.str());
365 366
        return false;
      }
367
    }
368
  } else if (!byproducts.empty()) {
369
    status.SetError("BYPRODUCTS may not be specified with SOURCE signatures");
370
    return false;
371
  } else if (uses_terminal) {
372
    status.SetError("USES_TERMINAL may not be used with SOURCE signatures");
373
    return false;
374
  } else {
375
    bool issueMessage = true;
376
    std::ostringstream e;
377
    MessageType messageType = MessageType::AUTHOR_WARNING;
378
    switch (mf.GetPolicyStatus(cmPolicies::CMP0050)) {
379 380 381 382 383 384 385 386 387
      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:
388
        messageType = MessageType::FATAL_ERROR;
389
        break;
390 391
    }

392
    if (issueMessage) {
393 394
      e << "The SOURCE signatures of add_custom_command are no longer "
           "supported.";
395
      mf.IssueMessage(messageType, e.str());
396
      if (messageType == MessageType::FATAL_ERROR) {
397 398
        return false;
      }
399
    }
400

401
    // Use the old-style mode for backward compatibility.
402 403
    mf.AddCustomCommandOldStyle(target, outputs, depends, source, commandLines,
                                comment);
404
  }
405

406 407
  return true;
}
408

409 410
bool cmAddCustomCommandCommandCheckOutputs(
  const std::vector<std::string>& outputs, cmExecutionStatus& status)
411
{
412
  cmMakefile& mf = status.GetMakefile();
413
  for (std::string const& o : outputs) {
414 415
    // Make sure the file will not be generated into the source
    // directory during an out of source build.
416
    if (!mf.CanIWriteThisFile(o)) {
417
      std::string e = "attempted to have a file \"" + o +
418
        "\" in a source directory as an output of custom command.";
419
      status.SetError(e);
420 421
      cmSystemTools::SetFatalErrorOccured();
      return false;
422
    }
423 424

    // Make sure the output file name has no invalid characters.
425
    std::string::size_type pos = o.find_first_of("#<>");
426
    if (pos != std::string::npos) {
427
      std::ostringstream msg;
428
      msg << "called with OUTPUT containing a \"" << o[pos]
429
          << "\".  This character is not allowed.";
430
      status.SetError(msg.str());
431 432
      return false;
    }
433
  }
434 435
  return true;
}