Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmTargetLinkLibrariesCommand.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 "cmTargetLinkLibrariesCommand.h"
4
5
#include <cassert>
6
#include <cstddef>
7
#include <memory>
8
#include <sstream>
9
#include <unordered_set>
10
#include <utility>
11
12
#include <cm/optional>
13
#include <cm/string_view>
14
15
#include "cmExecutionStatus.h"
16
#include "cmGeneratorExpression.h"
17
#include "cmGlobalGenerator.h"
18
#include "cmListFileCache.h"
19
#include "cmMakefile.h"
20
#include "cmMessageType.h"
21
#include "cmPolicies.h"
22
#include "cmState.h"
23
#include "cmStateTypes.h"
24
#include "cmStringAlgorithms.h"
25
#include "cmSystemTools.h"
26
#include "cmTarget.h"
27
#include "cmTargetLinkLibraryType.h"
28
29
namespace {
30
31
enum ProcessingState
32
{
33
  ProcessingLinkLibraries,
34
  ProcessingPlainLinkInterface,
35
  ProcessingKeywordLinkInterface,
36
  ProcessingPlainPublicInterface,
37
  ProcessingKeywordPublicInterface,
38
  ProcessingPlainPrivateInterface,
39
  ProcessingKeywordPrivateInterface
40
};
41
42
char const* LinkLibraryTypeNames[3] = { "general", "debug", "optimized" };
43
44
struct TLL
45
{
46
  cmMakefile& Makefile;
47
  cmTarget* Target;
48
  bool WarnRemoteInterface = false;
49
  bool RejectRemoteLinking = false;
50
  bool EncodeRemoteReference = false;
51
  std::string DirectoryId;
52
  std::unordered_set<std::string> Props;
53
54
  TLL(cmMakefile& mf, cmTarget* target);
55
  ~TLL();
56
57
  bool HandleLibrary(ProcessingState currentProcessingState,
58
                     std::string const& lib, cmTargetLinkLibraryType llt);
59
  void AppendProperty(std::string const& prop, std::string const& value);
60
  void AffectsProperty(std::string const& prop);
61
};
62
63
} // namespace
64
65
static void LinkLibraryTypeSpecifierWarning(cmMakefile& mf, int left,
66
                                            int right);
67
68
bool cmTargetLinkLibrariesCommand(std::vector<std::string> const& args,
69
                                  cmExecutionStatus& status)
