Coverage Report

Created: 2026-07-30 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmCMakePkgConfigCommand.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
4
#include "cmCMakePkgConfigCommand.h"
5
6
#include <cstdio>
7
#include <memory>
8
#include <string>
9
#include <unordered_map>
10
#include <utility>
11
#include <vector>
12
13
#include <cm/filesystem>
14
#include <cm/optional>
15
#include <cm/string_view>
16
#include <cmext/string_view>
17
18
#include "cmsys/FStream.hxx"
19
20
#include "cmArgumentParser.h"
21
#include "cmArgumentParserTypes.h"
22
#include "cmExecutionStatus.h"
23
#include "cmList.h"
24
#include "cmMakefile.h"
25
#include "cmMessageType.h"
26
#include "cmPkgConfigParser.h"
27
#include "cmPkgConfigResolver.h"
28
#include "cmStateTypes.h"
29
#include "cmStringAlgorithms.h"
30
#include "cmSubcommandTable.h"
31
#include "cmSystemTools.h"
32
#include "cmTarget.h"
33
#include "cmTargetTypes.h"
34
#include "cmValue.h"
35
#include <cmllpkgc/llpkgc.h>
36
37
// IWYU wants this
38
namespace {
39
struct ExtractArguments;
40
struct PopulateArguments;
41
struct ImportArguments;
42
}
43
44
namespace {
45
46
cm::optional<std::string> GetPkgConfigBin(cmMakefile& mf)
47
0
{
48
0
  cm::optional<std::string> result;
49
50
0
  auto pkgcfg = mf.GetDefinition("CMAKE_PKG_CONFIG_BIN");
51
0
  if (pkgcfg.IsNOTFOUND()) {
52
0
    return result;
53
0
  }
54
55
0
  if (pkgcfg) {
56
0
    result = *pkgcfg;
57
0
    return result;
58
0
  }
59
60
0
  std::string path = cmSystemTools::FindProgram("pkgconf");
61
0
  if (path.empty()) {
62
0
    path = cmSystemTools::FindProgram("pkg-config");
63
0
    if (path.empty()) {
64
0
      mf.AddCacheDefinition("CMAKE_PKG_CONFIG_BIN", "pkg-config-NOTFOUND",
65
0
                            "Location of pkg-config or pkgconf binary",
66
0
                            cmStateEnums::FILEPATH);
67
0
      return result;
68
0
    }
69
0
  }
70
71
0
  mf.AddCacheDefinition("CMAKE_PKG_CONFIG_BIN", path,
72
0
                        "Location of pkg-config or pkgconf binary",
73
0
                        cmStateEnums::FILEPATH);
74
75
0
  result = std::move(path);
76
0
  return result;
77
0
}
78
79
std::vector<std::string> GetLocations(cmMakefile& mf, char const* cachevar,
80
                                      char const* envvar, char const* desc,
81
                                      char const* pcvar, bool need_pkgconf,
82
                                      std::vector<std::string> default_locs)
83
0
{
84
0
  auto def = mf.GetDefinition(cachevar);
85
0
  if (def) {
86
0
    return cmList(def);
87
0
  }
88
89
0
  std::string paths;
90
0
  if (cmSystemTools::GetEnv(envvar, paths)) {
91
0
    cmPkgConfigResolver::ReplaceSep(paths);
92
0
    mf.AddCacheDefinition(cachevar, paths, desc, cmStateEnums::STRING);
93
0
    return cmList(paths);
94
0
  }
95
96
0
  auto pkgcfg = GetPkgConfigBin(mf);
97
0
  if (!pkgcfg || (need_pkgconf && (pkgcfg->find("pkgconf") == pkgcfg->npos))) {
98
0
    mf.AddCacheDefinition(cachevar, cmList::to_string(default_locs), desc,
99
0
                          cmStateEnums::STRING);
100
0
    return default_locs;
101
0
  }
102
103
0
  std::string out;
104
0
  cmSystemTools::RunSingleCommand({ *pkgcfg, pcvar, "pkg-config" }, &out,
105
0
                                  nullptr, nullptr, nullptr,
106
0
                                  cmSystemTools::OUTPUT_NONE);
107
108
0
  cmPkgConfigResolver::ReplaceSep(out);
109
0
  out = cmTrimWhitespace(out);
110
0
  mf.AddCacheDefinition(cachevar, out, desc, cmStateEnums::STRING);
111
0
  return cmList(out);
112
0
}
113
114
std::vector<std::string> GetPcLibDirs(cmMakefile& mf)
115
0
{
116
0
  std::vector<std::string> default_locs = {
117
0
#ifndef _WIN32
118
0
    "/usr/lib/pkgconfig", "/usr/share/pkgconfig"
119
0
#endif
120
0
  };
121
0
  return GetLocations(mf, "CMAKE_PKG_CONFIG_PC_LIB_DIRS", "PKG_CONFIG_LIBDIR",
122
0
                      "Default search locations for package files",
123
0
                      "--variable=pc_path", false, std::move(default_locs));
124
0
}
125
126
std::vector<std::string> GetSysLibDirs(cmMakefile& mf)
127
0
{
128
0
  std::vector<std::string> default_locs = {
129
0
#ifndef _WIN32
130
0
    "/lib", "/usr/lib"
131
0
#endif
132
0
  };
133
0
  return GetLocations(
134
0
    mf, "CMAKE_PKG_CONFIG_SYS_LIB_DIRS", "PKG_CONFIG_SYSTEM_LIBRARY_PATH",
135
0
    "System library directories filtered by flag mangling",
136
0
    "--variable=pc_system_libdirs", true, std::move(default_locs));
137
0
}
138
139
std::vector<std::string> GetSysCflags(cmMakefile& mf)
140
0
{
141
0
  std::vector<std::string> default_locs = {
142
0
#ifndef _WIN32
143
0
    "/usr/include"
144
0
#endif
145
0
  };
146
0
  return GetLocations(
147
0
    mf, "CMAKE_PKG_CONFIG_SYS_INCLUDE_DIRS", "PKG_CONFIG_SYSTEM_INCLUDE_PATH",
148
0
    "System include directories filtered by flag mangling",
149
0
    "--variable=pc_system_includedirs", true, std::move(default_locs));
150
0
}
151
152
std::vector<std::string> GetPkgConfSysLibs(cmMakefile& mf)
153
0
{
154
0
  auto def = mf.GetDefinition("CMAKE_PKG_CONFIG_PKGCONF_LIB_DIRS");
155
0
  if (def) {
156
0
    return cmList(def);
157
0
  }
158
159
0
  std::string paths;
160
0
  if (!cmSystemTools::GetEnv("LIBRARY_PATH", paths)) {
161
0
    return {};
162
0
  }
163
164
0
  cmPkgConfigResolver::ReplaceSep(paths);
165
0
  mf.AddCacheDefinition("CMAKE_PKG_CONFIG_PKGCONF_LIB_DIRS", paths,
166
0
                        "Additional system library directories filtered by "
167
0
                        "flag mangling in PKGCONF mode",
168
0
                        cmStateEnums::STRING);
169
0
  return cmList(paths);
170
0
}
171
172
std::vector<std::string> GetPkgConfSysCflags(cmMakefile& mf)
173
0
{
174
0
  auto def = mf.GetDefinition("CMAKE_PKG_CONFIG_PKGCONF_INCLUDES");
175
0
  if (def) {
176
0
    return cmList(def);
177
0
  }
178
179
0
  std::string paths;
180
0
  auto get_and_append = [&](char const* var) {
181
0
    if (paths.empty()) {
182
0
      cmSystemTools::GetEnv(var, paths);
183
0
    } else {
184
0
      std::string tmp;
185
0
      cmSystemTools::GetEnv(var, tmp);
186
0
      if (!tmp.empty()) {
187
0
        paths += ";" + tmp;
188
0
      }
189
0
    }
190
0
  };
191
192
0
  get_and_append("CPATH");
193
0
  get_and_append("C_INCLUDE_PATH");
194
0
  get_and_append("CPLUS_INCLUDE_PATH");
195
0
  get_and_append("OBJC_INCLUDE_PATH");
196
197
#ifdef _WIN32
198
  get_and_append("INCLUDE");
199
#endif
200
201
0
  cmPkgConfigResolver::ReplaceSep(paths);
202
0
  mf.AddCacheDefinition("CMAKE_PKG_CONFIG_PKGCONF_INCLUDES", paths,
203
0
                        "Additional system include directories filtered by "
204
0
                        "flag mangling in PKGCONF mode",
205
0
                        cmStateEnums::STRING);
206
0
  return cmList(paths);
207
0
}
208
209
std::vector<std::string> GetPcPath(cmMakefile& mf)
210
0
{
211
0
  auto def = mf.GetDefinition("CMAKE_PKG_CONFIG_PC_PATH");
212
0
  if (def) {
213
0
    return cmList(def);
214
0
  }
215
216
0
  std::string pcpath;
217
0
  if (cmSystemTools::GetEnv("PKG_CONFIG_PATH", pcpath)) {
218
0
    auto result = cmSystemTools::SplitString(pcpath, cmPkgConfigResolver::Sep);
219
0
    mf.AddCacheDefinition(
220
0
      "CMAKE_PKG_CONFIG_PC_PATH", cmList::to_string(result),
221
0
      "Additional search locations for package files", cmStateEnums::STRING);
222
0
    return result;
223
0
  }
224
225
0
  mf.AddCacheDefinition("CMAKE_PKG_CONFIG_PC_PATH", "",
226
0
                        "Additional search locations for package files",
227
0
                        cmStateEnums::STRING);
228
0
  return {};
229
0
}
230
231
cm::optional<std::string> GetPath(cmMakefile& mf, char const* cachevar,
232
                                  char const* envvar, char const* desc)
233
0
{
234
0
  cm::optional<std::string> result;
235
236
0
  auto def = mf.GetDefinition(cachevar);
237
0
  if (def) {
238
0
    result = *def;
239
0
    return result;
240
0
  }
241
242
0
  std::string path;
243
0
  if (cmSystemTools::GetEnv(envvar, path)) {
244
0
    mf.AddCacheDefinition(cachevar, path, desc, cmStateEnums::FILEPATH);
245
0
    result = std::move(path);
246
0
    return result;
247
0
  }
248
249
0
  return result;
250
0
}
251
252
cm::optional<std::string> GetSysrootDir(cmMakefile& mf)
253
0
{
254
0
  return GetPath(mf, "CMAKE_PKG_CONFIG_SYSROOT_DIR", "PKG_CONFIG_SYSROOT_DIR",
255
0
                 "System root used for re-rooting package includes and "
256
0
                 "library directories");
257
0
}
258
259
cm::optional<std::string> GetTopBuildDir(cmMakefile& mf)
260
0
{
261
0
  return GetPath(mf, "CMAKE_PKG_CONFIG_TOP_BUILD_DIR",
262
0
                 "PKG_CONFIG_TOP_BUILD_DIR",
263
0
                 "Package file top_build_dir variable default value");
264
0
}
265
266
bool GetBool(cmMakefile& mf, char const* cachevar, char const* envvar,
267
             char const* desc)
268
0
{
269
0
  auto def = mf.GetDefinition(cachevar);
270
0
  if (def) {
271
0
    return def.IsOn();
272
0
  }
273
274
0
  if (cmSystemTools::HasEnv(envvar)) {
275
0
    mf.AddCacheDefinition(cachevar, "ON", desc, cmStateEnums::BOOL);
276
0
    return true;
277
0
  }
278
279
0
  return false;
280
0
}
281
282
bool GetDisableUninstalled(cmMakefile& mf)
283
0
{
284
0
  return GetBool(mf, "CMAKE_PKG_CONFIG_DISABLE_UNINSTALLED",
285
0
                 "PKG_CONFIG_DISABLE_UNINSTALLED",
286
0
                 "Disable search for `-uninstalled` (build tree) packages");
287
0
}
288
289
bool GetAllowSysLibs(cmMakefile& mf)
290
0
{
291
0
  return GetBool(mf, "CMAKE_PKG_CONFIG_ALLOW_SYS_LIBS",
292
0
                 "PKG_CONFIG_ALLOW_SYSTEM_LIBS",
293
0
                 "Allow system library directories during flag mangling");
294
0
}
295
296
bool GetAllowSysInclude(cmMakefile& mf)
297
0
{
298
0
  return GetBool(mf, "CMAKE_PKG_CONFIG_ALLOW_SYS_INCLUDES",
299
0
                 "PKG_CONFIG_ALLOW_SYSTEM_CFLAGS",
300
0
                 "Allow system include paths during flag manglging");
301
0
}
302
303
struct CommonArguments : ArgumentParser::ParseResult
304
{
305
  bool Required = false;
306
  bool Exact = false;
307
  bool Quiet = false;
308
309
  enum StrictnessType
310
  {
311
    STRICTNESS_STRICT,
312
    STRICTNESS_PERMISSIVE,
313
    STRICTNESS_BEST_EFFORT,
314
  };
315
316
  StrictnessType Strictness = STRICTNESS_PERMISSIVE;
317
  std::string StrictnessError;
318
319
  ArgumentParser::Continue SetStrictness(cm::string_view strictness)
320
0
  {
321
0
    if (strictness == "STRICT"_s) {
322
0
      Strictness = STRICTNESS_STRICT;
323
0
    } else if (strictness == "PERMISSIVE"_s) {
324
0
      Strictness = STRICTNESS_PERMISSIVE;
325
0
    } else if (strictness == "BEST_EFFORT"_s) {
326
0
      Strictness = STRICTNESS_BEST_EFFORT;
327
0
    } else {
328
0
      StrictnessError =
329
0
        cmStrCat("Invalid 'STRICTNESS' '", strictness,
330
0
                 "'; must be one of 'STRICT', 'PERMISSIVE', or 'BEST_EFFORT'");
331
0
    }
332
0
    return ArgumentParser::Continue::Yes;
333
0
  }
334
335
  enum EnvModeType
336
  {
337
    ENVMODE_FDO,
338
    ENVMODE_PKGCONF,
339
    ENVMODE_IGNORE,
340
  };
341
342
  EnvModeType EnvMode = ENVMODE_PKGCONF;
343
  std::string EnvModeError;
344
345
  ArgumentParser::Continue SetEnvMode(cm::string_view envMode)
346
0
  {
347
0
    if (envMode == "FDO"_s) {
348
0
      EnvMode = ENVMODE_FDO;
349
0
    } else if (envMode == "PKGCONF"_s) {
350
0
      EnvMode = ENVMODE_PKGCONF;
351
0
    } else if (envMode == "IGNORE"_s) {
352
0
      EnvMode = ENVMODE_IGNORE;
353
0
    } else {
354
0
      EnvModeError =
355
0
        cmStrCat("Invalid 'ENV_MODE' '", envMode,
356
0
                 "'; must be one of 'FDO', 'PKGCONF', or 'IGNORE'");
357
0
    }
358
0
    return ArgumentParser::Continue::Yes;
359
0
  }
360
361
  cm::optional<std::string> Package;
362
  cm::optional<std::string> Version;
363
  cm::optional<std::string> SysrootDir;
364
  cm::optional<std::string> TopBuildDir;
365
366
  cm::optional<bool> DisableUninstalled;
367
368
  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> PcPath;
369
  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> PcLibdir;
370
371
  bool CheckArgs(cmExecutionStatus& status) const
372
0
  {
373
374
0
    if (!Package) {
375
0
      status.SetError("A package name or absolute path must be specified");
376
0
      return false;
377
0
    }
378
379
0
    if (!StrictnessError.empty()) {
380
0
      status.SetError(StrictnessError);
381
0
      return false;
382
0
    }
383
384
0
    if (!EnvModeError.empty()) {
385
0
      status.SetError(EnvModeError);
386
0
      return false;
387
0
    }
388
389
0
    return true;
390
0
  }
391
};
392
393
#define BIND_COMMON(argtype)                                                  \
394
  (cmArgumentParser<argtype>{})                                               \
395
    .Bind(1, &argtype::Package)                                               \
396
    .Bind(2, &argtype::Version)                                               \
397
    .Bind("REQUIRED"_s, &argtype::Required)                                   \
398
    .Bind("EXACT"_s, &argtype::Exact)                                         \
399
    .Bind("QUIET"_s, &argtype::Quiet)                                         \
400
    .Bind("STRICTNESS"_s, &argtype::SetStrictness)                            \
401
    .Bind("ENV_MODE"_s, &argtype::SetEnvMode)                                 \
402
    .Bind("PC_SYSROOT_DIR"_s, &argtype::SysrootDir)                           \
403
    .Bind("TOP_BUILD_DIR"_s, &argtype::TopBuildDir)                           \
404
    .Bind("DISABLE_UNINSTALLED"_s, &argtype::DisableUninstalled)              \
405
    .Bind("PC_LIBDIR"_s, &argtype::PcLibdir)                                  \
406
    .Bind("PC_PATH"_s, &argtype::PcPath)
407
408
void CollectEnv(cmMakefile& mf, cmPkgConfigEnv& env,
409
                CommonArguments::EnvModeType mode)
410
0
{
411
0
  if (mode == CommonArguments::EnvModeType::ENVMODE_IGNORE) {
412
0
    return;
413
0
  }
414
415
0
  if (!env.Path) {
416
0
    env.Path = GetPcPath(mf);
417
0
  }
418
419
0
  if (!env.LibDirs) {
420
0
    env.LibDirs = GetPcLibDirs(mf);
421
0
  }
422
423
0
  if (!env.DisableUninstalled) {
424
0
    env.DisableUninstalled = GetDisableUninstalled(mf);
425
0
  }
426
427
0
  if (!env.SysrootDir) {
428
0
    env.SysrootDir = GetSysrootDir(mf);
429
0
  }
430
431
0
  if (!env.TopBuildDir) {
432
0
    env.TopBuildDir = GetTopBuildDir(mf);
433
0
  }
434
435
0
  env.AllowSysCflags = GetAllowSysInclude(mf);
436
0
  env.SysCflags = GetSysCflags(mf);
437
438
0
  env.AllowSysLibs = GetAllowSysLibs(mf);
439
0
  env.SysLibs = GetSysLibDirs(mf);
440
441
0
  if (mode == CommonArguments::EnvModeType::ENVMODE_FDO) {
442
0
    return;
443
0
  }
444
445
0
  *env.SysCflags += GetPkgConfSysCflags(mf);
446
0
  *env.SysLibs += GetPkgConfSysLibs(mf);
447
0
}
448
449
struct ImportEnv
450
{
451
  bool required;
452
  bool quiet;
453
  bool exact;
454
  CommonArguments::StrictnessType strictness;
455
};
456
457
struct ImportState
458
{
459
  cmExecutionStatus& status;
460
  bool err;
461
};
462
463
void warn_or_error(std::string const& err, ImportEnv imEnv, ImportState& state)
464
0
{
465
0
  if (imEnv.required) {
466
0
    state.status.SetError(err);
467
0
    cmSystemTools::SetFatalErrorOccurred();
468
0
  } else if (!imEnv.quiet) {
469
0
    state.status.GetMakefile().IssueMessage(MessageType::WARNING, err);
470
0
  }
471
0
  state.err = true;
472
0
}
473
474
cm::optional<cmPkgConfigDependencySpec> MakeVersionRequirement(
475
  std::string const& version)
476
0
{
477
0
  std::string requirement = cmTrimWhitespace(version);
478
0
  std::string expression = "cmake_pkg_config_version";
479
0
  expression += ' ';
480
481
0
  if (requirement.empty()) {
482
0
    expression += '=';
483
0
  } else {
484
0
    char const first = requirement.front();
485
0
    if (first != '<' && first != '>' && first != '=' && first != '!') {
486
0
      expression += "= ";
487
0
    }
488
0
    expression += requirement;
489
0
  }
490
491
0
  cmPkgConfigDependencyParser parser;
492
0
  auto err = parser.Parse(expression.data(), expression.size());
493
0
  if (err != PDEP_OK || parser.Finish() != PDEP_OK) {
494
0
    return {};
495
0
  }
496
497
0
  auto const& dependencies = parser.Dependencies();
498
0
  if (dependencies.size() != 1 ||
499
0
      dependencies.front().Name != "cmake_pkg_config_version") {
500
0
    return {};
501
0
  }
502
503
0
  return dependencies.front();
504
0
}
505
506
cm::optional<cmPkgConfigResolver> ReadPackage(std::string const& package,
507
                                              ImportEnv imEnv,
508
                                              cmPkgConfigEnv const& pcEnv,
509
                                              ImportState& state)
510
0
{
511
0
  cm::optional<cmPkgConfigResolver> result;
512
0
  cm::filesystem::path path{ package };
513
514
0
  if (path.extension() == ".pc") {
515
0
    if (!cmSystemTools::FileExists(path.string())) {
516
0
      return result;
517
0
    }
518
0
  } else {
519
520
0
    if (pcEnv.DisableUninstalled && !*pcEnv.DisableUninstalled) {
521
0
      auto uninstalled = path;
522
0
      uninstalled.concat("-uninstalled.pc");
523
0
      uninstalled =
524
0
        cmSystemTools::FindFile(uninstalled.string(), pcEnv.search, true);
525
0
      if (uninstalled.empty()) {
526
0
        path = cmSystemTools::FindFile(path.concat(".pc").string(),
527
0
                                       pcEnv.search, true);
528
0
        if (path.empty()) {
529
0
          return result;
530
0
        }
531
0
      } else {
532
0
        path = uninstalled;
533
0
      }
534
0
    } else {
535
0
      path = cmSystemTools::FindFile(path.concat(".pc").string(), pcEnv.search,
536
0
                                     true);
537
0
      if (path.empty()) {
538
0
        return result;
539
0
      }
540
0
    }
541
0
  }
542
543
0
  auto len = cmSystemTools::FileLength(path.string());
544
545
  // Windows requires this weird string -> c_str dance
546
0
  cmsys::ifstream ifs(path.string().c_str(), std::ios::binary);
547
548
0
  if (!ifs) {
549
0
    warn_or_error(cmStrCat("Could not open file '", path.string(), '\''),
550
0
                  imEnv, state);
551
0
    return result;
552
0
  }
553
554
0
  std::unique_ptr<char[]> buf(new char[len]);
555
0
  ifs.read(buf.get(), len);
556
557
  // Shouldn't have hit eof on previous read, should hit eof now
558
0
  if (ifs.fail() || ifs.eof() || ifs.get() != EOF) {
559
0
    warn_or_error(cmStrCat("Error while reading file '", path.string(), '\''),
560
0
                  imEnv, state);
561
0
    return result;
562
0
  }
563
564
0
  using StrictnessType = CommonArguments::StrictnessType;
565
566
0
  cmPkgConfigParser parser;
567
0
  auto err = parser.Finish(buf.get(), len);
568
569
0
  if (imEnv.strictness != StrictnessType::STRICTNESS_BEST_EFFORT &&
570
0
      err != PFCE_OK) {
571
0
    warn_or_error(cmStrCat("Parsing failed for file '", path.string(), '\''),
572
0
                  imEnv, state);
573
0
    return result;
574
0
  }
575
576
0
  std::string const pcFileDir = cmSystemTools::GetFilenamePath(
577
0
    cmSystemTools::CollapseFullPath(path.string()));
578
579
0
  if (imEnv.strictness == StrictnessType::STRICTNESS_STRICT) {
580
0
    result =
581
0
      cmPkgConfigResolver::ResolveStrict(std::move(parser), pcEnv, pcFileDir);
582
0
  } else if (imEnv.strictness == StrictnessType::STRICTNESS_PERMISSIVE) {
583
0
    result = cmPkgConfigResolver::ResolvePermissive(std::move(parser), pcEnv,
584
0
                                                    pcFileDir);
585
0
  } else {
586
0
    result = cmPkgConfigResolver::ResolveBestEffort(std::move(parser), pcEnv,
587
0
                                                    pcFileDir);
588
0
  }
589
590
0
  if (!result) {
591
0
    warn_or_error(
592
0
      cmStrCat("Resolution failed for file '", path.string(), '\''), imEnv,
593
0
      state);
594
0
  }
595
596
0
  return result;
597
0
}
598
599
cm::optional<cmPkgConfigResolver> ImportPackage(
600
  std::string const& package, cm::optional<std::string> version,
601
  ImportEnv imEnv, cmPkgConfigEnv const& pcEnv, ImportState& state)
602
0
{
603
0
  auto result = ReadPackage(package, imEnv, pcEnv, state);
604
605
0
  if (!result) {
606
0
    if (!state.err) {
607
0
      warn_or_error(cmStrCat("Could not find pkg-config: '", package, '\''),
608
0
                    imEnv, state);
609
0
    }
610
0
    return result;
611
0
  }
612
613
0
  if (imEnv.exact) {
614
0
    std::string ver;
615
616
0
    if (version) {
617
0
      auto req = MakeVersionRequirement(*version);
618
0
      if (!req) {
619
0
        warn_or_error(
620
0
          cmStrCat("Invalid version requirement '", *version, '\''), imEnv,
621
0
          state);
622
0
        return {};
623
0
      }
624
0
      ver = req->Version;
625
0
    }
626
627
0
    if (ver != result->Version()) {
628
0
      warn_or_error(
629
0
        cmStrCat("Package '", package, "' version '", result->Version(),
630
0
                 "' does not meet exact version requirement '", ver, '\''),
631
0
        imEnv, state);
632
0
      return {};
633
0
    }
634
635
0
  } else if (version) {
636
0
    auto req = MakeVersionRequirement(*version);
637
0
    if (!req) {
638
0
      warn_or_error(cmStrCat("Invalid version requirement '", *version, '\''),
639
0
                    imEnv, state);
640
0
      return {};
641
0
    }
642
0
    if (!cmPkgConfigResolver::CheckVersion(*req, result->Version())) {
643
0
      warn_or_error(
644
0
        cmStrCat("Package '", package, "' version '", result->Version(),
645
0
                 "' does not meet version requirement '", *version, '\''),
646
0
        imEnv, state);
647
0
      return {};
648
0
    }
649
0
  }
650
651
0
  return result;
652
0
}
653
654
struct pkgStackEntry
655
{
656
  cmPkgConfigDependencySpec Dependency;
657
  std::string parent;
658
};
659
660
cm::optional<cmPkgConfigResolver> ImportPackage(
661
  std::string const& package, std::vector<pkgStackEntry> const& reqs,
662
  ImportEnv imEnv, cmPkgConfigEnv const& pcEnv, ImportState& state)
663
0
{
664
0
  auto result = ReadPackage(package, imEnv, pcEnv, state);
665
666
0
  if (!result) {
667
0
    if (!state.err) {
668
0
      std::string req_str = cmStrCat('\'', reqs.begin()->parent, '\'');
669
0
      for (auto it = reqs.begin() + 1; it != reqs.end(); ++it) {
670
0
        req_str = cmStrCat(req_str, ", '", it->parent, '\'');
671
0
      }
672
0
      warn_or_error(cmStrCat("Could not find pkg-config: '", package,
673
0
                             "' required by: ", req_str),
674
0
                    imEnv, state);
675
0
    }
676
0
    return result;
677
0
  }
678
679
0
  auto ver = result->Version();
680
0
  for (auto const& req : reqs) {
681
682
0
    if (!cmPkgConfigResolver::CheckVersion(req.Dependency, ver)) {
683
0
      warn_or_error(
684
0
        cmStrCat("Package '", package, "' version '", ver,
685
0
                 "' does not meet version requirement '",
686
0
                 cmPkgConfigResolver::VersionReqString(req.Dependency),
687
0
                 "' of '", req.parent, '\''),
688
0
        imEnv, state);
689
0
      return {};
690
0
    }
691
0
  }
692
693
0
  return result;
694
0
}
695
696
cm::optional<std::pair<cmPkgConfigEnv, ImportEnv>> HandleCommon(
697
  CommonArguments& args, cmExecutionStatus& status)
698
0
{
699
700
0
  auto& mf = status.GetMakefile();
701
702
0
  if (!args.CheckArgs(status)) {
703
0
    return {};
704
0
  }
705
706
0
  cmPkgConfigEnv pcEnv;
707
708
0
  if (args.PcLibdir) {
709
0
    pcEnv.LibDirs = std::move(*args.PcLibdir);
710
0
  }
711
712
0
  if (args.PcPath) {
713
0
    pcEnv.Path = std::move(*args.PcPath);
714
0
  }
715
716
0
  pcEnv.DisableUninstalled = args.DisableUninstalled;
717
718
0
  if (args.SysrootDir) {
719
0
    pcEnv.SysrootDir = std::move(*args.SysrootDir);
720
0
  }
721
722
0
  if (args.TopBuildDir) {
723
0
    pcEnv.TopBuildDir = std::move(*args.TopBuildDir);
724
0
  }
725
726
0
  CollectEnv(mf, pcEnv, args.EnvMode);
727
728
0
  if (pcEnv.Path) {
729
0
    pcEnv.search = *pcEnv.Path;
730
0
    if (pcEnv.LibDirs) {
731
0
      pcEnv.search += *pcEnv.LibDirs;
732
0
    }
733
0
  } else if (pcEnv.LibDirs) {
734
0
    pcEnv.search = *pcEnv.LibDirs;
735
0
  }
736
737
0
  return std::pair<cmPkgConfigEnv, ImportEnv>{
738
0
    pcEnv, { args.Required, args.Quiet, args.Exact, args.Strictness }
739
0
  };
740
0
}
741
742
struct ExtractArguments : CommonArguments
743
{
744
  cm::optional<bool> AllowSystemIncludes;
745
  cm::optional<bool> AllowSystemLibs;
746
747
  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
748
    SystemIncludeDirs;
749
  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
750
    SystemLibraryDirs;
751
};
752
753
auto const ExtractParser =
754
  BIND_COMMON(ExtractArguments)
755
    .Bind("ALLOW_SYSTEM_INCLUDES"_s, &ExtractArguments::AllowSystemIncludes)
756
    .Bind("ALLOW_SYSTEM_LIBS"_s, &ExtractArguments::AllowSystemLibs)
757
    .Bind("SYSTEM_INCLUDE_DIRS"_s, &ExtractArguments::SystemIncludeDirs)
758
    .Bind("SYSTEM_LIBRARY_DIRS"_s, &ExtractArguments::SystemLibraryDirs);
759
760
bool HandleExtractCommand(std::vector<std::string> const& args,
761
                          cmExecutionStatus& status)
762
0
{
763
764
0
  std::vector<std::string> unparsed;
765
0
  auto parsedArgs = ExtractParser.Parse(args, &unparsed);
766
0
  auto maybeEnv = HandleCommon(parsedArgs, status);
767
768
0
  if (!maybeEnv) {
769
0
    return !parsedArgs.Required;
770
0
  }
771
0
  auto& pcEnv = maybeEnv->first;
772
0
  auto const& imEnv = maybeEnv->second;
773
0
  ImportState state{ status, false };
774
775
0
  auto maybePackage = ImportPackage(*parsedArgs.Package, parsedArgs.Version,
776
0
                                    imEnv, pcEnv, state);
777
0
  if (!maybePackage) {
778
0
    return !parsedArgs.Required;
779
0
  }
780
0
  auto& package = *maybePackage;
781
782
0
  if (parsedArgs.AllowSystemIncludes) {
783
0
    pcEnv.AllowSysCflags = *parsedArgs.AllowSystemIncludes;
784
0
  }
785
786
0
  if (parsedArgs.AllowSystemLibs) {
787
0
    pcEnv.AllowSysLibs = *parsedArgs.AllowSystemLibs;
788
0
  }
789
790
0
  if (parsedArgs.SystemIncludeDirs) {
791
0
    pcEnv.SysCflags = *parsedArgs.SystemIncludeDirs;
792
0
  }
793
794
0
  if (parsedArgs.SystemLibraryDirs) {
795
0
    pcEnv.SysLibs = *parsedArgs.SystemLibraryDirs;
796
0
  }
797
798
0
  auto& mf = status.GetMakefile();
799
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_NAME", package.Name());
800
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_DESCRIPTION", package.Description());
801
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_VERSION", package.Version());
802
803
0
  auto make_list = [&](char const* def,
804
0
                       std::vector<cmPkgConfigDependencySpec> const& deps) {
805
0
    std::vector<cm::string_view> vec;
806
0
    vec.reserve(deps.size());
807
808
0
    for (auto const& dep : deps) {
809
0
      vec.emplace_back(dep.Name);
810
0
    }
811
812
0
    mf.AddDefinition(def, cmList::to_string(vec));
813
0
  };
814
815
0
  make_list("CMAKE_PKG_CONFIG_CONFLICTS", package.Conflicts());
816
0
  make_list("CMAKE_PKG_CONFIG_PROVIDES", package.Provides());
817
0
  make_list("CMAKE_PKG_CONFIG_REQUIRES", package.Requires());
818
0
  make_list("CMAKE_PKG_CONFIG_REQUIRES_PRIVATE", package.Requires(true));
819
820
0
  auto cflags = package.Cflags();
821
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_CFLAGS", cflags.Flagline);
822
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_INCLUDES",
823
0
                   cmList::to_string(cflags.Includes));
824
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_COMPILE_OPTIONS",
825
0
                   cmList::to_string(cflags.CompileOptions));
