Coverage Report

Created: 2026-06-15 07:03

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