Coverage Report

Created: 2026-02-09 06:05

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