Coverage Report

Created: 2026-07-14 07:09

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