826
827
0
  cflags = package.Cflags(true);
828
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_CFLAGS_PRIVATE", cflags.Flagline);
829
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_INCLUDES_PRIVATE",
830
0
                   cmList::to_string(cflags.Includes));
831
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_COMPILE_OPTIONS_PRIVATE",
832
0
                   cmList::to_string(cflags.CompileOptions));
833
834
0
  auto libs = package.Libs();
835
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LIBS", libs.Flagline);
836
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LIBDIRS",
837
0
                   cmList::to_string(libs.LibDirs));
838
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LIBNAMES",
839
0
                   cmList::to_string(libs.LibNames));
840
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LINK_OPTIONS",
841
0
                   cmList::to_string(libs.LinkOptions));
842
843
0
  libs = package.Libs(true);
844
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LIBS_PRIVATE", libs.Flagline);
845
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LIBDIRS_PRIVATE",
846
0
                   cmList::to_string(libs.LibDirs));
847
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LIBNAMES_PRIVATE",
848
0
                   cmList::to_string(libs.LibNames));
849
0
  mf.AddDefinition("CMAKE_PKG_CONFIG_LINK_OPTIONS_PRIVATE",
850
0
                   cmList::to_string(libs.LinkOptions));
851
852
0
  return true;
853
0
}
854
855
using pkgStack = std::unordered_map<std::string, std::vector<pkgStackEntry>>;
856
using pkgProviders = std::unordered_map<std::string, std::string>;
857
858
cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix,
859
                            cmPkgConfigResolver const& pkg,
