Coverage Report

Created: 2026-04-29 07:01

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