70
0
{
71
  // Must have at least one argument.
72
0
  if (args.empty()) {
73
0
    status.SetError("called with incorrect number of arguments");
74
0
    return false;
75
0
  }
76
77
0
  cmMakefile& mf = status.GetMakefile();
78
79
  // Alias targets cannot be on the LHS of this command.
80
0
  if (mf.IsAlias(args[0])) {
81
0
    status.SetError("can not be used on an ALIAS target.");
82
0
    return false;
83
0
  }
84
85
  // Lookup the target for which libraries are specified.
86
0
  cmTarget* target = mf.GetGlobalGenerator()->FindTarget(args[0]);
87
0
  if (!target) {
88
0
    for (auto const& importedTarget : mf.GetOwnedImportedTargets()) {
89
0
      if (importedTarget->GetName() == args[0] &&
90
0
          !importedTarget->IsForeign()) {
91
0
        target = importedTarget.get();
92
0
        break;
93
0
      }
94
0
    }
95
0
  }
96
0
  if (!target) {
97
0
    mf.IssueMessage(MessageType::FATAL_ERROR,
98
0
                    cmStrCat("Cannot specify link libraries for target \"",
99
0
                             args[0],
100
0
                             "\" which is not built by this project."));
101
0
    cmSystemTools::SetFatalErrorOccurred();
102
0
    return true;
103
0
  }
104
105
0
  if (target->IsSymbolic()) {
106
0
    status.SetError("can not be used on a SYMBOLIC target.");
107
0
    return false;
108
0
  }
109
110
  // Having a UTILITY library on the LHS is a bug.
111
0
  if (target->GetType() == cmStateEnums::UTILITY) {
112
0
    mf.IssueMessage(
113
0
      MessageType::FATAL_ERROR,
114
0
      cmStrCat(
115
0
        "Utility target \"", target->GetName(),
116
0
        "\" must not be used as the target of a target_link_libraries call."));
117
0
    return false;
118
0
  }
119
120
  // But we might not have any libs after variable expansion.
121
0
  if (args.size() < 2) {
122
0
    return true;
123
0
  }
124
125
0
  TLL tll(mf, target);
126
127
  // Keep track of link configuration specifiers.
128
0
  cmTargetLinkLibraryType llt = GENERAL_LibraryType;
129
0
  bool haveLLT = false;
130
131
  // Start with primary linking and switch to link interface
132
  // specification if the keyword is encountered as the first argument.
133
0
  ProcessingState currentProcessingState = ProcessingLinkLibraries;
134
135
  // Accumulate consecutive non-keyword arguments into one entry in
136
  // order to handle unquoted generator expressions containing ';'.
137
0
  std::size_t genexNesting = 0;
138
0
  cm::optional<std::string> currentEntry;
139
0
  auto processCurrentEntry = [&]() -> bool {
140
    // FIXME: Warn about partial genex if genexNesting > 0?
141
0
    genexNesting = 0;
142
0
    if (currentEntry) {
143
0
      assert(!haveLLT);
144
0
      if (!tll.HandleLibrary(currentProcessingState, *currentEntry,
145
0
                             GENERAL_LibraryType)) {
146
0
        return false;
147
0
      }
148
0
      currentEntry = cm::nullopt;
149
0
    }
150
0
    return true;
151
0
  };
152
0
  auto extendCurrentEntry = [&currentEntry](std::string const& arg) {
153
0
    if (currentEntry) {
154
0
      currentEntry = cmStrCat(*currentEntry, ';', arg);
155
0
    } else {
156
0
      currentEntry = arg;
157
0
    }
158
0
  };
159
160
  // Keep this list in sync with the keyword dispatch below.
161
0
  static std::unordered_set<std::string> const keywords{
162
0
    "LINK_INTERFACE_LIBRARIES",
163
0
    "INTERFACE",
164
0
    "LINK_PUBLIC",
165
0
    "PUBLIC",
166
0
    "LINK_PRIVATE",
167
0
    "PRIVATE",
168
0
    "debug",
169
0
    "optimized",
170
0
    "general",
171
0
  };
172
173
  // Add libraries, note that there is an optional prefix
174
  // of debug and optimized that can be used.
175
0
  for (unsigned int i = 1; i < args.size(); ++i) {
176
0
    if (keywords.count(args[i])) {
177
      // A keyword argument terminates any accumulated partial genex.
178
0
      if (!processCurrentEntry()) {
179
0
        return false;
180
0
      }
181
182
      // Process this keyword argument.
183
0
      if (args[i] == "LINK_INTERFACE_LIBRARIES") {
184
0
        currentProcessingState = ProcessingPlainLinkInterface;
185
0
        if (i != 1) {
186
0
          mf.IssueMessage(
187
0
            MessageType::FATAL_ERROR,
188
0
            "The LINK_INTERFACE_LIBRARIES option must appear as the "
189
0
            "second argument, just after the target name.");
190
0
          return true;
191
0
        }
192
0
      } else if (args[i] == "INTERFACE") {
193
0
        if (i != 1 &&
194
0
            currentProcessingState != ProcessingKeywordPrivateInterface &&
195
0
            currentProcessingState != ProcessingKeywordPublicInterface &&
196
0
            currentProcessingState != ProcessingKeywordLinkInterface) {
197
0
          mf.IssueMessage(MessageType::FATAL_ERROR,
198
0
                          "The INTERFACE, PUBLIC or PRIVATE option must "
199
0
                          "appear as the second argument, just after the "
200
0
                          "target name.");
201
0
          return true;
202
0
        }
203
0
        currentProcessingState = ProcessingKeywordLinkInterface;
204
0
      } else if (args[i] == "LINK_PUBLIC") {
205
0
        if (i != 1 &&
206
0
            currentProcessingState != ProcessingPlainPrivateInterface &&
207
0
            currentProcessingState != ProcessingPlainPublicInterface) {
208
0
          mf.IssueMessage(
209
0
            MessageType::FATAL_ERROR,
210
0
            "The LINK_PUBLIC or LINK_PRIVATE option must appear as the "
211
0
            "second argument, just after the target name.");
212
0
          return true;
213
0
        }
214
0
        currentProcessingState = ProcessingPlainPublicInterface;
215
0
      } else if (args[i] == "PUBLIC") {
216
0
        if (i != 1 &&
217
0
            currentProcessingState != ProcessingKeywordPrivateInterface &&
218
0
            currentProcessingState != ProcessingKeywordPublicInterface &&
219
0
            currentProcessingState != ProcessingKeywordLinkInterface) {
220
0
          mf.IssueMessage(MessageType::FATAL_ERROR,
221
0
                          "The INTERFACE, PUBLIC or PRIVATE option must "
222
0
                          "appear as the second argument, just after the "
223
0
                          "target name.");
224
0
          return true;
225
0
        }
226
0
        currentProcessingState = ProcessingKeywordPublicInterface;
227
0
      } else if (args[i] == "LINK_PRIVATE") {
228
0
        if (i != 1 &&
229
0
            currentProcessingState != ProcessingPlainPublicInterface &&
230
0
            currentProcessingState != ProcessingPlainPrivateInterface) {
231
0
          mf.IssueMessage(
232
0
            MessageType::FATAL_ERROR,
233
0
            "The LINK_PUBLIC or LINK_PRIVATE option must appear as the "
234
0
            "second argument, just after the target name.");
235
0
          return true;
236
0
        }
237
0
        currentProcessingState = ProcessingPlainPrivateInterface;
238
0
      } else if (args[i] == "PRIVATE") {
239
0
        if (i != 1 &&
240
0
            currentProcessingState != ProcessingKeywordPrivateInterface &&
241
0
            currentProcessingState != ProcessingKeywordPublicInterface &&
242
0
            currentProcessingState != ProcessingKeywordLinkInterface) {
243
0
          mf.IssueMessage(MessageType::FATAL_ERROR,
244
0
                          "The INTERFACE, PUBLIC or PRIVATE option must "
245
0
                          "appear as the second argument, just after the "
246
0
                          "target name.");
247
0
          return true;
248
0
        }
249
0
        currentProcessingState = ProcessingKeywordPrivateInterface;
250
0
      } else if (args[i] == "debug") {
251
0
        if (haveLLT) {
252
0
          LinkLibraryTypeSpecifierWarning(mf, llt, DEBUG_LibraryType);
253
0
        }
254
0
        llt = DEBUG_LibraryType;
255
0
        haveLLT = true;
256
0
      } else if (args[i] == "optimized") {
257
0
        if (haveLLT) {
258
0
          LinkLibraryTypeSpecifierWarning(mf, llt, OPTIMIZED_LibraryType);
259
0
        }
260
0
        llt = OPTIMIZED_LibraryType;
261
0
        haveLLT = true;
262
0
      } else if (args[i] == "general") {
263
0
        if (haveLLT) {
264
0
          LinkLibraryTypeSpecifierWarning(mf, llt, GENERAL_LibraryType);
265
0
        }
266
0
        llt = GENERAL_LibraryType;
267
0
        haveLLT = true;
268
0
      }
269
0
    } else if (haveLLT) {
270
      // The link type was specified by the previous argument.
271
0
      haveLLT = false;
272
0
      assert(!currentEntry);
273
0
      if (!tll.HandleLibrary(currentProcessingState, args[i], llt)) {
274
0
        return false;
275
0
      }
276
0
      llt = GENERAL_LibraryType;
277
0
    } else {
278
      // Track the genex nesting level.
279
0
      {
280
0
        cm::string_view arg = args[i];
281
0
        for (std::string::size_type pos = 0; pos < arg.size(); ++pos) {
282
0
          cm::string_view cur = arg.substr(pos);
283
0
          if (cmHasLiteralPrefix(cur, "$<")) {
284
0
            ++genexNesting;
285
0
            ++pos;
286
0
          } else if (genexNesting > 0 && cmHasPrefix(cur, '>')) {
287
0
            --genexNesting;
288
0
          }
289
0
        }
290
0
      }
291
292
      // Accumulate this argument in the current entry.
293
0
      extendCurrentEntry(args[i]);
294
295
      // Process this entry if it does not end inside a genex.
296
0
      if (genexNesting == 0) {
297
0
        if (!processCurrentEntry()) {
298
0
          return false;
299
0
        }
300
0
      }
301
0
    }
302
0
  }
303
304
  // Process the last accumulated partial genex, if any.
305
0
  if (!processCurrentEntry()) {
306
0
    return false;
307
0
  }
308
309
  // Make sure the last argument was not a library type specifier.
310
0
  if (haveLLT) {
311
0
    mf.IssueMessage(MessageType::FATAL_ERROR,
312
0
                    cmStrCat("The \"", LinkLibraryTypeNames[llt],
313
0
                             "\" argument must be followed by a library."));
314
0
    cmSystemTools::SetFatalErrorOccurred();
315
0
  }
316
317
0
  return true;
318
0
}
319
320
static void LinkLibraryTypeSpecifierWarning(cmMakefile& mf, int left,
321
                                            int right)