860
                            pkgProviders const& providers, cmMakefile& mf)
861
0
{
862
0
  auto* tgt = mf.AddForeignTarget("pkgcfg", cmStrCat(prefix, name));
863
864
0
  tgt->AppendProperty("VERSION", pkg.Version());
865
866
0
  auto libs = pkg.Libs();
867
0
  for (auto const& flag : libs.LibNames) {
868
0
    tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", flag.substr(2));
869
0
  }
870
0
  for (auto const& flag : libs.LibDirs) {
871
0
    tgt->AppendProperty("INTERFACE_LINK_DIRECTORIES", flag.substr(2));
872
0
  }
873
0
  tgt->AppendProperty("INTERFACE_LINK_OPTIONS",
874
0
                      cmList::to_string(libs.LinkOptions));
875
876
0
  auto cflags = pkg.Cflags();
877
0
  for (auto const& flag : cflags.Includes) {
878
0
    tgt->AppendProperty("INTERFACE_INCLUDE_DIRECTORIES", flag.substr(2));
879
0
  }
880
0
  tgt->AppendProperty("INTERFACE_COMPILE_OPTIONS",
881
0
                      cmList::to_string(cflags.CompileOptions));
882
883
0
  for (auto const& dep : pkg.Requires()) {
884
0
    auto it = providers.find(dep.Name);
885
0
    if (it != providers.end()) {
886
0
      tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", it->second);
887
0
      continue;
888
0
    }
889
890
0
    tgt->AppendProperty("INTERFACE_LINK_LIBRARIES",
891
0
                        cmStrCat("@foreign_pkgcfg::", prefix, dep.Name));
892
0
  }
893
0
  return tgt;
894
0
}
895
896
bool CheckPackageDependencies(
897
  std::string const& name, std::string const& prefix,
898
  cmPkgConfigResolver const& pkg, pkgStack& inStack,
899
  std::unordered_map<std::string, cmPkgConfigResolver> const& outStack,
900
  pkgProviders const& providers, ImportEnv imEnv, ImportState& state)
