Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmAddCustomTargetCommand.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmAddCustomTargetCommand.h"
4
5
#include <utility>
6
7
#include <cm/memory>
8
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 "cmStringAlgorithms.h"
17
#include "cmSystemTools.h"
18
#include "cmTarget.h"
19
#include "cmValue.h"
20
21
bool cmAddCustomTargetCommand(std::vector<std::string> const& args,
22
                              cmExecutionStatus& status)
23
0
{
24
0
  if (args.empty()) {
25
0
    status.SetError("called with incorrect number of arguments");
26
0
    return false;
27
0
  }
28
29
0
  cmMakefile& mf = status.GetMakefile();
30
0
  std::string const& targetName = args[0];
31
32
  // Check the target name.
33
0
  if (targetName.find_first_of("/\\") != std::string::npos) {
34
0
    status.SetError(cmStrCat("called with invalid target name \"", targetName,
35
0
                             "\".  Target names may not contain a slash.  "
36
0
                             "Use ADD_CUSTOM_COMMAND to generate files."));
37
0
    return false;
38
0
  }
39
40
  // Accumulate one command line at a time.
41
0
  cmCustomCommandLine currentLine;
42
43
  // Save all command lines.
44
0
  cmCustomCommandLines commandLines;
45
46
  // Accumulate dependencies.
47
0
  std::vector<std::string> depends;
48
0
  std::vector<std::string> byproducts;
49
0
  std::string working_directory;
50
0
  bool verbatim = false;
51
0
  bool uses_terminal = false;
52
0
  bool command_expand_lists = false;
53
0
  std::string comment_buffer;
54
0
  char const* comment = nullptr;
55
0
  std::vector<std::string> sources;
56
0
  std::string job_pool;
57
0
  std::string job_server_aware;
58
59
  // Keep track of parser state.
60
0
  enum tdoing
61
0
  {
62
0
    doing_command,
63
0
    doing_depends,
64
0
    doing_byproducts,
65
0
    doing_working_directory,
66
0
    doing_comment,
67
0
    doing_source,
68
0
    doing_job_pool,
69
0
    doing_job_server_aware,
70
0
    doing_nothing
71
0
  };
72
0
  tdoing doing = doing_command;
73
74
  // Look for the ALL option.
75
0
  bool excludeFromAll = true;
76
0
  unsigned int start = 1;
77
0
  if (args.size() > 1) {
78
0
    if (args[1] == "ALL") {
79
0
      excludeFromAll = false;
80
0
      start = 2;
81
0
    }
82
0
  }
83
84
  // Parse the rest of the arguments.
85
0
  for (unsigned int j = start; j < args.size(); ++j) {
86
0
    std::string const& copy = args[j];
87
88
0
    if (copy == "DEPENDS") {
89
0
      doing = doing_depends;
90
0
    } else if (copy == "BYPRODUCTS") {
91
0
      doing = doing_byproducts;
92
0
    } else if (copy == "WORKING_DIRECTORY") {
93
0
      doing = doing_working_directory;
94
0
    } else if (copy == "VERBATIM") {
95
0
      doing = doing_nothing;
96
0
      verbatim = true;
97
0
    } else if (copy == "USES_TERMINAL") {
98
0
      doing = doing_nothing;
99
0
      uses_terminal = true;
100
0
    } else if (copy == "COMMAND_EXPAND_LISTS") {
101
0
      doing = doing_nothing;
102
0
      command_expand_lists = true;
103
0
    } else if (copy == "COMMENT") {
104
0
      doing = doing_comment;
105
0
    } else if (copy == "JOB_POOL") {
106
0
      doing = doing_job_pool;
107
0
    } else if (copy == "JOB_SERVER_AWARE") {
108
0
      doing = doing_job_server_aware;
109
0
    } else if (copy == "COMMAND") {
110
0
      doing = doing_command;
111
112
      // Save the current command before starting the next command.
113
0
      if (!currentLine.empty()) {
114
0
        commandLines.push_back(currentLine);
115
0
        currentLine.clear();
116
0
      }
117
0
    } else if (copy == "SOURCES") {
118
0
      doing = doing_source;
119
0
    } else {
120
0
      switch (doing) {
121
0
        case doing_working_directory:
122
0
          working_directory = copy;
123
0
          break;
124
0
        case doing_command:
125
0
          currentLine.push_back(copy);
126
0
          break;
127
0
        case doing_byproducts: {
128
0
          std::string filename;
129
0
          if (!cmSystemTools::FileIsFullPath(copy) &&
130
0
              cmGeneratorExpression::Find(copy) != 0) {
131
0
            filename = cmStrCat(mf.GetCurrentBinaryDirectory(), '/');
132
0
          }
133
0
          filename += copy;
134
0
          cmSystemTools::ConvertToUnixSlashes(filename);
135
0
          if (cmSystemTools::FileIsFullPath(filename)) {
136
0
            filename = cmSystemTools::CollapseFullPath(filename);
137
0
          }
138
0
          byproducts.push_back(filename);
139
0
        } break;
140
0
        case doing_depends: {
141
0
          std::string dep = copy;
142
0
          cmSystemTools::ConvertToUnixSlashes(dep);
143
0
          depends.push_back(std::move(dep));
144
0
        } break;
145
0
        case doing_comment:
146
0
          comment_buffer = copy;
147
0
          comment = comment_buffer.c_str();
148
0
          break;
149
0
        case doing_source:
150
0
          sources.push_back(copy);
151
0
          break;
152
0
        case doing_job_pool:
153
0
          job_pool = copy;
154
0
          break;
155
0
        case doing_job_server_aware:
156
0
          job_server_aware = copy;
157
0
          break;
158
0
        default:
159
0
          status.SetError("Wrong syntax. Unknown type of argument.");
160
0
          return false;
161
0
      }
162
0
    }
163
0
  }
164
165
0
  bool nameOk = cmGeneratorExpression::IsValidTargetName(targetName) &&
166
0
    !cmGlobalGenerator::IsReservedTarget(targetName) &&
167
0
    targetName.find(':') == std::string::npos;
168
0
  if (!nameOk) {
169
0
    mf.IssueInvalidTargetNameError(targetName);
170
0
    return false;
171
0
  }
172
173
  // Store the last command line finished.
174
0
  if (!currentLine.empty()) {
175
0
    commandLines.push_back(currentLine);
176
0
    currentLine.clear();
177
0
  }
178
179
  // Enforce name uniqueness.
180
0
  {
181
0
    std::string msg;
182
0
    if (!mf.EnforceUniqueName(targetName, msg, true)) {
183
0
      status.SetError(msg);
184
0
      return false;
185
0
    }
186
0
  }
187
188
0
  if (commandLines.empty() && !byproducts.empty()) {
189
0
    mf.IssueMessage(MessageType::FATAL_ERROR,
190
0
                    "BYPRODUCTS may not be specified without any COMMAND");
191
0
    return true;
192
0
  }
193
0
  if (commandLines.empty() && uses_terminal) {
194
0
    mf.IssueMessage(MessageType::FATAL_ERROR,
195
0
                    "USES_TERMINAL may not be specified without any COMMAND");
196
0
    return true;
197
0
  }
198
0
  if (commandLines.empty() && command_expand_lists) {
199
0
    mf.IssueMessage(
200
0
      MessageType::FATAL_ERROR,
201
0
      "COMMAND_EXPAND_LISTS may not be specified without any COMMAND");
202
0
    return true;
203
0
  }
204
205
0
  if (uses_terminal && !job_pool.empty()) {
206
0
    status.SetError("JOB_POOL is shadowed by USES_TERMINAL.");
207
0
    return false;
208
0
  }
209
210
  // Add the utility target to the makefile.
211
0
  auto cc = cm::make_unique<cmCustomCommand>();
212
0
  cc->SetWorkingDirectory(working_directory.c_str());
213
0
  cc->SetByproducts(byproducts);
214
0
  cc->SetDepends(depends);
215
0
  cc->SetCommandLines(commandLines);
216
0
  cc->SetEscapeOldStyle(!verbatim);
217
0
  cc->SetComment(comment);
218
0
  cc->SetUsesTerminal(uses_terminal);
219
0
  cc->SetCommandExpandLists(command_expand_lists);
220
0
  cc->SetJobPool(job_pool);
221
0
  cc->SetJobserverAware(cmIsOn(job_server_aware));
222
0
  cmTarget* target =
223
0
    mf.AddUtilityCommand(targetName, excludeFromAll, std::move(cc));
224
225
  // Add additional user-specified source files to the target.
226
0
  target->AddSources(sources);
227
228
0
  return true;
229
0
}