322
0
{
323
0
  mf.IssueMessage(
324
0
    MessageType::AUTHOR_WARNING,
325
0
    cmStrCat(
326
0
      "Link library type specifier \"", LinkLibraryTypeNames[left],
327
0
      "\" is followed by specifier \"", LinkLibraryTypeNames[right],
328
0
      "\" instead of a library name.  The first specifier will be ignored."));
329
0
}
330
331
namespace {
332
333
TLL::TLL(cmMakefile& mf, cmTarget* target)
334
0
  : Makefile(mf)
335
0
  , Target(target)
336
0
{
337
0
  if (&this->Makefile != this->Target->GetMakefile()) {
338
    // The LHS target was created in another directory.
339
0
    switch (this->Makefile.GetPolicyStatus(cmPolicies::CMP0079)) {
340
0
      case cmPolicies::WARN:
341
0
        this->WarnRemoteInterface = true;
342
0
        CM_FALLTHROUGH;
343
0
      case cmPolicies::OLD:
344
0
        this->RejectRemoteLinking = true;
345
0
        break;
346
0
      case cmPolicies::NEW:
347
0
        this->EncodeRemoteReference = true;
348
0
        break;
349
0
    }
350
0
  }
351
0
  if (this->EncodeRemoteReference) {
352
0
    cmDirectoryId const dirId = this->Makefile.GetDirectoryId();
353
0
    this->DirectoryId = cmStrCat(CMAKE_DIRECTORY_ID_SEP, dirId.String);
354
0
  }
355
0
}
356
357
bool TLL::HandleLibrary(ProcessingState currentProcessingState,
358
                        std::string const& lib, cmTargetLinkLibraryType llt)
359
0
{
360
0
  if (this->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY &&
361
0
      currentProcessingState != ProcessingKeywordLinkInterface) {
362
0
    this->Makefile.IssueMessage(
363
0
      MessageType::FATAL_ERROR,
364
0
      "INTERFACE library can only be used with the INTERFACE keyword of "
365
0
      "target_link_libraries");
366
0
    return false;
367
0
  }
368
0
  if (this->Target->IsImported() &&
369
0
      currentProcessingState != ProcessingKeywordLinkInterface) {
370
0
    this->Makefile.IssueMessage(
371
0
      MessageType::FATAL_ERROR,
372
0
      "IMPORTED library can only be used with the INTERFACE keyword of "
373
0
      "target_link_libraries");
374
0
    return false;
375
0
  }
376
377
0
  cmTarget::TLLSignature sig =
378
0
    (currentProcessingState == ProcessingPlainPrivateInterface ||
379
0
     currentProcessingState == ProcessingPlainPublicInterface ||
380
0
     currentProcessingState == ProcessingKeywordPrivateInterface ||
381
0
     currentProcessingState == ProcessingKeywordPublicInterface ||
382
0
     currentProcessingState == ProcessingKeywordLinkInterface)
383
0
    ? cmTarget::KeywordTLLSignature
384
0
    : cmTarget::PlainTLLSignature;
385
0
  if (!this->Target->PushTLLCommandTrace(
386
0
        sig, this->Makefile.GetBacktrace().Top())) {
387
0
    std::ostringstream e;
388
    // If the sig is a keyword form and there is a conflict, the existing
389
    // form must be the plain form.
390
0
    char const* existingSig =
391
0
      (sig == cmTarget::KeywordTLLSignature ? "plain" : "keyword");
392
0
    e << "The " << existingSig
393
0
      << " signature for target_link_libraries has "
394
0
         "already been used with the target \""
395
0
      << this->Target->GetName()
396
0
      << "\".  All uses of target_link_libraries with a target must "
397
0
      << " be either all-keyword or all-plain.\n";
398
0
    this->Target->GetTllSignatureTraces(e,
399
0
                                        sig == cmTarget::KeywordTLLSignature
400
0
                                          ? cmTarget::PlainTLLSignature
401
0
                                          : cmTarget::KeywordTLLSignature);
402
0
    this->Makefile.IssueMessage(MessageType::FATAL_ERROR, e.str());
403
0
    return false;
404
0
  }
405
406
  // Handle normal case where the command was called with another keyword than
407
  // INTERFACE / LINK_INTERFACE_LIBRARIES or none at all. (The "LINK_LIBRARIES"
408
  // property of the target on the LHS shall be populated.)
409
0
  if (currentProcessingState != ProcessingKeywordLinkInterface &&
410
0
      currentProcessingState != ProcessingPlainLinkInterface) {
411
412
0
    if (this->RejectRemoteLinking) {
413
0
      this->Makefile.IssueMessage(
414
0
        MessageType::FATAL_ERROR,
415
0
        cmStrCat("Attempt to add link library \"", lib, "\" to target \"",
416
0
                 this->Target->GetName(),
417
0
                 "\" which is not built in this "
418
0
                 "directory.\nThis is allowed only when policy CMP0079 "
419
0
                 "is set to NEW."));
420
0
      return false;
421
0
    }
422
423
0
    cmTarget* tgt = this->Makefile.GetGlobalGenerator()->FindTarget(lib);
424
425
0
    if (tgt && (tgt->GetType() != cmStateEnums::STATIC_LIBRARY) &&
426
0
        (tgt->GetType() != cmStateEnums::SHARED_LIBRARY) &&
427
0
        (tgt->GetType() != cmStateEnums::UNKNOWN_LIBRARY) &&
428
0
        (tgt->GetType() != cmStateEnums::OBJECT_LIBRARY) &&
429
0
        (tgt->GetType() != cmStateEnums::INTERFACE_LIBRARY) &&
430
0
        !tgt->IsExecutableWithExports()) {
431
0
      this->Makefile.IssueMessage(
432
0
        MessageType::FATAL_ERROR,
433
0
        cmStrCat(
434
0
          "Target \"", lib, "\" of type ",
435
0
          cmState::GetTargetTypeName(tgt->GetType()),
436
0
          " may not be linked into another target. One may link only to "
437
0
          "INTERFACE, OBJECT, STATIC or SHARED libraries, or to "
438
0
          "executables with the ENABLE_EXPORTS property set."));
439
0
    }
440
441
0
    this->AffectsProperty("LINK_LIBRARIES");
442
0
    this->Target->AddLinkLibrary(this->Makefile, lib, llt);
443
0
  }
444
445
0
  if (this->WarnRemoteInterface) {
446
0
    this->Makefile.IssueMessage(
447
0
      MessageType::AUTHOR_WARNING,
448
0
      cmStrCat(
449
0
        cmPolicies::GetPolicyWarning(cmPolicies::CMP0079), "\nTarget\n  ",
450
0
        this->Target->GetName(),
451
0
        "\nis not created in this "
452
0
        "directory.  For compatibility with older versions of CMake, link "
453
0
        "library\n  ",
454
0
        lib,
455
0
        "\nwill be looked up in the directory in which "
456
0
        "the target was created rather than in this calling directory."));
457
0
  }
458
459
  // Handle (additional) case where the command was called with PRIVATE /
460
  // LINK_PRIVATE and stop its processing. (The "INTERFACE_LINK_LIBRARIES"
461
  // property of the target on the LHS shall only be populated if it is a
462
  // STATIC library.)
463
0
  if (currentProcessingState == ProcessingKeywordPrivateInterface ||
464
0
      currentProcessingState == ProcessingPlainPrivateInterface) {
465
0
    if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
466
0
        this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
467
      // TODO: Detect and no-op `$<COMPILE_ONLY>` genexes here.
468
0
      std::string configLib =
469
0
        this->Target->GetDebugGeneratorExpressions(lib, llt);
470
0
      if (cmGeneratorExpression::IsValidTargetName(lib) ||
471
0
          cmGeneratorExpression::Find(lib) != std::string::npos) {
472
0
        configLib = "$<LINK_ONLY:" + configLib + ">";
473
0
      }
474
0
      this->AppendProperty("INTERFACE_LINK_LIBRARIES", configLib);
475
0
    }
476
0
    return true;
477
0
  }
478
479
  // Handle general case where the command was called with another keyword than
480
  // PRIVATE / LINK_PRIVATE or none at all. (The "INTERFACE_LINK_LIBRARIES"
481
  // property of the target on the LHS shall be populated.)
482
0
  this->AppendProperty("INTERFACE_LINK_LIBRARIES",
483
0
                       this->Target->GetDebugGeneratorExpressions(lib, llt));
484
0
  return true;
485
0
}
486
487
void TLL::AppendProperty(std::string const& prop, std::string const& value)
488
0
{
489
0
  this->AffectsProperty(prop);
490
0
  this->Target->AppendProperty(prop, value, this->Makefile.GetBacktrace());
491
0
}
492
493
void TLL::AffectsProperty(std::string const& prop)
494
0
{
495
0
  if (!this->EncodeRemoteReference) {
496
0
    return;
497
0
  }
498
  // Add a wrapper to the expression to tell LookupLinkItem to look up
499
  // names in the caller's directory.
500
0
  if (this->Props.insert(prop).second) {
501
0
    this->Target->AppendProperty(prop, this->DirectoryId,
502
0
                                 this->Makefile.GetBacktrace());
503
0
  }
504
0
}
505
506
TLL::~TLL()
507
0
{
508
0
  for (std::string const& prop : this->Props) {
509
0
    this->Target->AppendProperty(prop, CMAKE_DIRECTORY_ID_SEP,
510
0
                                 this->Makefile.GetBacktrace());
511
0
  }
512
0
}
513
514
} // namespace