901
0
{
902
0
  for (auto const& dep : pkg.Requires()) {
903
0
    auto prov_it = providers.find(dep.Name);
904
0
    if (prov_it != providers.end()) {
905
0
      continue;
906
0
    }
907
908
0
    auto* tgt = state.status.GetMakefile().FindTargetToUse(
909
0
      cmStrCat("@foreign_pkgcfg::", prefix, dep.Name),
910
0
      cm::TargetDomain::FOREIGN);
911
0
    if (tgt) {
912
0
      auto ver = tgt->GetProperty("VERSION");
913
0
      if (!cmPkgConfigResolver::CheckVersion(dep, *ver)) {
914
0
        warn_or_error(cmStrCat("Package '", dep.Name, "' version '", *ver,
915
0
                               "' does not meet version requirement '",
916
0
                               cmPkgConfigResolver::VersionReqString(dep),
917
0
                               "' of '", name, '\''),
918
0
                      imEnv, state);
919
0
        return false;
920
0
      }
921
0
      continue;
922
0
    }
923
924
0
    auto it = outStack.find(dep.Name);
925
0
    if (it != outStack.end()) {
926
0
      auto ver = it->second.Version();
927
0
      if (!cmPkgConfigResolver::CheckVersion(dep, ver)) {
928
0
        warn_or_error(cmStrCat("Package '", dep.Name, "' version '", ver,
929
0
                               "' does not meet version requirement '",
930
0
                               cmPkgConfigResolver::VersionReqString(dep),
931
0
                               "' of '", name, '\''),
932
0
                      imEnv, state);
933
0
        return false;
934
0
      }
935
0
      continue;
936
0
    }
937
938
0
    inStack[dep.Name].emplace_back(pkgStackEntry{ dep, name });
939
0
  }
940
941
0
  return true;
942
0
}
943
944
struct PopulateArguments : CommonArguments
945
{
946
  cm::optional<std::string> Prefix;
947
  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Providers;
948
};
949
950
#define BIND_POPULATE(argtype)                                                \
951
  BIND_COMMON(argtype)                                                        \
952
    .Bind("PREFIX"_s, &argtype::Prefix)                                       \
953
    .Bind("BIND_PC_REQUIRES"_s, &argtype::Providers)
954
955
auto const PopulateParser = BIND_POPULATE(PopulateArguments);
956
957
std::pair<bool, bool> PopulatePCTarget(PopulateArguments& args,
958
                                       cmExecutionStatus& status)
959
0
{
960
961
0
  std::string prefix = args.Prefix ? cmStrCat(*args.Prefix, "_"_s) : "";
962
963
0
  auto& mf = status.GetMakefile();
964
0
  auto maybeEnv = HandleCommon(args, status);
965
966
0
  if (!maybeEnv) {
967
0
    return { !args.Required, false };
968
0
  }
969
0
  auto& pcEnv = maybeEnv->first;
970
971
0
  pcEnv.AllowSysCflags = true;
972
0
  pcEnv.AllowSysLibs = true;
973
974
0
  auto const& imEnv = maybeEnv->second;
975
0
  ImportState state{ status, false };
976
977
0
  pkgProviders providers;
978
0
  if (args.Providers) {
979
0
    for (auto const& provider_str : *args.Providers) {
980
0
      auto assignment = provider_str.find('=');
981
0
      if (assignment != std::string::npos) {
982
0
        providers.emplace(provider_str.substr(0, assignment),
983
0
                          provider_str.substr(assignment + 1));
984
0
      } else {
985
0
        state.status.SetError(cmStrCat(
986
0
          "No '=' found in BIND_PC_REQUIRES argument '", provider_str, '\''));
987
0
        cmSystemTools::SetFatalErrorOccurred();
988
0
        return { false, false };
989
0
      }
990
0
    }
991
0
  }
992
993
0
  pkgStack inStack;
994
0
  std::unordered_map<std::string, cmPkgConfigResolver> outStack;
995
996
0
  auto maybePackage =
997
0
    ImportPackage(*args.Package, args.Version, imEnv, pcEnv, state);
998
0
  if (!maybePackage) {
999
0
    return { !args.Required, false };
1000
0
  }
1001
0
  ImportEnv depEnv = imEnv;
1002
0
  depEnv.exact = false;
1003
1004
0
  if (!CheckPackageDependencies(*args.Package, prefix, *maybePackage, inStack,
1005
0
                                outStack, providers, depEnv, state)) {
1006
0
    return { !args.Required, false };
1007
0
  }
1008
0
  outStack.emplace(*args.Package, std::move(*maybePackage));
1009
1010
0
  while (!inStack.empty()) {
1011
0
    auto name = inStack.begin()->first;
1012
0
    auto reqs = inStack.begin()->second;
1013
0
    maybePackage = ImportPackage(name, reqs, depEnv, pcEnv, state);
1014
0
    if (!maybePackage) {
1015
0
      return { !args.Required, false };
1016
0
    }
1017
0
    if (!CheckPackageDependencies(name, prefix, *maybePackage, inStack,
1018
0
                                  outStack, providers, depEnv, state)) {
1019
0
      return { !args.Required, false };
1020
0
    }
1021
0
    inStack.erase(name);
1022
0
    outStack.emplace(std::move(name), std::move(*maybePackage));
1023
0
  }
1024
1025
0
  for (auto& entry : outStack) {
1026
0
    CreateCMakeTarget(entry.first, prefix, entry.second, providers, mf);
1027
0
  }
1028
1029
0
  return { true, true };
1030
0
}
1031
1032
bool HandlePopulateCommand(std::vector<std::string> const& args,
1033
                           cmExecutionStatus& status)
1034
0
{
1035
0
  std::vector<std::string> unparsed;
1036
0
  auto parsedArgs = PopulateParser.Parse(args, &unparsed);
1037
1038
0
  std::string prefix =
1039
0
    parsedArgs.Prefix ? cmStrCat(*parsedArgs.Prefix, "_"_s) : "";
1040
1041
0
  auto foreign_name =
1042
0
    cmStrCat("@foreign_pkgcfg::", prefix, *parsedArgs.Package);
1043
0
  auto found_var = cmStrCat("PKGCONFIG_", *parsedArgs.Package, "_FOUND");
1044
1045
0
  auto& mf = status.GetMakefile();
1046
1047
0
  if (mf.FindTargetToUse(foreign_name, cm::TargetDomain::FOREIGN)) {
1048
0
    mf.AddDefinition(found_var, "TRUE");
1049
0
    return true;
1050
0
  }
1051
1052
0
  auto result = PopulatePCTarget(parsedArgs, status);
1053
0
  mf.AddDefinition(found_var, result.second ? "TRUE" : "FALSE");
1054
0
  return result.first;
1055
0
}
1056
1057
struct ImportArguments : PopulateArguments
1058
{
1059
  cm::optional<std::string> Name;
1060
};
1061
1062
auto const ImportParser =
1063
  BIND_POPULATE(ImportArguments).Bind("NAME"_s, &ImportArguments::Name);
1064
1065
bool HandleImportCommand(std::vector<std::string> const& args,
1066
                         cmExecutionStatus& status)
1067
0
{
1068
0
  std::vector<std::string> unparsed;
1069
0
  auto parsedArgs = ImportParser.Parse(args, &unparsed);
1070
1071
0
  std::string prefix =
1072
0
    parsedArgs.Prefix ? cmStrCat(*parsedArgs.Prefix, "_"_s) : "";
1073
1074
0
  auto foreign_name =
1075
0
    cmStrCat("@foreign_pkgcfg::", prefix, *parsedArgs.Package);
1076
0
  auto local_name =
1077
0
    cmStrCat("PkgConfig::", parsedArgs.Name.value_or(*parsedArgs.Package));
1078
0
  auto found_var = cmStrCat("PKGCONFIG_", *parsedArgs.Package, "_FOUND");
1079
1080
0
  auto& mf = status.GetMakefile();
1081
1082
0
  if (mf.FindTargetToUse(local_name)) {
1083
0
    mf.AddDefinition(found_var, "TRUE");
1084
0
    return true;
1085
0
  }
1086
1087
0
  if (!mf.FindTargetToUse(foreign_name, cm::TargetDomain::FOREIGN)) {
1088
0
    auto result = PopulatePCTarget(parsedArgs, status);
1089
0
    if (!result.second) {
1090
0
      mf.AddDefinition(found_var, "FALSE");
1091
0
      return result.first;
1092
0
    }
1093
0
  }
1094
1095
0
  mf.AddDefinition(found_var, "TRUE");
1096
0
  auto* tgt =
1097
0
    mf.AddImportedTarget(local_name, cm::TargetType::INTERFACE_LIBRARY,
1098
0
                         cm::ImportedTargetScope::Local);
1099
0
  tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", foreign_name);
1100
0
  return true;
1101
0
}
1102
1103
} // namespace
1104
1105
bool cmCMakePkgConfigCommand(std::vector<std::string> const& args,
1106
                             cmExecutionStatus& status)
1107
0
{
1108
0
  if (args.size() < 2) {
1109
0
    status.SetError("must be called with at least two arguments.");
1110
0
    return false;
1111
0
  }
1112
1113
0
  static cmSubcommandTable const subcommand{
1114
0
    { "EXTRACT"_s, HandleExtractCommand },
1115
0
    { "POPULATE"_s, HandlePopulateCommand },
1116
0
    { "IMPORT"_s, HandleImportCommand },
1117
0
  };
1118
1119
0
  return subcommand(args[0], args, status);
1120
0
}